diff --git a/.travis.yml b/.travis.yml index 6779588..dad47e6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -36,11 +36,15 @@ env: # test individual features - FEATURES="backend_winit" - FEATURES="backend_drm" + - FEATURES="backend_drm_legacy" + - FEATURES="backend_drm_gbm" + - FEATURES="backend_drm_egl" + - FEATURES="backend_egl" - FEATURES="backend_libinput" - FEATURES="backend_udev" - FEATURES="backend_session" - - FEATURES="backend_session_udev" - FEATURES="backend_session_logind" + - FEATURES="renderer_gl" - FEATURES="renderer_glium" - FEATURES="xwayland" # test default features diff --git a/Cargo.toml b/Cargo.toml index 64de8fb..f08b880 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,9 +10,9 @@ repository = "https://github.com/Smithay/smithay" members = [ "anvil" ] [dependencies] -wayland-server = "0.21.1" -wayland-sys = "0.21.1" +wayland-server = "0.21.6" wayland-commons = "0.21.1" +wayland-sys = { version = "0.21.6", optional = true } nix = "0.11" xkbcommon = "0.2.1" tempfile = "2.1.5" @@ -21,29 +21,37 @@ slog-stdlog = "3.0.2" libloading = "0.4.0" wayland-client = { version = "0.21.1", features = ["egl"], optional = true } winit = { version = "0.18.0", optional = true } -drm = { version = "^0.3.1", optional = true } -gbm = { version = "^0.4.0", optional = true, default-features = false, features = ["drm-support"] } +drm = { version = "^0.3.4", optional = true } +gbm = { version = "^0.5.0", optional = true, default-features = false, features = ["drm-support"] } glium = { version = "0.19.0", optional = true, default-features = false } -input = { version = "0.4.0", optional = true } +input = { version = "0.4.1", optional = true } udev = { version = "0.2.0", optional = true } dbus = { version = "0.6.1", optional = true } systemd = { version = "^0.2.0", optional = true } -wayland-protocols = { version = "0.21.1", features = ["unstable_protocols", "native_server"] } +wayland-protocols = { version = "0.21.3", features = ["unstable_protocols", "server"] } image = "0.17.0" error-chain = "0.11.0" lazy_static = "1.0.0" +[dev-dependencies] +slog-term = "2.3" + [build-dependencies] -gl_generator = "0.9" +gl_generator = { version = "0.9", optional = true } [features] -default = ["backend_winit", "backend_drm", "backend_libinput", "backend_udev", "renderer_glium", "xwayland"] -backend_winit = ["winit", "wayland-server/dlopen", "wayland-client/dlopen"] -backend_drm = ["drm", "gbm"] +default = ["backend_winit", "backend_drm_legacy", "backend_drm_gbm", "backend_drm_egl", "backend_libinput", "backend_udev", "backend_session", "renderer_glium", "xwayland"] +backend_winit = ["winit", "wayland-server/dlopen", "wayland-client/dlopen", "backend_egl", "renderer_gl", "native_lib"] +backend_drm = ["drm"] +backend_drm_legacy = ["backend_drm"] +backend_drm_gbm = ["backend_drm", "gbm"] +backend_drm_egl = ["backend_drm", "backend_egl"] +backend_egl = ["gl_generator"] backend_libinput = ["input"] backend_session = [] -backend_session_udev = ["udev", "backend_session"] +backend_udev = ["udev"] backend_session_logind = ["dbus", "systemd", "backend_session"] -backend_udev = ["udev", "backend_drm", "backend_session_udev"] -renderer_glium = ["glium"] +renderer_gl = ["gl_generator"] +renderer_glium = ["renderer_gl", "glium"] +native_lib = ["wayland-sys", "wayland-server/native_lib", "wayland-protocols/native_server"] xwayland = [] \ No newline at end of file diff --git a/anvil/Cargo.toml b/anvil/Cargo.toml index fec642d..536a207 100644 --- a/anvil/Cargo.toml +++ b/anvil/Cargo.toml @@ -23,8 +23,8 @@ features = [ "renderer_glium" ] gl_generator = "0.9" [features] -default = [ "winit", "tty_launch", "udev" ] +default = [ "winit", "egl", "udev" ] +egl = [ "smithay/native_lib" ] winit = [ "smithay/backend_winit" ] -tty_launch = [ "smithay/backend_libinput", "smithay/backend_drm" ] -udev = [ "tty_launch", "smithay/backend_udev" ] +udev = [ "smithay/backend_libinput", "smithay/backend_drm_legacy", "smithay/backend_drm_gbm", "smithay/backend_drm_egl", "smithay/backend_udev", "smithay/backend_session" ] logind = [ "smithay/backend_session_logind" ] diff --git a/anvil/src/glium_drawer.rs b/anvil/src/glium_drawer.rs index 135ed5f..440ed5a 100644 --- a/anvil/src/glium_drawer.rs +++ b/anvil/src/glium_drawer.rs @@ -11,20 +11,18 @@ use glium::{ }; use slog::Logger; +#[cfg(feature = "egl")] +use smithay::backend::egl::EGLDisplay; use smithay::{ - backend::graphics::{ - egl::{ - error::Result as EGLResult, - wayland::{BufferAccessError, EGLDisplay, EGLImages, EGLWaylandExtensions, Format}, - EGLGraphicsBackend, - }, - glium::GliumGraphicsBackend, + backend::{ + egl::{BufferAccessError, EGLImages, Format}, + graphics::{gl::GLGraphicsBackend, glium::GliumGraphicsBackend}, }, wayland::{ compositor::{roles::Role, SubsurfaceRole, TraversalAction}, shm::with_buffer_contents as shm_buffer_contents, }, - wayland_server::{protocol::wl_buffer, Display, Resource}, + wayland_server::{protocol::wl_buffer, Resource}, }; use shaders; @@ -38,22 +36,24 @@ struct Vertex { implement_vertex!(Vertex, position, tex_coords); -pub struct GliumDrawer { +pub struct GliumDrawer { display: GliumGraphicsBackend, vertex_buffer: glium::VertexBuffer, index_buffer: glium::IndexBuffer, programs: [glium::Program; shaders::FRAGMENT_COUNT], + #[cfg(feature = "egl")] egl_display: Rc>>, log: Logger, } -impl GliumDrawer { +impl GliumDrawer { pub fn borrow(&self) -> Ref { self.display.borrow() } } -impl> + EGLGraphicsBackend + 'static> GliumDrawer { +impl> + GLGraphicsBackend + 'static> GliumDrawer { + #[cfg(feature = "egl")] pub fn init(backend: T, egl_display: Rc>>, log: Logger) -> GliumDrawer { let display = backend.into(); @@ -78,7 +78,8 @@ impl> + EGLGraphicsBackend + 'static> GliumDrawe tex_coords: [1.0, 0.0], }, ], - ).unwrap(); + ) + .unwrap(); // building the index buffer let index_buffer = @@ -95,9 +96,53 @@ impl> + EGLGraphicsBackend + 'static> GliumDrawe log, } } + + #[cfg(not(feature = "egl"))] + pub fn init(backend: T, log: Logger) -> GliumDrawer { + let display = backend.into(); + + // building the vertex buffer, which contains all the vertices that we will draw + let vertex_buffer = glium::VertexBuffer::new( + &display, + &[ + Vertex { + position: [0.0, 0.0], + tex_coords: [0.0, 0.0], + }, + Vertex { + position: [0.0, 1.0], + tex_coords: [0.0, 1.0], + }, + Vertex { + position: [1.0, 1.0], + tex_coords: [1.0, 1.0], + }, + Vertex { + position: [1.0, 0.0], + tex_coords: [1.0, 0.0], + }, + ], + ) + .unwrap(); + + // building the index buffer + let index_buffer = + glium::IndexBuffer::new(&display, PrimitiveType::TriangleStrip, &[1 as u16, 2, 0, 3]).unwrap(); + + let programs = opengl_programs!(&display); + + GliumDrawer { + display, + vertex_buffer, + index_buffer, + programs, + log, + } + } } -impl GliumDrawer { +impl GliumDrawer { + #[cfg(feature = "egl")] pub fn texture_from_buffer(&self, buffer: Resource) -> Result { // try to retrieve the egl contents of this buffer let images = if let Some(display) = &self.egl_display.borrow().as_ref() { @@ -122,7 +167,8 @@ impl GliumDrawer { MipmapsOption::NoMipmap, images.width, images.height, - ).unwrap(); + ) + .unwrap(); unsafe { images .bind_to_texture(0, opengl_texture.get_id()) @@ -138,26 +184,7 @@ impl GliumDrawer { } Err(BufferAccessError::NotManaged(buffer)) => { // this is not an EGL buffer, try SHM - match shm_buffer_contents(&buffer, |slice, data| { - ::shm_load::load_shm_buffer(data, slice) - .map(|(image, kind)| (Texture2d::new(&self.display, image).unwrap(), kind, data)) - }) { - Ok(Ok((texture, kind, data))) => Ok(TextureMetadata { - texture, - fragment: kind, - y_inverted: false, - dimensions: (data.width as u32, data.height as u32), - images: None, - }), - Ok(Err(format)) => { - warn!(self.log, "Unsupported SHM buffer format"; "format" => format!("{:?}", format)); - Err(()) - } - Err(err) => { - warn!(self.log, "Unable to load buffer contents"; "err" => format!("{:?}", err)); - Err(()) - } - } + self.texture_from_shm_buffer(buffer) } Err(err) => { error!(self.log, "EGL error"; "err" => format!("{:?}", err)); @@ -166,6 +193,35 @@ impl GliumDrawer { } } + #[cfg(not(feature = "egl"))] + pub fn texture_from_buffer(&self, buffer: Resource) -> Result { + self.texture_from_shm_buffer(buffer) + } + + fn texture_from_shm_buffer(&self, buffer: Resource) -> Result { + match shm_buffer_contents(&buffer, |slice, data| { + ::shm_load::load_shm_buffer(data, slice) + .map(|(image, kind)| (Texture2d::new(&self.display, image).unwrap(), kind, data)) + }) { + Ok(Ok((texture, kind, data))) => Ok(TextureMetadata { + texture, + fragment: kind, + y_inverted: false, + dimensions: (data.width as u32, data.height as u32), + #[cfg(feature = "egl")] + images: None, + }), + Ok(Err(format)) => { + warn!(self.log, "Unsupported SHM buffer format"; "format" => format!("{:?}", format)); + Err(()) + } + Err(err) => { + warn!(self.log, "Unable to load buffer contents"; "err" => format!("{:?}", err)); + Err(()) + } + } + } + pub fn render_texture( &self, target: &mut glium::Frame, @@ -208,7 +264,8 @@ impl GliumDrawer { blend: blending, ..Default::default() }, - ).unwrap(); + ) + .unwrap(); } #[inline] @@ -217,21 +274,16 @@ impl GliumDrawer { } } -impl EGLWaylandExtensions for GliumDrawer { - fn bind_wl_display(&self, display: &Display) -> EGLResult { - self.display.bind_wl_display(display) - } -} - pub struct TextureMetadata { pub texture: Texture2d, pub fragment: usize, pub y_inverted: bool, pub dimensions: (u32, u32), + #[cfg(feature = "egl")] images: Option, } -impl GliumDrawer { +impl GliumDrawer { pub fn draw_windows(&self, window_map: &MyWindowMap, compositor_token: MyCompositorToken, log: &Logger) { let mut frame = self.draw(); frame.clear(None, Some((0.8, 0.8, 0.9, 1.0)), false, Some(1.0), None); @@ -290,7 +342,8 @@ impl GliumDrawer { TraversalAction::SkipChildren } }, - ).unwrap(); + ) + .unwrap(); } }); } diff --git a/anvil/src/input_handler.rs b/anvil/src/input_handler.rs index 31b0563..cdb397f 100644 --- a/anvil/src/input_handler.rs +++ b/anvil/src/input_handler.rs @@ -11,7 +11,7 @@ use std::{ use slog::Logger; #[cfg(feature = "udev")] -use smithay::backend::session::auto::AutoSession; +use smithay::backend::session::{auto::AutoSession, Session}; use smithay::{ backend::input::{ self, Event, InputBackend, InputHandler, KeyState, KeyboardKeyEvent, PointerAxisEvent, @@ -131,13 +131,15 @@ impl InputHandler for AnvilInputHandler { info!(self.log, "Quitting."); self.running.store(false, Ordering::SeqCst); } - #[cfg(feature = "tty_lauch")] - KeyAction::VtSwitch(vt) => if let Some(ref mut session) = self.session { - info!(log, "Trying to switch to vt {}", vt); - if let Err(err) = session.change_vt(vt) { - error!(log, "Error switching to vt {}: {}", vt, err); + #[cfg(feature = "udev")] + KeyAction::VtSwitch(vt) => { + if let Some(ref mut session) = self.session { + info!(log, "Trying to switch to vt {}", vt); + if let Err(err) = session.change_vt(vt) { + error!(log, "Error switching to vt {}: {}", vt, err); + } } - }, + } KeyAction::Run(cmd) => { info!(self.log, "Starting program"; "cmd" => cmd.clone()); if let Err(e) = Command::new(&cmd).spawn() { diff --git a/anvil/src/main.rs b/anvil/src/main.rs index ce4523e..b920d2e 100644 --- a/anvil/src/main.rs +++ b/anvil/src/main.rs @@ -16,8 +16,6 @@ use smithay::wayland_server::{calloop::EventLoop, Display}; mod shaders; mod glium_drawer; mod input_handler; -#[cfg(feature = "tty_launch")] -mod raw_drm; mod shell; mod shm_load; #[cfg(feature = "udev")] @@ -29,8 +27,6 @@ mod winit; static POSSIBLE_BACKENDS: &'static [&'static str] = &[ #[cfg(feature = "winit")] "--winit : Run anvil as a X11 or Wayland client using winit.", - #[cfg(feature = "tty_launch")] - "--tty-raw : Run anvil as a raw DRM client (requires root).", #[cfg(feature = "udev")] "--tty-udev : Run anvil as a tty udev client (requires root if without logind).", ]; @@ -54,13 +50,6 @@ fn main() { crit!(log, "Failed to initialize winit backend."); } } - #[cfg(feature = "tty_launch")] - Some("--tty-raw") => { - info!(log, "Starting anvil on a tty using raw DRM"); - if let Err(()) = raw_drm::run_raw_drm(display, event_loop, log.clone()) { - crit!(log, "Failed to initialize tty backend."); - } - } #[cfg(feature = "udev")] Some("--tty-udev") => { info!(log, "Starting anvil on a tty using udev"); diff --git a/anvil/src/raw_drm.rs b/anvil/src/raw_drm.rs deleted file mode 100644 index 0d0388f..0000000 --- a/anvil/src/raw_drm.rs +++ /dev/null @@ -1,169 +0,0 @@ -use std::{ - cell::RefCell, - fs::{File, OpenOptions}, - os::unix::io::{AsRawFd, RawFd}, - rc::Rc, - time::Duration, -}; - -use smithay::{ - backend::{ - drm::{drm_device_bind, DrmBackend, DrmDevice, DrmHandler}, - graphics::egl::wayland::EGLWaylandExtensions, - }, - drm::{ - control::{ - connector::{Info as ConnectorInfo, State as ConnectorState}, - crtc, - encoder::Info as EncoderInfo, - Device as ControlDevice, ResourceInfo, - }, - result::Error as DrmError, - Device as BasicDevice, - }, - wayland::{compositor::CompositorToken, shm::init_shm_global}, - wayland_server::{calloop::EventLoop, Display}, -}; - -use glium::Surface; -use slog::Logger; - -use glium_drawer::GliumDrawer; -use shell::{init_shell, MyWindowMap, Roles, SurfaceData}; - -#[derive(Debug)] -pub struct Card(File); - -impl AsRawFd for Card { - fn as_raw_fd(&self) -> RawFd { - self.0.as_raw_fd() - } -} - -impl BasicDevice for Card {} -impl ControlDevice for Card {} - -pub fn run_raw_drm(mut display: Display, mut event_loop: EventLoop<()>, log: Logger) -> Result<(), ()> { - /* - * Initialize the drm backend - */ - // "Find" a suitable drm device - let mut options = OpenOptions::new(); - options.read(true); - options.write(true); - let mut device = - DrmDevice::new(Card(options.clone().open("/dev/dri/card0").unwrap()), log.clone()).unwrap(); - - // Get a set of all modesetting resource handles (excluding planes): - let res_handles = device.resource_handles().unwrap(); - - // Use first connected connector - let connector_info = res_handles - .connectors() - .iter() - .map(|conn| ConnectorInfo::load_from_device(&device, *conn).unwrap()) - .find(|conn| conn.connection_state() == ConnectorState::Connected) - .unwrap(); - - // Use the first encoder - let encoder_info = EncoderInfo::load_from_device(&device, connector_info.encoders()[0]).unwrap(); - - // use the connected crtc if any - let crtc = encoder_info - .current_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() - }); - - // Assuming we found a good connector and loaded the info into `connector_info` - let mode = connector_info.modes()[0]; // Use first mode (usually highest resoltion, but in reality you should filter and sort and check and match with other connectors, if you use more then one.) - - // Initialize the hardware backend - let backend = device - .create_backend(crtc, mode, vec![connector_info.handle()]) - .unwrap(); - let egl_display = Rc::new(RefCell::new( - if let Ok(egl_display) = backend.bind_wl_display(&display) { - info!(log, "EGL hardware-acceleration enabled"); - Some(egl_display) - } else { - None - }, - )); - let renderer = GliumDrawer::init(backend, egl_display, log.clone()); - { - /* - * Initialize Glium - */ - let mut frame = renderer.draw(); - frame.clear_color(0.8, 0.8, 0.9, 1.0); - frame.finish().unwrap(); - } - - /* - * Initialize the globals - */ - - init_shm_global(&mut display, vec![], log.clone()); - - let (compositor_token, _, _, window_map) = init_shell(&mut display, log.clone()); - - /* - * Add a listening socket: - */ - let name = display.add_socket_auto().unwrap().into_string().unwrap(); - println!("Listening on socket: {}", name); - - /* - * Register the DrmDevice on the EventLoop - */ - let _source = drm_device_bind( - &event_loop.handle(), - device, - DrmHandlerImpl { - compositor_token, - window_map: window_map.clone(), - drawer: renderer, - logger: log, - }, - ).map_err(|(err, _)| err) - .unwrap(); - - loop { - event_loop - .dispatch(Some(::std::time::Duration::from_millis(16)), &mut ()) - .unwrap(); - display.flush_clients(); - - window_map.borrow_mut().refresh(); - } -} - -pub struct DrmHandlerImpl { - compositor_token: CompositorToken, - window_map: Rc>, - drawer: GliumDrawer>, - logger: ::slog::Logger, -} - -impl DrmHandler for DrmHandlerImpl { - fn ready( - &mut self, - _device: &mut DrmDevice, - _crtc: crtc::Handle, - _frame: u32, - _duration: Duration, - ) { - self.drawer - .draw_windows(&*self.window_map.borrow(), self.compositor_token, &self.logger); - } - - fn error(&mut self, _device: &mut DrmDevice, error: DrmError) { - panic!("{:?}", error); - } -} diff --git a/anvil/src/udev.rs b/anvil/src/udev.rs index a3de2aa..ffe09fa 100644 --- a/anvil/src/udev.rs +++ b/anvil/src/udev.rs @@ -2,41 +2,42 @@ use std::{ cell::RefCell, collections::HashMap, io::Error as IoError, + os::unix::io::{AsRawFd, RawFd}, path::PathBuf, rc::Rc, sync::{ atomic::{AtomicBool, Ordering}, Arc, }, - time::Duration, }; -use glium::Surface; +use glium::Surface as GliumSurface; use slog::Logger; +#[cfg(feature = "egl")] +use smithay::backend::egl::{EGLDisplay, EGLGraphicsBackend}; use smithay::{ backend::{ - drm::{DevPath, DrmBackend, DrmDevice, DrmHandler}, - graphics::{ - egl::wayland::{EGLDisplay, EGLWaylandExtensions}, - GraphicsBackend, + drm::{ + dev_t, device_bind, + egl::{EglDevice, EglSurface}, + gbm::{egl::Gbm as EglGbmBackend, GbmDevice}, + legacy::LegacyDrmDevice, + DevPath, Device, DeviceHandler, Surface, }, + graphics::CursorBackend, input::InputBackend, libinput::{libinput_bind, LibinputInputBackend, LibinputSessionInterface}, session::{ auto::{auto_session_bind, AutoSession}, - Session, SessionNotifier, + notify_multiplexer, AsSessionObserver, OFlag, Session, SessionNotifier, }, - udev::{primary_gpu, udev_backend_bind, SessionFdDrmDevice, UdevBackend, UdevHandler}, + udev::{primary_gpu, udev_backend_bind, UdevBackend, UdevHandler}, }, - drm::{ - control::{ - connector::{Info as ConnectorInfo, State as ConnectorState}, - crtc, - encoder::Info as EncoderInfo, - Device as ControlDevice, ResourceInfo, - }, - result::Error as DrmError, + drm::control::{ + connector::{Info as ConnectorInfo, State as ConnectorState}, + crtc, + encoder::Info as EncoderInfo, }, image::{ImageBuffer, Rgba}, input::Libinput, @@ -47,18 +48,38 @@ use smithay::{ seat::{Seat, XkbConfig}, shm::init_shm_global, }, - wayland_server::{calloop::EventLoop, protocol::wl_output, Display}, + wayland_server::{ + calloop::{ + generic::{EventedFd, Generic}, + EventLoop, LoopHandle, Source, + }, + protocol::wl_output, + Display, + }, }; use glium_drawer::GliumDrawer; use input_handler::AnvilInputHandler; use shell::{init_shell, MyWindowMap, Roles, SurfaceData}; +pub struct SessionFd(RawFd); +impl AsRawFd for SessionFd { + fn as_raw_fd(&self) -> RawFd { + self.0 + } +} + +type RenderDevice = + EglDevice>, GbmDevice>>; +type RenderSurface = + EglSurface>, GbmDevice>>; + pub fn run_udev(mut display: Display, mut event_loop: EventLoop<()>, log: Logger) -> Result<(), ()> { let name = display.add_socket_auto().unwrap().into_string().unwrap(); info!(log, "Listening on wayland socket"; "name" => name.clone()); ::std::env::set_var("WAYLAND_DISPLAY", name); + #[cfg(feature = "egl")] let active_egl_context = Rc::new(RefCell::new(None)); let display = Rc::new(RefCell::new(display)); @@ -74,6 +95,8 @@ pub fn run_udev(mut display: Display, mut event_loop: EventLoop<()>, log: Logger * Initialize session */ let (session, mut notifier) = AutoSession::new(log.clone()).ok_or(())?; + let (udev_observer, udev_notifier) = notify_multiplexer(); + let udev_session_id = notifier.register(udev_observer); let running = Arc::new(AtomicBool::new(true)); @@ -88,26 +111,31 @@ pub fn run_udev(mut display: Display, mut event_loop: EventLoop<()>, log: Logger let primary_gpu = primary_gpu(&context, &seat).unwrap_or_default(); let bytes = include_bytes!("../resources/cursor2.rgba"); - let mut udev_backend = UdevBackend::new( - event_loop.handle(), + let udev_backend = UdevBackend::new( &context, - session.clone(), UdevHandlerImpl { compositor_token, + #[cfg(feature = "egl")] active_egl_context, + session: session.clone(), backends: HashMap::new(), display: display.clone(), primary_gpu, window_map: window_map.clone(), pointer_location: pointer_location.clone(), pointer_image: ImageBuffer::from_raw(64, 64, bytes.to_vec()).unwrap(), + loop_handle: event_loop.handle(), + notifier: udev_notifier, logger: log.clone(), }, + seat.clone(), log.clone(), - ).map_err(|_| ())?; - - let udev_session_id = notifier.register(&mut udev_backend); + ) + .map_err(|_| ())?; + /* + * Initialize wayland clipboard + */ init_data_device( &mut display.borrow_mut(), |_| {}, @@ -115,14 +143,21 @@ pub fn run_udev(mut display: Display, mut event_loop: EventLoop<()>, log: Logger log.clone(), ); + /* + * Initialize wayland input object + */ let (mut w_seat, _) = Seat::new(&mut display.borrow_mut(), session.seat(), log.clone()); let pointer = w_seat.add_pointer(); let keyboard = w_seat .add_keyboard(XkbConfig::default(), 1000, 500, |seat, focus| { set_data_device_focus(seat, focus.and_then(|s| s.client())) - }).expect("Failed to initialize the keyboard"); + }) + .expect("Failed to initialize the keyboard"); + /* + * Initialize a fake output (we render one screen to every device in this example) + */ let (output, _output_global) = Output::new( &mut display.borrow_mut(), "Drm".into(), @@ -157,7 +192,7 @@ pub fn run_udev(mut display: Display, mut event_loop: EventLoop<()>, log: Logger */ let mut libinput_context = Libinput::new_from_udev::>(session.clone().into(), &context); - let libinput_session_id = notifier.register(&mut libinput_context); + let libinput_session_id = notifier.register(libinput_context.observer()); libinput_context.udev_assign_seat(&seat).unwrap(); let mut libinput_backend = LibinputInputBackend::new(libinput_context, log.clone()); libinput_backend.set_handler(AnvilInputHandler::new_with_session( @@ -170,15 +205,23 @@ pub fn run_udev(mut display: Display, mut event_loop: EventLoop<()>, log: Logger pointer_location, session, )); - let libinput_event_source = libinput_bind(libinput_backend, event_loop.handle()) - .map_err(|(e, _)| e) - .unwrap(); + /* + * Bind all our objects that get driven by the event loop + */ + let libinput_event_source = libinput_bind(libinput_backend, event_loop.handle()) + .map_err(|e| -> IoError { e.into() }) + .unwrap(); let session_event_source = auto_session_bind(notifier, &event_loop.handle()) .map_err(|(e, _)| e) .unwrap(); - let udev_event_source = udev_backend_bind(udev_backend).unwrap(); + let udev_event_source = udev_backend_bind(udev_backend, &event_loop.handle()) + .map_err(|e| -> IoError { e.into() }) + .unwrap(); + /* + * And run our loop + */ while running.load(Ordering::SeqCst) { if event_loop .dispatch(Some(::std::time::Duration::from_millis(16)), &mut ()) @@ -191,9 +234,12 @@ pub fn run_udev(mut display: Display, mut event_loop: EventLoop<()>, log: Logger } } + // Cleanup stuff + window_map.borrow_mut().clear(); + let mut notifier = session_event_source.unbind(); - notifier.unregister(udev_session_id); notifier.unregister(libinput_session_id); + notifier.unregister(udev_session_id); libinput_event_source.remove(); udev_event_source.remove(); @@ -201,24 +247,36 @@ pub fn run_udev(mut display: Display, mut event_loop: EventLoop<()>, log: Logger Ok(()) } -struct UdevHandlerImpl { +struct UdevHandlerImpl { compositor_token: CompositorToken, + #[cfg(feature = "egl")] active_egl_context: Rc>>, - backends: HashMap>>>>>, + session: AutoSession, + backends: HashMap< + dev_t, + ( + S::Id, + Source>>, + Rc>>>, + ), + >, display: Rc>, primary_gpu: Option, window_map: Rc>, pointer_location: Rc>, pointer_image: ImageBuffer, Vec>, + loop_handle: LoopHandle, + notifier: S, logger: ::slog::Logger, } -impl UdevHandlerImpl { +impl UdevHandlerImpl { + #[cfg(feature = "egl")] pub fn scan_connectors( - &self, - device: &mut DrmDevice, + device: &mut RenderDevice, egl_display: Rc>>, - ) -> HashMap>> { + logger: &::slog::Logger, + ) -> HashMap> { // Get a set of all modesetting resource handles (excluding planes): let res_handles = device.resource_handles().unwrap(); @@ -226,9 +284,9 @@ impl UdevHandlerImpl { let connector_infos: Vec = res_handles .connectors() .iter() - .map(|conn| ConnectorInfo::load_from_device(device, *conn).unwrap()) + .map(|conn| device.resource_info::(*conn).unwrap()) .filter(|conn| conn.connection_state() == ConnectorState::Connected) - .inspect(|conn| info!(self.logger, "Connected: {:?}", conn.connector_type())) + .inspect(|conn| info!(logger, "Connected: {:?}", conn.connector_type())) .collect(); let mut backends = HashMap::new(); @@ -238,33 +296,58 @@ impl UdevHandlerImpl { let encoder_infos = connector_info .encoders() .iter() - .flat_map(|encoder_handle| EncoderInfo::load_from_device(device, *encoder_handle)) + .flat_map(|encoder_handle| device.resource_info::(*encoder_handle)) .collect::>(); for encoder_info in encoder_infos { for crtc in res_handles.filter_crtcs(encoder_info.possible_crtcs()) { if !backends.contains_key(&crtc) { - let mode = connector_info.modes()[0]; // Use first mode (usually highest resoltion, but in reality you should filter and sort and check and match with other connectors, if you use more then one.) - // create a backend let renderer = GliumDrawer::init( - device - .create_backend(crtc, mode, vec![connector_info.handle()]) - .unwrap(), + device.create_surface(crtc).unwrap(), egl_display.clone(), - self.logger.clone(), + logger.clone(), ); - // create cursor - renderer - .borrow() - .set_cursor_representation(&self.pointer_image, (2, 2)) - .unwrap(); + backends.insert(crtc, renderer); + break; + } + } + } + } - // render first frame - { - let mut frame = renderer.draw(); - frame.clear_color(0.8, 0.8, 0.9, 1.0); - frame.finish().unwrap(); - } + backends + } + + #[cfg(not(feature = "egl"))] + pub fn scan_connectors( + device: &mut RenderDevice, + logger: &::slog::Logger, + ) -> HashMap> { + // Get a set of all modesetting resource handles (excluding planes): + let res_handles = device.resource_handles().unwrap(); + + // Use first connected connector + let connector_infos: Vec = res_handles + .connectors() + .iter() + .map(|conn| device.resource_info::(*conn).unwrap()) + .filter(|conn| conn.connection_state() == ConnectorState::Connected) + .inspect(|conn| info!(logger, "Connected: {:?}", conn.connector_type())) + .collect(); + + let mut backends = HashMap::new(); + + // very naive way of finding good crtc/encoder/connector combinations. This problem is np-complete + for connector_info in connector_infos { + let encoder_infos = connector_info + .encoders() + .iter() + .flat_map(|encoder_handle| device.resource_info::(*encoder_handle)) + .collect::>(); + 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, renderer); break; @@ -277,64 +360,151 @@ impl UdevHandlerImpl { } } -impl UdevHandler for UdevHandlerImpl { - fn device_added(&mut self, device: &mut DrmDevice) -> Option { - // init hardware acceleration on the primary gpu. - if device.dev_path().and_then(|path| path.canonicalize().ok()) == self.primary_gpu { - *self.active_egl_context.borrow_mut() = device.bind_wl_display(&*self.display.borrow()).ok(); +impl UdevHandler for UdevHandlerImpl { + fn device_added(&mut self, _device: dev_t, path: PathBuf) { + // Try to open the device + if let Some(mut device) = self + .session + .open( + &path, + OFlag::O_RDWR | OFlag::O_CLOEXEC | OFlag::O_NOCTTY | OFlag::O_NONBLOCK, + ) + .ok() + .and_then(|fd| LegacyDrmDevice::new(SessionFd(fd), self.logger.clone()).ok()) + .and_then(|drm| GbmDevice::new(drm, self.logger.clone()).ok()) + .and_then(|gbm| EglDevice::new(gbm, self.logger.clone()).ok()) + { + // init hardware acceleration on the primary gpu. + #[cfg(feature = "egl")] + { + if path.canonicalize().ok() == self.primary_gpu { + *self.active_egl_context.borrow_mut() = + device.bind_wl_display(&*self.display.borrow()).ok(); + } + } + + #[cfg(feature = "egl")] + let backends = Rc::new(RefCell::new(UdevHandlerImpl::::scan_connectors( + &mut device, + self.active_egl_context.clone(), + &self.logger, + ))); + + #[cfg(not(feature = "egl"))] + let backends = Rc::new(RefCell::new(UdevHandlerImpl::::scan_connectors( + &mut device, + &self.logger, + ))); + + // Set the handler. + // Note: if you replicate this (very simple) structure, it is rather easy + // to introduce reference cycles with Rc. Be sure about your drop order + device.set_handler(DrmHandlerImpl { + compositor_token: self.compositor_token, + backends: backends.clone(), + window_map: self.window_map.clone(), + pointer_location: self.pointer_location.clone(), + logger: self.logger.clone(), + }); + + let device_session_id = self.notifier.register(device.observer()); + let dev_id = device.device_id(); + let event_source = device_bind(&self.loop_handle, device) + .map_err(|e| -> IoError { e.into() }) + .unwrap(); + + for renderer in backends.borrow_mut().values() { + // create cursor + renderer + .borrow() + .set_cursor_representation(&self.pointer_image, (2, 2)) + .unwrap(); + + // render first frame + { + let mut frame = renderer.draw(); + frame.clear_color(0.8, 0.8, 0.9, 1.0); + frame.finish().unwrap(); + } + } + + self.backends + .insert(dev_id, (device_session_id, event_source, backends)); } - - let backends = Rc::new(RefCell::new( - self.scan_connectors(device, self.active_egl_context.clone()), - )); - self.backends.insert(device.device_id(), backends.clone()); - - Some(DrmHandlerImpl { - compositor_token: self.compositor_token, - backends, - window_map: self.window_map.clone(), - pointer_location: self.pointer_location.clone(), - logger: self.logger.clone(), - }) } - fn device_changed(&mut self, device: &mut DrmDevice) { - //quick and dirt, just re-init all backends - let backends = &self.backends[&device.device_id()]; - *backends.borrow_mut() = self.scan_connectors(device, self.active_egl_context.clone()); + fn device_changed(&mut self, device: dev_t) { + //quick and dirty, just re-init all backends + if let Some((_, ref mut evt_source, ref backends)) = self.backends.get_mut(&device) { + let source = evt_source.clone_inner(); + let mut evented = source.borrow_mut(); + let mut backends = backends.borrow_mut(); + #[cfg(feature = "egl")] + let new_backends = UdevHandlerImpl::::scan_connectors( + &mut (*evented).0, + self.active_egl_context.clone(), + &self.logger, + ); + #[cfg(not(feature = "egl"))] + let new_backends = UdevHandlerImpl::::scan_connectors(&mut (*evented).0, &self.logger); + *backends = new_backends; + + for renderer in backends.values() { + // create cursor + renderer + .borrow() + .set_cursor_representation(&self.pointer_image, (2, 2)) + .unwrap(); + + // render first frame + { + let mut frame = renderer.draw(); + frame.clear_color(0.8, 0.8, 0.9, 1.0); + frame.finish().unwrap(); + } + } + } } - fn device_removed(&mut self, device: &mut DrmDevice) { + fn device_removed(&mut self, device: dev_t) { // drop the backends on this side - self.backends.remove(&device.device_id()); + if let Some((id, evt_source, renderers)) = self.backends.remove(&device) { + // drop surfaces + renderers.borrow_mut().clear(); + debug!(self.logger, "Surfaces dropped"); - // don't use hardware acceleration anymore, if this was the primary gpu - if device.dev_path().and_then(|path| path.canonicalize().ok()) == self.primary_gpu { - *self.active_egl_context.borrow_mut() = None; + let device = Rc::try_unwrap(evt_source.remove().unwrap()) + .map_err(|_| "This should not happend") + .unwrap() + .into_inner() + .0; + + // don't use hardware acceleration anymore, if this was the primary gpu + #[cfg(feature = "egl")] + { + if device.dev_path().and_then(|path| path.canonicalize().ok()) == self.primary_gpu { + *self.active_egl_context.borrow_mut() = None; + } + } + + self.notifier.unregister(id); + debug!(self.logger, "Dropping device"); } } - - fn error(&mut self, error: IoError) { - error!(self.logger, "{:?}", error); - } } pub struct DrmHandlerImpl { compositor_token: CompositorToken, - backends: Rc>>>>, + backends: Rc>>>, window_map: Rc>, pointer_location: Rc>, logger: ::slog::Logger, } -impl DrmHandler for DrmHandlerImpl { - fn ready( - &mut self, - _device: &mut DrmDevice, - crtc: crtc::Handle, - _frame: u32, - _duration: Duration, - ) { +impl DeviceHandler for DrmHandlerImpl { + type Device = RenderDevice; + + fn vblank(&mut self, crtc: crtc::Handle) { if let Some(drawer) = self.backends.borrow().get(&crtc) { { let (x, y) = *self.pointer_location.borrow(); @@ -343,11 +513,12 @@ impl DrmHandler for DrmHandlerImpl { .set_cursor_position(x.trunc().abs() as u32, y.trunc().abs() as u32); } + // and draw in sync with our monitor drawer.draw_windows(&*self.window_map.borrow(), self.compositor_token, &self.logger); } } - fn error(&mut self, _device: &mut DrmDevice, error: DrmError) { + fn error(&mut self, error: ::Error) { error!(self.logger, "{:?}", error); } } diff --git a/anvil/src/winit.rs b/anvil/src/winit.rs index 4d191bd..f405e4a 100644 --- a/anvil/src/winit.rs +++ b/anvil/src/winit.rs @@ -5,11 +5,7 @@ use std::{ }; use smithay::{ - backend::{ - graphics::egl::{wayland::EGLWaylandExtensions, EGLGraphicsBackend}, - input::InputBackend, - winit, - }, + backend::{egl::EGLGraphicsBackend, graphics::gl::GLGraphicsBackend, input::InputBackend, winit}, wayland::{ data_device::{default_action_chooser, init_data_device, set_data_device_focus}, output::{Mode, Output, PhysicalProperties}, @@ -63,7 +59,8 @@ pub fn run_winit(display: &mut Display, event_loop: &mut EventLoop<()>, log: Log let keyboard = seat .add_keyboard(XkbConfig::default(), 1000, 500, |seat, focus| { set_data_device_focus(seat, focus.and_then(|s| s.client())) - }).expect("Failed to initialize the keyboard"); + }) + .expect("Failed to initialize the keyboard"); let (output, _) = Output::new( display, diff --git a/build.rs b/build.rs index 700499f..dbd1dec 100644 --- a/build.rs +++ b/build.rs @@ -1,44 +1,56 @@ +#[cfg(any(feature = "backend_egl", feature = "renderer_gl"))] extern crate gl_generator; +#[cfg(any(feature = "backend_egl", feature = "renderer_gl"))] use gl_generator::{Api, Fallbacks, Profile, Registry}; use std::{env, fs::File, path::PathBuf}; +#[cfg(any(feature = "backend_egl", feature = "renderer_gl"))] fn main() { let dest = PathBuf::from(&env::var("OUT_DIR").unwrap()); println!("cargo:rerun-if-changed=build.rs"); - let mut file = File::create(&dest.join("egl_bindings.rs")).unwrap(); - Registry::new( - Api::Egl, - (1, 5), - Profile::Core, - Fallbacks::All, - [ - "EGL_KHR_create_context", - "EGL_EXT_create_context_robustness", - "EGL_KHR_create_context_no_error", - "EGL_KHR_platform_x11", - "EGL_KHR_platform_android", - "EGL_KHR_platform_wayland", - "EGL_KHR_platform_gbm", - "EGL_EXT_platform_base", - "EGL_EXT_platform_x11", - "EGL_MESA_platform_gbm", - "EGL_EXT_platform_wayland", - "EGL_EXT_platform_device", - "EGL_KHR_image_base", - ], - ).write_bindings(gl_generator::GlobalGenerator, &mut file) - .unwrap(); + if env::var_os("CARGO_FEATURE_BACKEND_EGL").is_some() { + let mut file = File::create(&dest.join("egl_bindings.rs")).unwrap(); + Registry::new( + Api::Egl, + (1, 5), + Profile::Core, + Fallbacks::All, + [ + "EGL_KHR_create_context", + "EGL_EXT_create_context_robustness", + "EGL_KHR_create_context_no_error", + "EGL_KHR_platform_x11", + "EGL_KHR_platform_android", + "EGL_KHR_platform_wayland", + "EGL_KHR_platform_gbm", + "EGL_EXT_platform_base", + "EGL_EXT_platform_x11", + "EGL_MESA_platform_gbm", + "EGL_EXT_platform_wayland", + "EGL_EXT_platform_device", + "EGL_KHR_image_base", + ], + ) + .write_bindings(gl_generator::GlobalGenerator, &mut file) + .unwrap(); + } - let mut file = File::create(&dest.join("gl_bindings.rs")).unwrap(); - Registry::new( - Api::Gles2, - (3, 2), - Profile::Compatibility, - Fallbacks::None, - ["GL_OES_EGL_image"], - ).write_bindings(gl_generator::GlobalGenerator, &mut file) - .unwrap(); + if env::var_os("CARGO_FEATURE_RENDERER_GL").is_some() { + let mut file = File::create(&dest.join("gl_bindings.rs")).unwrap(); + Registry::new( + Api::Gles2, + (3, 2), + Profile::Compatibility, + Fallbacks::None, + ["GL_OES_EGL_image"], + ) + .write_bindings(gl_generator::StructGenerator, &mut file) + .unwrap(); + } } + +#[cfg(not(any(feature = "backend_egl", feature = "renderer_gl")))] +fn main() {} diff --git a/examples/raw_drm.rs b/examples/raw_drm.rs new file mode 100644 index 0000000..ba14bff --- /dev/null +++ b/examples/raw_drm.rs @@ -0,0 +1,150 @@ +extern crate drm; +extern crate smithay; +#[macro_use] +extern crate slog; +extern crate slog_term; + +use drm::{buffer::PixelFormat, control::dumbbuffer::DumbBuffer}; +use slog::Drain; +use smithay::{ + backend::drm::{ + connector::{self, State as ConnectorState}, + crtc, device_bind, encoder, framebuffer, + legacy::{error::Error, LegacyDrmDevice, LegacyDrmSurface}, + ControlDevice, Device, DeviceHandler, RawSurface, ResourceInfo, Surface, + }, + wayland_server::calloop::EventLoop, +}; +use std::{ + fs::{File, OpenOptions}, + io::Error as IoError, + rc::Rc, + sync::Mutex, +}; + +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 mut device = LegacyDrmDevice::new(options.open("/dev/dri/card0").unwrap(), log.clone()).unwrap(); + + // 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::resource_info::(&device, *conn).unwrap()) + .find(|conn| conn.connection_state() == ConnectorState::Connected) + .unwrap(); + + // Use the first encoder + let encoder_info = Device::resource_info::(&device, connector_info.encoders()[0]).unwrap(); + + // use the connected crtc if any + let crtc = encoder_info + .current_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() + }); + + // Assuming we found a good connector and loaded the info into `connector_info` + let mode = connector_info.modes()[0]; // Use first mode (usually highest resoltion, but in reality you should filter and sort and check and match with other connectors, if you use more then one.) + + // Initialize the hardware backend + let surface = Rc::new(device.create_surface(crtc).unwrap()); + + surface.use_mode(Some(mode)).unwrap(); + for conn in surface.current_connectors().into_iter() { + if conn != connector_info.handle() { + surface.remove_connector(conn).unwrap(); + } + } + surface.add_connector(connector_info.handle()).unwrap(); + + /* + * Lets create buffers and framebuffers. + * We use drm-rs DumbBuffers, because they always work and require little to no setup. + * But they are very slow, this is just for demonstration purposes. + */ + let (w, h) = mode.size(); + let front_buffer = + DumbBuffer::create_from_device(&device, (w as u32, h as u32), PixelFormat::XRGB8888).unwrap(); + let front_framebuffer = device.create_framebuffer(&front_buffer).unwrap(); + let back_buffer = + DumbBuffer::create_from_device(&device, (w as u32, h as u32), PixelFormat::XRGB8888).unwrap(); + let back_framebuffer = device.create_framebuffer(&back_buffer).unwrap(); + + device.set_handler(DrmHandlerImpl { + current: front_framebuffer.handle(), + front: (front_buffer, front_framebuffer.clone()), + back: (back_buffer, back_framebuffer), + surface: surface.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 surface.commit_pending() { + surface.commit(front_framebuffer.handle()).unwrap(); + } + surface.page_flip(front_framebuffer.handle()).unwrap(); + + // Run + event_loop.run(None, &mut (), |_| {}).unwrap(); +} + +pub struct DrmHandlerImpl { + front: (DumbBuffer, framebuffer::Info), + back: (DumbBuffer, framebuffer::Info), + current: framebuffer::Handle, + surface: Rc>, +} + +impl DeviceHandler for DrmHandlerImpl { + type Device = LegacyDrmDevice; + + fn vblank(&mut self, _crtc: crtc::Handle) { + { + // Swap and map buffer + let mut mapping = if self.current == self.front.1.handle() { + self.current = self.back.1.handle(); + self.back.0.map(&*self.surface).unwrap() + } else { + self.current = self.front.1.handle(); + self.front.0.map(&*self.surface).unwrap() + }; + + // now we could render to the mapping via software rendering. + // this example just sets some grey color + + for mut x in mapping.as_mut() { + *x = 128; + } + } + self.surface.page_flip(self.current).unwrap(); + } + + fn error(&mut self, error: Error) { + panic!("{:?}", error); + } +} diff --git a/src/backend/drm/backend.rs b/src/backend/drm/backend.rs deleted file mode 100644 index 6abd4bb..0000000 --- a/src/backend/drm/backend.rs +++ /dev/null @@ -1,537 +0,0 @@ -use super::{error::*, DevPath}; -use backend::graphics::{ - egl::{ - error::Result as EGLResult, - native::{Gbm, GbmSurfaceArguments}, - wayland::{EGLDisplay, EGLWaylandExtensions}, - EGLContext, EGLGraphicsBackend, EGLSurface, PixelFormat, SwapBuffersError, - }, - GraphicsBackend, -}; -use drm::{ - control::{connector, crtc, encoder, framebuffer, Device, Mode, ResourceInfo}, - Device as BasicDevice, -}; -use gbm::{ - BufferObject, BufferObjectFlags, Device as GbmDevice, Format as GbmFormat, Surface as GbmSurface, - SurfaceBufferHandle, -}; -use image::{ImageBuffer, Rgba}; -use nix::libc::c_void; -use std::{ - cell::Cell, - os::unix::io::{AsRawFd, RawFd}, - rc::{Rc, Weak}, -}; -use wayland_server::Display; - -/// Backend based on a `DrmDevice` and a given crtc -pub struct DrmBackend { - backend: Rc>, - surface: EGLSurface>, - mode: Mode, - connectors: Vec, -} - -pub(crate) struct DrmBackendInternal { - pub(crate) context: Rc, GbmDevice>>, - pub(crate) cursor: Cell<(BufferObject<()>, (u32, u32))>, - current_frame_buffer: Cell, - front_buffer: Cell>, - next_buffer: Cell>>, - crtc: crtc::Handle, - logger: ::slog::Logger, -} - -impl DrmBackend { - pub(crate) fn new( - context: Rc, GbmDevice>>, - crtc: crtc::Handle, - mode: Mode, - connectors: Vec, - log: ::slog::Logger, - ) -> Result { - // logger already initialized by the DrmDevice - info!(log, "Initializing DrmBackend"); - - let (w, h) = mode.size(); - - debug!(log, "Creating Surface"); - let surface = context - .create_surface(GbmSurfaceArguments { - size: (w as u32, h as u32), - format: GbmFormat::XRGB8888, - flags: BufferObjectFlags::SCANOUT | BufferObjectFlags::RENDERING, - }).chain_err(|| ErrorKind::GbmInitFailed)?; - - // make it active for the first `crtc::set` - // (which is needed before the first page_flip) - unsafe { surface.make_current().chain_err(|| ErrorKind::FailedToSwap)? }; - surface.swap_buffers().chain_err(|| ErrorKind::FailedToSwap)?; - - // init the first screen - // (must be done before calling page_flip for the first time) - let mut front_bo = surface - .lock_front_buffer() - .chain_err(|| ErrorKind::FailedToSwap)?; - - debug!(log, "FrontBuffer color format: {:?}", front_bo.format()); - - // we need a framebuffer for the front buffer - let fb = framebuffer::create(&*context, &*front_bo).chain_err(|| { - ErrorKind::DrmDev(format!("Error creating framebuffer on {:?}", context.dev_path())) - })?; - - debug!(log, "Initialize screen"); - crtc::set(&*context, crtc, fb.handle(), &connectors, (0, 0), Some(mode)).chain_err(|| { - ErrorKind::DrmDev(format!( - "Error setting crtc {:?} on {:?}", - crtc, - context.dev_path() - )) - })?; - front_bo.set_userdata(fb).unwrap(); - - let cursor = Cell::new(( - context - .create_buffer_object( - 1, - 1, - GbmFormat::ARGB8888, - BufferObjectFlags::CURSOR | BufferObjectFlags::WRITE, - ).chain_err(|| ErrorKind::GbmInitFailed)?, - (0, 0), - )); - - Ok(DrmBackend { - backend: Rc::new(DrmBackendInternal { - context, - cursor, - current_frame_buffer: Cell::new(fb), - front_buffer: Cell::new(front_bo), - next_buffer: Cell::new(None), - crtc, - logger: log, - }), - surface, - mode, - connectors, - }) - } - - pub(crate) fn weak(&self) -> Weak> { - Rc::downgrade(&self.backend) - } - - /// Add a connector to backend - /// - /// # Errors - /// - /// Errors if the new connector does not support the currently set `Mode` - pub fn add_connector(&mut self, connector: connector::Handle) -> Result<()> { - let info = connector::Info::load_from_device(&*self.backend.context, connector).chain_err(|| { - ErrorKind::DrmDev(format!( - "Error loading connector info on {:?}", - self.backend.context.dev_path() - )) - })?; - - // check if the connector can handle the current mode - if info.modes().contains(&self.mode) { - // check if there is a valid encoder - let encoders = info - .encoders() - .iter() - .map(|encoder| { - encoder::Info::load_from_device(&*self.backend.context, *encoder).chain_err(|| { - ErrorKind::DrmDev(format!( - "Error loading encoder info on {:?}", - self.backend.context.dev_path() - )) - }) - }).collect::>>()?; - - // and if any encoder supports the selected crtc - let resource_handles = self.backend.context.resource_handles().chain_err(|| { - ErrorKind::DrmDev(format!( - "Error loading resources on {:?}", - self.backend.context.dev_path() - )) - })?; - if !encoders - .iter() - .map(|encoder| encoder.possible_crtcs()) - .all(|crtc_list| { - resource_handles - .filter_crtcs(crtc_list) - .contains(&self.backend.crtc) - }) { - bail!(ErrorKind::NoSuitableEncoder(info, self.backend.crtc)); - } - - info!( - self.backend.logger, - "Adding new connector: {:?}", - info.connector_type() - ); - self.connectors.push(connector); - Ok(()) - } else { - bail!(ErrorKind::ModeNotSuitable(self.mode)) - } - } - - /// Returns the currently set connectors - pub fn used_connectors(&self) -> &[connector::Handle] { - &*self.connectors - } - - /// Removes a currently set connector - pub fn remove_connector(&mut self, connector: connector::Handle) { - if let Ok(info) = connector::Info::load_from_device(&*self.backend.context, connector) { - info!( - self.backend.logger, - "Removing connector: {:?}", - info.connector_type() - ); - } else { - info!(self.backend.logger, "Removing unknown connector"); - } - - self.connectors.retain(|x| *x != connector); - } - - /// Gets the currently used mode - pub fn current_mode(&self) -> Mode { - self.mode - } - - /// Changes the currently set mode - /// - /// # Errors - /// - /// This will fail if not all set connectors support the new `Mode`. - /// Several internal resources will need to be recreated to fit the new `Mode`. - /// Other errors might occur. - pub fn use_mode(&mut self, mode: Mode) -> Result<()> { - // check the connectors - for connector in &self.connectors { - if !connector::Info::load_from_device(&*self.backend.context, *connector) - .chain_err(|| { - ErrorKind::DrmDev(format!( - "Error loading connector info on {:?}", - self.backend.context.dev_path() - )) - })?.modes() - .contains(&mode) - { - bail!(ErrorKind::ModeNotSuitable(mode)); - } - } - - info!(self.backend.logger, "Setting new mode: {:?}", mode.name()); - let (w, h) = mode.size(); - - // Recreate the surface and the related resources to match the new - // resolution. - debug!( - self.backend.logger, - "Reinitializing surface for new mode: {}:{}", w, h - ); - let surface = self - .backend - .context - .create_surface(GbmSurfaceArguments { - size: (w as u32, h as u32), - format: GbmFormat::XRGB8888, - flags: BufferObjectFlags::SCANOUT | BufferObjectFlags::RENDERING, - }).chain_err(|| ErrorKind::GbmInitFailed)?; - - // make it active for the first `crtc::set` - // (which is needed before the first page_flip) - unsafe { surface.make_current().chain_err(|| ErrorKind::FailedToSwap)? }; - surface.swap_buffers().chain_err(|| ErrorKind::FailedToSwap)?; - - // Clean up next_buffer - { - if let Some(mut old_bo) = self.backend.next_buffer.take() { - if let Ok(Some(fb)) = old_bo.take_userdata() { - if let Err(err) = framebuffer::destroy(&*self.backend.context, fb.handle()) { - warn!( - self.backend.logger, - "Error releasing old back_buffer framebuffer: {:?}", err - ); - } - } - } - } - - // Cleanup front_buffer and init the first screen on the new front_buffer - // (must be done before calling page_flip for the first time) - let mut old_front_bo = self.backend.front_buffer.replace({ - let mut front_bo = surface - .lock_front_buffer() - .chain_err(|| ErrorKind::FailedToSwap)?; - - debug!( - self.backend.logger, - "FrontBuffer color format: {:?}", - front_bo.format() - ); - - // we also need a new framebuffer for the front buffer - let dev_path = self.backend.context.dev_path(); - let fb = framebuffer::create(&*self.backend.context, &*front_bo) - .chain_err(|| ErrorKind::DrmDev(format!("Error creating framebuffer on {:?}", dev_path)))?; - - front_bo.set_userdata(fb).unwrap(); - - debug!(self.backend.logger, "Setting screen"); - crtc::set( - &*self.backend.context, - self.backend.crtc, - fb.handle(), - &self.connectors, - (0, 0), - Some(mode), - ).chain_err(|| { - ErrorKind::DrmDev(format!( - "Error setting crtc {:?} on {:?}", - self.backend.crtc, - self.backend.context.dev_path() - )) - })?; - - front_bo - }); - if let Ok(Some(fb)) = old_front_bo.take_userdata() { - if let Err(err) = framebuffer::destroy(&*self.backend.context, fb.handle()) { - warn!( - self.backend.logger, - "Error releasing old front_buffer framebuffer: {:?}", err - ); - } - } - - // Drop the old surface after cleanup - self.surface = surface; - self.mode = mode; - Ok(()) - } - - /// Returns the crtc id used by this backend - pub fn crtc(&self) -> crtc::Handle { - self.backend.crtc - } -} - -impl DrmBackendInternal { - pub(crate) fn unlock_buffer(&self) { - // after the page swap is finished we need to release the rendered buffer. - // this is called from the PageFlipHandler - if let Some(next_buffer) = self.next_buffer.replace(None) { - trace!(self.logger, "Releasing old front buffer"); - self.front_buffer.set(next_buffer); - // drop and release the old buffer - } - } - - pub(crate) fn page_flip( - &self, - fb: Option<&framebuffer::Info>, - ) -> ::std::result::Result<(), SwapBuffersError> { - trace!(self.logger, "Queueing Page flip"); - - let fb = *fb.unwrap_or(&self.current_frame_buffer.get()); - - // and flip - crtc::page_flip( - &*self.context, - self.crtc, - fb.handle(), - &[crtc::PageFlipFlags::PageFlipEvent], - ).map_err(|_| SwapBuffersError::ContextLost)?; - - self.current_frame_buffer.set(fb); - - Ok(()) - } -} - -impl Drop for DrmBackend { - fn drop(&mut self) { - // Drop framebuffers attached to the userdata of the gbm surface buffers. - // (They don't implement drop, as they need the device) - if let Ok(Some(fb)) = { - if let Some(mut next) = self.backend.next_buffer.take() { - next.take_userdata() - } else if let Ok(mut next) = self.surface.lock_front_buffer() { - next.take_userdata() - } else { - Ok(None) - } - } { - // ignore failure at this point - let _ = framebuffer::destroy(&*self.backend.context, fb.handle()); - } - } -} - -impl Drop for DrmBackendInternal { - fn drop(&mut self) { - if let Ok(Some(fb)) = self.front_buffer.get_mut().take_userdata() { - // ignore failure at this point - let _ = framebuffer::destroy(&*self.context, fb.handle()); - } - - // ignore failure at this point - let _ = crtc::clear_cursor(&*self.context, self.crtc); - } -} - -impl GraphicsBackend for DrmBackend { - type CursorFormat = ImageBuffer, Vec>; - type Error = Error; - - fn set_cursor_position(&self, x: u32, y: u32) -> Result<()> { - trace!(self.backend.logger, "Move the cursor to {},{}", x, y); - crtc::move_cursor(&*self.backend.context, self.backend.crtc, (x as i32, y as i32)).chain_err(|| { - ErrorKind::DrmDev(format!( - "Error moving cursor on {:?}", - self.backend.context.dev_path() - )) - }) - } - - fn set_cursor_representation( - &self, - buffer: &ImageBuffer, Vec>, - hotspot: (u32, u32), - ) -> Result<()> { - let (w, h) = buffer.dimensions(); - debug!(self.backend.logger, "Importing cursor"); - - // import the cursor into a buffer we can render - let mut cursor = self - .backend - .context - .create_buffer_object( - w, - h, - GbmFormat::ARGB8888, - BufferObjectFlags::CURSOR | BufferObjectFlags::WRITE, - ).chain_err(|| ErrorKind::GbmInitFailed)?; - cursor - .write(&**buffer) - .chain_err(|| ErrorKind::GbmInitFailed)? - .chain_err(|| ErrorKind::GbmInitFailed)?; - - trace!(self.backend.logger, "Setting the new imported cursor"); - - // and set it - if crtc::set_cursor2( - &*self.backend.context, - self.backend.crtc, - &cursor, - (hotspot.0 as i32, hotspot.1 as i32), - ).is_err() - { - crtc::set_cursor(&*self.backend.context, self.backend.crtc, &cursor).chain_err(|| { - ErrorKind::DrmDev(format!( - "Failed to set cursor on {:?}", - self.backend.context.dev_path() - )) - })?; - } - - // and store it - self.backend.cursor.set((cursor, hotspot)); - Ok(()) - } -} - -impl EGLGraphicsBackend for DrmBackend { - fn swap_buffers(&self) -> ::std::result::Result<(), SwapBuffersError> { - let res = { - let nb = self.backend.next_buffer.take(); - let res = nb.is_some(); - self.backend.next_buffer.set(nb); - res - }; - if res { - // We cannot call lock_front_buffer anymore without releasing the previous buffer, which will happen when the page flip is done - warn!( - self.backend.logger, - "Tried to swap a DrmBackend with a queued flip" - ); - return Err(SwapBuffersError::AlreadySwapped); - } - - // flip normally - self.surface.swap_buffers()?; - - // supporting only one buffer would cause a lot of inconvinience and - // would most likely result in a lot of flickering. - // neither weston, wlc or wlroots bother with that as well. - // so we just assume we got at least two buffers to do flipping. - let mut next_bo = self - .surface - .lock_front_buffer() - .expect("Surface only has one front buffer. Not supported by smithay"); - - // create a framebuffer if the front buffer does not have one already - // (they are reused by gbm) - let maybe_fb = next_bo - .userdata() - .map_err(|_| SwapBuffersError::ContextLost)? - .cloned(); - let fb = if let Some(info) = maybe_fb { - info - } else { - let fb = framebuffer::create(&*self.backend.context, &*next_bo) - .map_err(|_| SwapBuffersError::ContextLost)?; - next_bo.set_userdata(fb).unwrap(); - fb - }; - self.backend.next_buffer.set(Some(next_bo)); - - self.backend.page_flip(Some(&fb)) - } - - unsafe fn get_proc_address(&self, symbol: &str) -> *const c_void { - self.backend.context.get_proc_address(symbol) - } - - fn get_framebuffer_dimensions(&self) -> (u32, u32) { - let (w, h) = self.mode.size(); - (w as u32, h as u32) - } - - fn is_current(&self) -> bool { - self.backend.context.is_current() && self.surface.is_current() - } - - unsafe fn make_current(&self) -> ::std::result::Result<(), SwapBuffersError> { - self.surface.make_current() - } - - fn get_pixel_format(&self) -> PixelFormat { - self.backend.context.get_pixel_format() - } -} - -// for users convenience -impl AsRawFd for DrmBackend { - fn as_raw_fd(&self) -> RawFd { - self.backend.context.as_raw_fd() - } -} - -impl BasicDevice for DrmBackend {} -impl Device for DrmBackend {} - -impl EGLWaylandExtensions for DrmBackend { - fn bind_wl_display(&self, display: &Display) -> EGLResult { - self.backend.context.bind_wl_display(display) - } -} diff --git a/src/backend/drm/egl/error.rs b/src/backend/drm/egl/error.rs new file mode 100644 index 0000000..07f0e94 --- /dev/null +++ b/src/backend/drm/egl/error.rs @@ -0,0 +1,19 @@ +//! +//! Errors thrown by the `DrmDevice` and `DrmBackend` +//! + +use backend::egl::error as egl; + +error_chain! { + errors { + #[doc = "Underlying backend failed"] + UnderlyingBackendError { + description("The underlying backend reported an error"), + display("The underlying backend reported an error"), + } + } + + links { + EGL(egl::Error, egl::ErrorKind) #[doc = "EGL error"]; + } +} diff --git a/src/backend/drm/egl/mod.rs b/src/backend/drm/egl/mod.rs new file mode 100644 index 0000000..b918427 --- /dev/null +++ b/src/backend/drm/egl/mod.rs @@ -0,0 +1,204 @@ +//! +//! [`Device`](../trait.Device.html) and [`Surface`](../trait.Surface.html) +//! implementations using egl contexts and surfaces for efficient rendering. +//! +//! Usually this implementation's [`EglSurface`](struct.EglSurface.html)s implementation +//! of [`GlGraphicsBackend`](../../graphics/gl/trait.GlGraphicsBackend.html) will be used +//! to let your compositor render. +//! Take a look at `anvil`s source code for an example of this. +//! + +use drm::control::{crtc, ResourceHandles, ResourceInfo}; +use nix::libc::dev_t; +use std::os::unix::io::{AsRawFd, RawFd}; +use std::rc::Rc; +use wayland_server::Display; + +use super::{Device, DeviceHandler, Surface}; +use backend::egl::context::GlAttributes; +use backend::egl::error::Result as EGLResult; +use backend::egl::native::{Backend, NativeDisplay, NativeSurface}; +use backend::egl::EGLContext; +#[cfg(feature = "native_lib")] +use backend::egl::{EGLDisplay, EGLGraphicsBackend}; + +pub mod error; +use self::error::*; + +mod surface; +pub use self::surface::*; + +#[cfg(feature = "backend_session")] +pub mod session; + +/// Representation of an egl device to create egl rendering surfaces +pub struct EglDevice +where + B: Backend::Surface> + 'static, + D: Device + NativeDisplay + 'static, + ::Surface: NativeSurface, +{ + dev: Rc>, + logger: ::slog::Logger, +} + +impl AsRawFd for EglDevice +where + B: Backend::Surface> + 'static, + D: Device + NativeDisplay + 'static, + ::Surface: NativeSurface, +{ + fn as_raw_fd(&self) -> RawFd { + self.dev.borrow().as_raw_fd() + } +} + +impl EglDevice +where + B: Backend::Surface> + 'static, + D: Device + NativeDisplay + 'static, + ::Surface: NativeSurface, +{ + /// Try to create a new `EglDevice` from an open device. + /// + /// Returns an error if the file is no valid device or context + /// creation was not successful. + pub fn new(dev: D, logger: L) -> Result + where + L: Into>, + { + EglDevice::new_with_gl_attr( + dev, + GlAttributes { + version: None, + profile: None, + debug: cfg!(debug_assertions), + vsync: true, + }, + logger, + ) + } + + /// Create a new `EglDevice` from an open device and given `GlAttributes` + /// + /// Returns an error if the file is no valid device or context + /// creation was not successful. + pub fn new_with_gl_attr(mut dev: D, attributes: GlAttributes, logger: L) -> Result + where + L: Into>, + { + let log = ::slog_or_stdlog(logger).new(o!("smithay_module" => "backend_egl")); + + dev.clear_handler(); + + debug!(log, "Creating egl context from device"); + Ok(EglDevice { + // Open the gbm device from the drm device and create a context based on that + dev: Rc::new( + EGLContext::new(dev, attributes, Default::default(), log.clone()).map_err(Error::from)?, + ), + logger: log, + }) + } +} + +struct InternalDeviceHandler +where + B: Backend::Surface> + 'static, + D: Device + NativeDisplay + 'static, + ::Surface: NativeSurface, +{ + handler: Box> + 'static>, +} + +impl DeviceHandler for InternalDeviceHandler +where + B: Backend::Surface> + 'static, + D: Device + NativeDisplay + 'static, + ::Surface: NativeSurface, +{ + type Device = D; + + fn vblank(&mut self, crtc: crtc::Handle) { + self.handler.vblank(crtc) + } + fn error(&mut self, error: <::Surface as Surface>::Error) { + self.handler + .error(ResultExt::<()>::chain_err(Err(error), || ErrorKind::UnderlyingBackendError).unwrap_err()) + } +} + +impl Device for EglDevice +where + B: Backend::Surface> + 'static, + D: Device + NativeDisplay + 'static, + ::Surface: NativeSurface, +{ + type Surface = EglSurface; + + fn device_id(&self) -> dev_t { + self.dev.borrow().device_id() + } + + fn set_handler(&mut self, handler: impl DeviceHandler + 'static) { + self.dev.borrow_mut().set_handler(InternalDeviceHandler { + handler: Box::new(handler), + }); + } + + fn clear_handler(&mut self) { + self.dev.borrow_mut().clear_handler() + } + + fn create_surface(&mut self, crtc: crtc::Handle) -> Result> { + info!(self.logger, "Initializing EglSurface"); + + let surface = self.dev.create_surface(crtc)?; + + Ok(EglSurface { + dev: self.dev.clone(), + surface, + }) + } + + fn process_events(&mut self) { + self.dev.borrow_mut().process_events() + } + + fn resource_info(&self, handle: T::Handle) -> Result { + self.dev + .borrow() + .resource_info(handle) + .chain_err(|| ErrorKind::UnderlyingBackendError) + } + + fn resource_handles(&self) -> Result { + self.dev + .borrow() + .resource_handles() + .chain_err(|| ErrorKind::UnderlyingBackendError) + } +} + +#[cfg(feature = "native_lib")] +impl EGLGraphicsBackend for EglDevice +where + B: Backend::Surface> + 'static, + D: Device + NativeDisplay + 'static, + ::Surface: NativeSurface, +{ + fn bind_wl_display(&self, display: &Display) -> EGLResult { + self.dev.bind_wl_display(display) + } +} + +impl Drop for EglDevice +where + B: Backend::Surface> + 'static, + D: Device + NativeDisplay + 'static, + ::Surface: NativeSurface, +{ + fn drop(&mut self) { + self.clear_handler(); + } +} diff --git a/src/backend/drm/egl/session.rs b/src/backend/drm/egl/session.rs new file mode 100644 index 0000000..0d6fc3f --- /dev/null +++ b/src/backend/drm/egl/session.rs @@ -0,0 +1,43 @@ +//! +//! Support to register an [`EglDevice`](../struct.EglDevice.html) +//! to an open [`Session`](../../session/trait.Session.html). +//! + +use drm::control::crtc; +use std::os::unix::io::RawFd; + +use super::EglDevice; +use backend::drm::Device; +use backend::egl::native::{Backend, NativeDisplay, NativeSurface}; +use backend::session::{AsSessionObserver, SessionObserver}; + +/// [`SessionObserver`](../../session/trait.SessionObserver.html) +/// linked to the [`EglDevice`](../struct.EglDevice.html) it was +/// created from. +pub struct EglDeviceObserver { + observer: S, +} + +impl AsSessionObserver> for EglDevice +where + S: SessionObserver + 'static, + B: Backend::Surface> + 'static, + D: Device + NativeDisplay + AsSessionObserver + 'static, + ::Surface: NativeSurface, +{ + fn observer(&mut self) -> EglDeviceObserver { + EglDeviceObserver { + observer: self.dev.borrow_mut().observer(), + } + } +} + +impl SessionObserver for EglDeviceObserver { + fn pause(&mut self, devnum: Option<(u32, u32)>) { + self.observer.pause(devnum); + } + + fn activate(&mut self, devnum: Option<(u32, u32, Option)>) { + self.observer.activate(devnum); + } +} diff --git a/src/backend/drm/egl/surface.rs b/src/backend/drm/egl/surface.rs new file mode 100644 index 0000000..064825e --- /dev/null +++ b/src/backend/drm/egl/surface.rs @@ -0,0 +1,130 @@ +use drm::control::{connector, crtc, Mode}; +use nix::libc::c_void; +use std::rc::Rc; + +use super::error::*; +use backend::drm::{Device, Surface}; +use backend::egl::native::{Backend, NativeDisplay, NativeSurface}; +use backend::egl::{EGLContext, EGLSurface}; +#[cfg(feature = "renderer_gl")] +use backend::graphics::gl::GLGraphicsBackend; +#[cfg(feature = "renderer_gl")] +use backend::graphics::PixelFormat; +use backend::graphics::{CursorBackend, SwapBuffersError}; + +/// Egl surface for rendering +pub struct EglSurface +where + B: Backend::Surface> + 'static, + D: Device + NativeDisplay + 'static, + ::Surface: NativeSurface, +{ + pub(super) dev: Rc>, + pub(super) surface: EGLSurface, +} + +impl Surface for EglSurface +where + B: Backend::Surface> + 'static, + D: Device + NativeDisplay + 'static, + ::Surface: NativeSurface, +{ + type Error = Error; + type Connectors = <::Surface as Surface>::Connectors; + + fn crtc(&self) -> crtc::Handle { + (*self.surface).crtc() + } + + fn current_connectors(&self) -> Self::Connectors { + self.surface.current_connectors() + } + + fn pending_connectors(&self) -> Self::Connectors { + self.surface.pending_connectors() + } + + fn add_connector(&self, connector: connector::Handle) -> Result<()> { + self.surface + .add_connector(connector) + .chain_err(|| ErrorKind::UnderlyingBackendError) + } + + fn remove_connector(&self, connector: connector::Handle) -> Result<()> { + self.surface + .remove_connector(connector) + .chain_err(|| ErrorKind::UnderlyingBackendError) + } + + fn current_mode(&self) -> Option { + self.surface.current_mode() + } + + fn pending_mode(&self) -> Option { + self.surface.pending_mode() + } + + fn use_mode(&self, mode: Option) -> Result<()> { + self.surface + .use_mode(mode) + .chain_err(|| ErrorKind::UnderlyingBackendError) + } +} + +impl<'a, B, D> CursorBackend<'a> for EglSurface +where + B: Backend::Surface> + 'static, + D: Device + NativeDisplay + 'static, + ::Surface: NativeSurface + CursorBackend<'a>, +{ + type CursorFormat = >::CursorFormat; + type Error = >::Error; + + fn set_cursor_position(&self, x: u32, y: u32) -> ::std::result::Result<(), Self::Error> { + self.surface.set_cursor_position(x, y) + } + + fn set_cursor_representation<'b>( + &'b self, + buffer: Self::CursorFormat, + hotspot: (u32, u32), + ) -> ::std::result::Result<(), Self::Error> + where + 'a: 'b, + { + self.surface.set_cursor_representation(buffer, hotspot) + } +} + +#[cfg(feature = "renderer_gl")] +impl GLGraphicsBackend for EglSurface +where + B: Backend::Surface> + 'static, + D: Device + NativeDisplay + 'static, + ::Surface: NativeSurface, +{ + fn swap_buffers(&self) -> ::std::result::Result<(), SwapBuffersError> { + self.surface.swap_buffers() + } + + unsafe fn get_proc_address(&self, symbol: &str) -> *const c_void { + self.dev.get_proc_address(symbol) + } + + fn get_framebuffer_dimensions(&self) -> (u32, u32) { + let (w, h) = self.pending_mode().map(|mode| mode.size()).unwrap_or((1, 1)); + (w as u32, h as u32) + } + + fn is_current(&self) -> bool { + self.dev.is_current() && self.surface.is_current() + } + + unsafe fn make_current(&self) -> ::std::result::Result<(), SwapBuffersError> { + self.surface.make_current() + } + + fn get_pixel_format(&self) -> PixelFormat { + self.dev.get_pixel_format() + } +} diff --git a/src/backend/drm/gbm/egl.rs b/src/backend/drm/gbm/egl.rs new file mode 100644 index 0000000..5977870 --- /dev/null +++ b/src/backend/drm/gbm/egl.rs @@ -0,0 +1,95 @@ +//! +//! Egl [`NativeDisplay`](../../egl/native/trait.NativeDisplay.html) and +//! [`NativeSurface`](../../egl/native/trait.NativeSurface.html) support for +//! [`GbmDevice`](../struct.GbmDevice.html) and [`GbmSurface`](../struct.GbmSurface.html). +//! + +use backend::drm::{Device, RawDevice}; +use backend::egl::error::Result as EglResult; +use backend::egl::ffi; +use backend::egl::native::{Backend, NativeDisplay, NativeSurface}; +use backend::graphics::SwapBuffersError; + +use super::error::{Error, Result}; +use super::{GbmDevice, GbmSurface}; + +use drm::control::{crtc, Device as ControlDevice}; +use gbm::AsRaw; +use std::marker::PhantomData; +use std::ptr; + +/// Egl Gbm backend type +/// +/// See [`Backend`](../../egl/native/trait.Backend.html). +pub struct Gbm { + _userdata: PhantomData, +} + +impl Backend for Gbm { + type Surface = GbmSurface; + + unsafe fn get_display( + display: ffi::NativeDisplayType, + has_dp_extension: F, + log: ::slog::Logger, + ) -> ffi::egl::types::EGLDisplay + where + F: Fn(&str) -> bool, + { + if has_dp_extension("EGL_KHR_platform_gbm") && ffi::egl::GetPlatformDisplay::is_loaded() { + trace!(log, "EGL Display Initialization via EGL_KHR_platform_gbm"); + ffi::egl::GetPlatformDisplay(ffi::egl::PLATFORM_GBM_KHR, display as *mut _, ptr::null()) + } else if has_dp_extension("EGL_MESA_platform_gbm") && ffi::egl::GetPlatformDisplayEXT::is_loaded() { + trace!(log, "EGL Display Initialization via EGL_MESA_platform_gbm"); + ffi::egl::GetPlatformDisplayEXT(ffi::egl::PLATFORM_GBM_MESA, display as *mut _, ptr::null()) + } else if has_dp_extension("EGL_MESA_platform_gbm") && ffi::egl::GetPlatformDisplay::is_loaded() { + trace!(log, "EGL Display Initialization via EGL_MESA_platform_gbm"); + ffi::egl::GetPlatformDisplay(ffi::egl::PLATFORM_GBM_MESA, display as *mut _, ptr::null()) + } else { + trace!(log, "Default EGL Display Initialization via GetDisplay"); + ffi::egl::GetDisplay(display as *mut _) + } + } +} + +unsafe impl NativeDisplay> for GbmDevice { + type Arguments = crtc::Handle; + type Error = Error; + + fn is_backend(&self) -> bool { + true + } + + fn ptr(&self) -> EglResult { + Ok(self.dev.borrow().as_raw() as *const _) + } + + fn create_surface(&mut self, crtc: crtc::Handle) -> Result> { + Device::create_surface(self, crtc) + } +} + +unsafe impl NativeSurface for GbmSurface { + fn ptr(&self) -> ffi::NativeWindowType { + self.0.surface.borrow().as_raw() as *const _ + } + + fn needs_recreation(&self) -> bool { + self.needs_recreation() + } + + fn recreate(&self) -> bool { + if let Err(err) = GbmSurface::recreate(self) { + error!(self.0.logger, "Failure recreating internal resources: {:?}", err); + false + } else { + true + } + } + + fn swap_buffers(&self) -> ::std::result::Result<(), SwapBuffersError> { + // 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() } + } +} diff --git a/src/backend/drm/gbm/error.rs b/src/backend/drm/gbm/error.rs new file mode 100644 index 0000000..452e657 --- /dev/null +++ b/src/backend/drm/gbm/error.rs @@ -0,0 +1,53 @@ +//! +//! Errors thrown by the `DrmDevice` and `DrmBackend` +//! + +error_chain! { + errors { + #[doc = "Creation of gbm device failed"] + InitFailed { + description("Creation of gbm device failed"), + display("Creation of gbm device failed"), + } + + #[doc = "Creation of gbm surface failed"] + SurfaceCreationFailed { + description("Creation of gbm surface failed"), + display("Creation of gbm surface failed"), + } + + #[doc = "No mode is set, blocking the current operation"] + NoModeSet { + description("No mode is currently set"), + display("No mode is currently set"), + } + + #[doc = "Creation of gbm buffer object failed"] + BufferCreationFailed { + description("Creation of gbm buffer object failed"), + display("Creation of gbm buffer object failed"), + } + + #[doc = "Writing to gbm buffer failed"] + BufferWriteFailed { + description("Writing to gbm buffer failed"), + display("Writing to gbm buffer failed"), + } + + #[doc = "Lock of gbm surface front buffer failed"] + FrontBufferLockFailed { + description("Lock of gbm surface front buffer failed"), + display("Lock of gbm surface front buffer failed"), + } + + #[doc = "Underlying backend failed"] + UnderlyingBackendError { + description("The underlying backend reported an error"), + display("The underlying backend reported an error"), + } + } + + foreign_links { + FailedToSwap(::backend::graphics::SwapBuffersError) #[doc = "Swapping front buffers failed"]; + } +} diff --git a/src/backend/drm/gbm/mod.rs b/src/backend/drm/gbm/mod.rs new file mode 100644 index 0000000..7298d7c --- /dev/null +++ b/src/backend/drm/gbm/mod.rs @@ -0,0 +1,213 @@ +//! +//! [`Device`](../trait.Device.html) and [`Surface`](../trait.Surface.html) +//! implementations using gbm buffers for efficient rendering. +//! +//! Usually this implementation will be wrapped into a [`EglDevice`](../egl/struct.EglDevice.html). +//! Take a look at `anvil`s source code for an example of this. +//! +//! To use these types standalone, you will need to consider the special requirements +//! of [`GbmSurface::page_flip`](struct.GbmSurface.html#method.page_flip). +//! + +use super::{Device, DeviceHandler, RawDevice, ResourceHandles, ResourceInfo, Surface}; + +use drm::control::{crtc, Device as ControlDevice}; +use gbm::{self, BufferObjectFlags, Format as GbmFormat}; +use nix::libc::dev_t; + +use std::cell::{Cell, RefCell}; +use std::collections::HashMap; +use std::os::unix::io::{AsRawFd, RawFd}; +use std::rc::{Rc, Weak}; +use std::sync::{Once, ONCE_INIT}; + +pub mod error; +use self::error::*; + +mod surface; +pub use self::surface::GbmSurface; +use self::surface::GbmSurfaceInternal; + +#[cfg(feature = "backend_egl")] +pub mod egl; + +#[cfg(feature = "backend_session")] +pub mod session; + +static LOAD: Once = ONCE_INIT; + +/// Representation of an open gbm device to create rendering surfaces +pub struct GbmDevice { + pub(self) dev: Rc>>, + backends: Rc>>>>, + logger: ::slog::Logger, +} + +impl GbmDevice { + /// Create a new `GbmDevice` from an open drm node + /// + /// Returns an error if the file is no valid drm node or context creation was not + /// successful. + pub fn new(mut dev: D, logger: L) -> Result + where + L: Into>, + { + /* GBM will load a dri driver, but even though they need symbols from + * libglapi, in some version of Mesa they are not linked to it. Since + * only the gl-renderer module links to it, these symbols won't be + * globally available, and loading the DRI driver fails. + * Workaround this by dlopen()'ing libglapi with RTLD_GLOBAL. + */ + LOAD.call_once(|| unsafe { + nix::libc::dlopen( + "libglapi.so.0".as_ptr() as *const _, + nix::libc::RTLD_LAZY | nix::libc::RTLD_GLOBAL, + ); + }); + + let log = ::slog_or_stdlog(logger).new(o!("smithay_module" => "backend_gbm")); + + dev.clear_handler(); + + debug!(log, "Creating gbm device"); + Ok(GbmDevice { + // Open the gbm device from the drm device + dev: Rc::new(RefCell::new( + gbm::Device::new(dev).chain_err(|| ErrorKind::InitFailed)?, + )), + 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 let Some(surface) = surface.upgrade() { + surface.unlock_buffer(); + self.handler.vblank(crtc); + } + } else { + warn!( + self.logger, + "Surface ({:?}) not managed by gbm, event not handled.", crtc + ); + } + } + } + fn error(&mut self, error: <::Surface as Surface>::Error) { + self.handler + .error(ResultExt::<()>::chain_err(Err(error), || ErrorKind::UnderlyingBackendError).unwrap_err()) + } +} + +impl Device for GbmDevice { + type Surface = GbmSurface; + + fn device_id(&self) -> dev_t { + self.dev.borrow().device_id() + } + + fn set_handler(&mut self, handler: impl DeviceHandler + 'static) { + self.dev.borrow_mut().set_handler(InternalDeviceHandler { + handler: Box::new(handler), + backends: Rc::downgrade(&self.backends), + logger: self.logger.clone(), + }); + } + + fn clear_handler(&mut self) { + self.dev.borrow_mut().clear_handler(); + } + + fn create_surface(&mut self, crtc: crtc::Handle) -> Result> { + info!(self.logger, "Initializing GbmSurface"); + + let drm_surface = Device::create_surface(&mut **self.dev.borrow_mut(), crtc) + .chain_err(|| ErrorKind::UnderlyingBackendError)?; + + // initialize the surface + let (w, h) = drm_surface + .pending_mode() + .map(|mode| mode.size()) + .unwrap_or((1, 1)); + let surface = self + .dev + .borrow() + .create_surface( + w as u32, + h as u32, + GbmFormat::XRGB8888, + BufferObjectFlags::SCANOUT | BufferObjectFlags::RENDERING, + ) + .chain_err(|| ErrorKind::SurfaceCreationFailed)?; + + // initialize a buffer for the cursor image + let cursor = Cell::new(( + self.dev + .borrow() + .create_buffer_object( + 1, + 1, + GbmFormat::ARGB8888, + BufferObjectFlags::CURSOR | BufferObjectFlags::WRITE, + ) + .chain_err(|| ErrorKind::BufferCreationFailed)?, + (0, 0), + )); + + let backend = Rc::new(GbmSurfaceInternal { + dev: self.dev.clone(), + surface: RefCell::new(surface), + crtc: drm_surface, + cursor, + current_frame_buffer: Cell::new(None), + front_buffer: Cell::new(None), + next_buffer: Cell::new(None), + recreated: Cell::new(true), + logger: self.logger.new(o!("crtc" => format!("{:?}", crtc))), + }); + self.backends.borrow_mut().insert(crtc, Rc::downgrade(&backend)); + Ok(GbmSurface(backend)) + } + + fn process_events(&mut self) { + self.dev.borrow_mut().process_events() + } + + fn resource_info(&self, handle: T::Handle) -> Result { + self.dev + .borrow() + .resource_info(handle) + .chain_err(|| ErrorKind::UnderlyingBackendError) + } + + fn resource_handles(&self) -> Result { + self.dev + .borrow() + .resource_handles() + .chain_err(|| ErrorKind::UnderlyingBackendError) + } +} + +impl AsRawFd for GbmDevice { + fn as_raw_fd(&self) -> RawFd { + self.dev.borrow().as_raw_fd() + } +} + +impl Drop for GbmDevice { + fn drop(&mut self) { + self.clear_handler(); + } +} diff --git a/src/backend/drm/gbm/session.rs b/src/backend/drm/gbm/session.rs new file mode 100644 index 0000000..0777cf4 --- /dev/null +++ b/src/backend/drm/gbm/session.rs @@ -0,0 +1,88 @@ +//! +//! Support to register a [`GbmDevice`](../struct.GbmDevice.html) +//! to an open [`Session`](../../session/trait.Session.html). +//! + +use drm::control::{crtc, Device as ControlDevice, ResourceInfo}; +use gbm::BufferObject; +use std::cell::RefCell; +use std::collections::HashMap; +use std::os::unix::io::RawFd; +use std::rc::{Rc, Weak}; + +use super::{GbmDevice, GbmSurfaceInternal}; +use backend::drm::{RawDevice, RawSurface}; +use backend::session::{AsSessionObserver, SessionObserver}; + +/// [`SessionObserver`](../../session/trait.SessionObserver.html) +/// linked to the [`GbmDevice`](../struct.GbmDevice.html) it was +/// created from. +pub struct GbmDeviceObserver< + S: SessionObserver + 'static, + D: RawDevice + ControlDevice + AsSessionObserver + 'static, +> { + observer: S, + backends: Weak>>>>, + logger: ::slog::Logger, +} + +impl + 'static> + AsSessionObserver> for GbmDevice +{ + fn observer(&mut self) -> GbmDeviceObserver { + GbmDeviceObserver { + observer: (**self.dev.borrow_mut()).observer(), + backends: Rc::downgrade(&self.backends), + logger: self.logger.clone(), + } + } +} + +impl + 'static> + SessionObserver for GbmDeviceObserver +{ + fn pause(&mut self, devnum: Option<(u32, u32)>) { + self.observer.pause(devnum); + } + + fn activate(&mut self, devnum: Option<(u32, u32, Option)>) { + self.observer.activate(devnum); + let mut crtcs = Vec::new(); + if let Some(backends) = self.backends.upgrade() { + for (crtc, backend) in backends.borrow().iter() { + if let Some(backend) = backend.upgrade() { + // restart rendering loop, if it was previously running + if let Some(Err(err)) = backend + .current_frame_buffer + .get() + .map(|fb| backend.crtc.page_flip(fb.handle())) + { + warn!(self.logger, "Failed to restart rendering loop. Error: {}", err); + } + // reset cursor + { + let &(ref cursor, ref hotspot): &(BufferObject<()>, (u32, u32)) = + unsafe { &*backend.cursor.as_ptr() }; + if crtc::set_cursor2( + &*backend.dev.borrow(), + *crtc, + cursor, + ((*hotspot).0 as i32, (*hotspot).1 as i32), + ) + .is_err() + { + if let Err(err) = crtc::set_cursor(&*backend.dev.borrow(), *crtc, cursor) { + error!(self.logger, "Failed to reset cursor. Error: {}", err); + } + } + } + } else { + crtcs.push(*crtc); + } + } + for crtc in crtcs { + backends.borrow_mut().remove(&crtc); + } + } + } +} diff --git a/src/backend/drm/gbm/surface.rs b/src/backend/drm/gbm/surface.rs new file mode 100644 index 0000000..62a7ea2 --- /dev/null +++ b/src/backend/drm/gbm/surface.rs @@ -0,0 +1,373 @@ +use super::super::{Device, RawDevice, RawSurface, Surface}; +use super::error::*; + +use drm::control::{connector, crtc, framebuffer, Mode, ResourceInfo}; +use gbm::{self, BufferObject, BufferObjectFlags, Format as GbmFormat, SurfaceBufferHandle}; +use image::{ImageBuffer, Rgba}; + +use std::cell::{Cell, RefCell}; +use std::os::unix::io::AsRawFd; +use std::rc::Rc; + +#[cfg(feature = "backend_drm_legacy")] +use backend::drm::legacy::LegacyDrmDevice; +use backend::graphics::CursorBackend; +use backend::graphics::SwapBuffersError; + +pub(super) struct GbmSurfaceInternal { + pub(super) dev: Rc>>, + pub(super) surface: RefCell>, + pub(super) crtc: ::Surface, + pub(super) cursor: Cell<(BufferObject<()>, (u32, u32))>, + pub(super) current_frame_buffer: Cell>, + pub(super) front_buffer: Cell>>, + pub(super) next_buffer: Cell>>, + pub(super) recreated: Cell, + pub(super) logger: ::slog::Logger, +} + +impl GbmSurfaceInternal { + pub(super) fn unlock_buffer(&self) { + // after the page swap is finished we need to release the rendered buffer. + // this is called from the PageFlipHandler + trace!(self.logger, "Releasing old front buffer"); + self.front_buffer.set(self.next_buffer.replace(None)); + // drop and release the old buffer + } + + pub unsafe fn page_flip(&self) -> ::std::result::Result<(), SwapBuffersError> { + let res = { + let nb = self.next_buffer.take(); + let res = nb.is_some(); + self.next_buffer.set(nb); + res + }; + if res { + // We cannot call lock_front_buffer anymore without releasing the previous buffer, which will happen when the page flip is done + warn!(self.logger, "Tried to swap with an already queued flip"); + return Err(SwapBuffersError::AlreadySwapped); + } + + // supporting only one buffer would cause a lot of inconvinience and + // would most likely result in a lot of flickering. + // neither weston, wlc or wlroots bother with that as well. + // so we just assume we got at least two buffers to do flipping. + let mut next_bo = self + .surface + .borrow() + .lock_front_buffer() + .expect("Surface only has one front buffer. Not supported by smithay"); + + // create a framebuffer if the front buffer does not have one already + // (they are reused by gbm) + let maybe_fb = next_bo + .userdata() + .map_err(|_| SwapBuffersError::ContextLost)? + .cloned(); + let fb = if let Some(info) = maybe_fb { + info + } else { + let fb = framebuffer::create(&self.crtc, &*next_bo).map_err(|_| SwapBuffersError::ContextLost)?; + next_bo.set_userdata(fb).unwrap(); + fb + }; + self.next_buffer.set(Some(next_bo)); + + if self.recreated.get() { + debug!(self.logger, "Commiting new state"); + self.crtc + .commit(fb.handle()) + .map_err(|_| SwapBuffersError::ContextLost)?; + self.recreated.set(false); + } + + trace!(self.logger, "Queueing Page flip"); + self.crtc.page_flip(fb.handle())?; + + self.current_frame_buffer.set(Some(fb)); + + Ok(()) + } + + pub fn recreate(&self) -> Result<()> { + let (w, h) = self.pending_mode().chain_err(|| ErrorKind::NoModeSet)?.size(); + + // Recreate the surface and the related resources to match the new + // resolution. + debug!(self.logger, "(Re-)Initializing surface for mode: {}:{}", w, h); + let surface = self + .dev + .borrow_mut() + .create_surface( + w as u32, + h as u32, + GbmFormat::XRGB8888, + BufferObjectFlags::SCANOUT | BufferObjectFlags::RENDERING, + ) + .chain_err(|| ErrorKind::SurfaceCreationFailed)?; + + // Clean up buffers + if let Some(Ok(Some(fb))) = self.next_buffer.take().map(|mut bo| bo.take_userdata()) { + if let Err(err) = framebuffer::destroy(&self.crtc, fb.handle()) { + warn!( + self.logger, + "Error releasing old back_buffer framebuffer: {:?}", err + ); + } + } + + if let Some(Ok(Some(fb))) = self.front_buffer.take().map(|mut bo| bo.take_userdata()) { + if let Err(err) = framebuffer::destroy(&self.crtc, fb.handle()) { + warn!( + self.logger, + "Error releasing old front_buffer framebuffer: {:?}", err + ); + } + } + + // Drop the old surface after cleanup + *self.surface.borrow_mut() = surface; + + self.recreated.set(true); + + Ok(()) + } +} + +impl Surface for GbmSurfaceInternal { + type Connectors = <::Surface as Surface>::Connectors; + type Error = 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.crtc + .add_connector(connector) + .chain_err(|| ErrorKind::UnderlyingBackendError) + } + + fn remove_connector(&self, connector: connector::Handle) -> Result<()> { + self.crtc + .remove_connector(connector) + .chain_err(|| ErrorKind::UnderlyingBackendError) + } + + fn current_mode(&self) -> Option { + self.crtc.current_mode() + } + + fn pending_mode(&self) -> Option { + self.crtc.pending_mode() + } + + fn use_mode(&self, mode: Option) -> Result<()> { + self.crtc + .use_mode(mode) + .chain_err(|| ErrorKind::UnderlyingBackendError) + } +} + +// FIXME: +// +// Option 1: When there is GAT support, impl `GraphicsBackend` for `LegacyDrmBackend` +// using a new generic `B: Buffer` and use this: +/* +impl<'a, D: RawDevice + 'static> CursorBackend<'a> for GbmSurfaceInternal +where + ::Surface: CursorBackend<'a>, + <::Surface as CursorBackend<'a>>::CursorFormat: Buffer, + <::Surface as CursorBackend<'a>>::Error: ::std::error::Error + Send +{ +*/ +// +// Option 2: When equality checks in where clauses are supported, we could at least do this: +/* +impl<'a, D: RawDevice + 'static> GraphicsBackend<'a> for GbmSurfaceInternal +where + ::Surface: CursorBackend<'a>, + <::Surface as CursorBackend<'a>>::CursorFormat=&'a Buffer, + <::Surface as CursorBackend<'a>>::Error: ::std::error::Error + Send +{ +*/ +// But for now got to do this: + +#[cfg(feature = "backend_drm_legacy")] +impl<'a, A: AsRawFd + 'static> CursorBackend<'a> for GbmSurfaceInternal> { + type CursorFormat = &'a ImageBuffer, Vec>; + type Error = Error; + + fn set_cursor_position(&self, x: u32, y: u32) -> Result<()> { + ResultExt::chain_err(self.crtc.set_cursor_position(x, y), || { + ErrorKind::UnderlyingBackendError + }) + } + + fn set_cursor_representation<'b>( + &'b self, + buffer: &ImageBuffer, Vec>, + hotspot: (u32, u32), + ) -> Result<()> + where + 'a: 'b, + { + let (w, h) = buffer.dimensions(); + debug!(self.logger, "Importing cursor"); + + // import the cursor into a buffer we can render + let mut cursor = self + .dev + .borrow_mut() + .create_buffer_object( + w, + h, + GbmFormat::ARGB8888, + BufferObjectFlags::CURSOR | BufferObjectFlags::WRITE, + ) + .chain_err(|| ErrorKind::BufferCreationFailed)?; + + cursor + .write(&**buffer) + .chain_err(|| ErrorKind::BufferWriteFailed)? + .chain_err(|| ErrorKind::BufferWriteFailed)?; + + trace!(self.logger, "Setting the new imported cursor"); + + ResultExt::chain_err(self.crtc.set_cursor_representation(&cursor, hotspot), || { + ErrorKind::UnderlyingBackendError + })?; + + // and store it + self.cursor.set((cursor, hotspot)); + Ok(()) + } +} + +impl Drop for GbmSurfaceInternal { + fn drop(&mut self) { + // Drop framebuffers attached to the userdata of the gbm surface buffers. + // (They don't implement drop, as they need the device) + if let Ok(Some(fb)) = { + if let Some(mut next) = self.next_buffer.take() { + next.take_userdata() + } else { + Ok(None) + } + } { + // ignore failure at this point + let _ = framebuffer::destroy(&self.crtc, fb.handle()); + } + + if let Ok(Some(fb)) = { + if let Some(mut next) = self.front_buffer.take() { + next.take_userdata() + } else { + Ok(None) + } + } { + // ignore failure at this point + let _ = framebuffer::destroy(&self.crtc, fb.handle()); + } + } +} + +/// Gbm surface for rendering +pub struct GbmSurface(pub(super) Rc>); + +impl GbmSurface { + /// Flips the underlying buffers. + /// + /// The surface will report being already flipped until the matching event + /// was processed either by calling `GbmDevice::process_events` manually after the flip + /// (bad idea performance-wise) or by binding the device to an event-loop by using + /// `device_bind`. + /// + /// *Note*: This might trigger a full modeset on the underlying device, + /// potentially causing some flickering. In that case this operation is + /// blocking until the crtc is in the desired state. + pub unsafe fn page_flip(&self) -> ::std::result::Result<(), SwapBuffersError> { + self.0.page_flip() + } + + /// Recreate underlying gbm resources. + /// + /// This recreates the gbm surfaces resources, which might be needed after e.g. + /// calling [`Surface::use_mode`](../trait.Surface.html#method.use_mode). + /// You may check if your `GbmSurface` needs recreation through + /// [`needs_recreation`](#method.needs_recreation). + pub fn recreate(&self) -> Result<()> { + self.0.recreate() + } + + /// Check if underlying gbm resources need to be recreated. + pub fn needs_recreation(&self) -> bool { + self.0.crtc.commit_pending() + } +} + +impl Surface for GbmSurface { + type Connectors = <::Surface as Surface>::Connectors; + type Error = 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.0.add_connector(connector) + } + + fn remove_connector(&self, connector: connector::Handle) -> Result<()> { + self.0.remove_connector(connector) + } + + fn current_mode(&self) -> Option { + self.0.current_mode() + } + + fn pending_mode(&self) -> Option { + self.0.pending_mode() + } + + fn use_mode(&self, mode: Option) -> Result<()> { + self.0.use_mode(mode) + } +} + +#[cfg(feature = "backend_drm_legacy")] +impl<'a, A: AsRawFd + 'static> CursorBackend<'a> for GbmSurface> { + type CursorFormat = &'a ImageBuffer, Vec>; + type Error = Error; + + fn set_cursor_position(&self, x: u32, y: u32) -> Result<()> { + self.0.set_cursor_position(x, y) + } + + fn set_cursor_representation<'b>( + &'b self, + buffer: &ImageBuffer, Vec>, + hotspot: (u32, u32), + ) -> Result<()> + where + 'a: 'b, + { + self.0.set_cursor_representation(buffer, hotspot) + } +} diff --git a/src/backend/drm/error.rs b/src/backend/drm/legacy/error.rs similarity index 76% rename from src/backend/drm/error.rs rename to src/backend/drm/legacy/error.rs index 3c9b589..4e14eaf 100644 --- a/src/backend/drm/error.rs +++ b/src/backend/drm/legacy/error.rs @@ -1,8 +1,7 @@ //! -//! Errors thrown by the `DrmDevice` and `DrmBackend` +//! Errors thrown by the `LegacyDrmDevice` and `LegacyDrmSurface` //! -use backend::graphics::egl::error as egl; use drm::control::{connector, crtc, Mode}; error_chain! { @@ -23,18 +22,6 @@ error_chain! { description("Unable to determine device id of drm device"), } - #[doc = "Creation of gbm resource failed"] - GbmInitFailed { - description("Creation of gbm resource failed"), - display("Creation of gbm resource failed"), - } - - #[doc = "Swapping front buffers failed"] - FailedToSwap { - description("Swapping front buffers failed"), - display("Swapping front buffers failed"), - } - #[doc = "Device is currently paused"] DeviceInactive { description("Device is currently paused, operation rejected"), @@ -60,7 +47,7 @@ error_chain! { } } - links { - EGL(egl::Error, egl::ErrorKind) #[doc = "EGL error"]; + foreign_links { + FailedToSwap(::backend::graphics::SwapBuffersError) #[doc = "Swapping front buffers failed"]; } } diff --git a/src/backend/drm/legacy/mod.rs b/src/backend/drm/legacy/mod.rs new file mode 100644 index 0000000..e37cd20 --- /dev/null +++ b/src/backend/drm/legacy/mod.rs @@ -0,0 +1,286 @@ +//! +//! [`RawDevice`](../trait.RawDevice.html) and [`RawSurface`](../trait.RawSurface.html) +//! implementations using the legacy mode-setting infrastructure. +//! +//! Usually this implementation will be wrapped into a [`GbmDevice`](../gbm/struct.GbmDevice.html). +//! Take a look at `anvil`s source code for an example of this. +//! +//! For an example how to use this standalone, take a look at the `raw_drm` example. +//! + +use super::{DevPath, Device, DeviceHandler, RawDevice}; + +use drm::control::{connector, crtc, encoder, Device as ControlDevice, ResourceHandles, ResourceInfo}; +use drm::Device as BasicDevice; +use nix::libc::dev_t; +use nix::sys::stat::fstat; + +use std::cell::RefCell; +use std::collections::{HashMap, HashSet}; +use std::os::unix::io::{AsRawFd, RawFd}; +use std::rc::{Rc, Weak}; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::{Arc, RwLock}; + +mod surface; +pub use self::surface::LegacyDrmSurface; +use self::surface::{LegacyDrmSurfaceInternal, State}; + +pub mod error; +use self::error::*; + +#[cfg(feature = "backend_session")] +pub mod session; + +/// Open raw drm device utilizing legacy mode-setting +pub struct LegacyDrmDevice { + dev: Rc>, + dev_id: dev_t, + active: Arc, + backends: Rc>>>>, + handler: Option>>>>, + logger: ::slog::Logger, +} + +pub(in crate::backend::drm) struct Dev { + fd: A, + priviledged: bool, + active: Arc, + old_state: HashMap)>, + logger: ::slog::Logger, +} +impl AsRawFd for Dev { + fn as_raw_fd(&self) -> RawFd { + self.fd.as_raw_fd() + } +} +impl BasicDevice for Dev {} +impl ControlDevice for Dev {} +impl Drop for Dev { + fn drop(&mut self) { + info!(self.logger, "Dropping device: {:?}", self.dev_path()); + if self.active.load(Ordering::SeqCst) { + // Here we restore the tty to it's previous state. + // In case e.g. getty was running on the tty sets the correct framebuffer again, + // so that getty will be visible. + let old_state = self.old_state.clone(); + for (handle, (info, connectors)) in old_state { + if let Err(err) = crtc::set( + &*self, + handle, + info.fb(), + &connectors, + info.position(), + info.mode(), + ) { + error!(self.logger, "Failed to reset crtc ({:?}). Error: {}", handle, err); + } + } + } + if self.priviledged { + if let Err(err) = self.drop_master() { + error!(self.logger, "Failed to drop drm master state. Error: {}", err); + } + } + } +} + +impl LegacyDrmDevice { + /// Create a new `LegacyDrmDevice` from an open drm node + /// + /// Returns an error if the file is no valid drm node or context creation was not + /// successful. + pub fn new(dev: A, logger: L) -> Result + where + L: Into>, + { + let log = ::slog_or_stdlog(logger).new(o!("smithay_module" => "backend_drm")); + info!(log, "DrmDevice initializing"); + + let dev_id = fstat(dev.as_raw_fd()) + .chain_err(|| ErrorKind::UnableToGetDeviceId)? + .st_rdev; + + let active = Arc::new(AtomicBool::new(true)); + let mut dev = Dev { + fd: dev, + priviledged: true, + old_state: HashMap::new(), + active: active.clone(), + logger: log.clone(), + }; + + // we want to modeset, so we better be the master, if we run via a tty session + if dev.set_master().is_err() { + warn!(log, "Unable to become drm master, assuming unpriviledged mode"); + dev.priviledged = false; + }; + + // enumerate (and save) the current device state + let res_handles = ControlDevice::resource_handles(&dev).chain_err(|| { + ErrorKind::DrmDev(format!("Error loading drm resources on {:?}", dev.dev_path())) + })?; + for &con in res_handles.connectors() { + let con_info = connector::Info::load_from_device(&dev, con).chain_err(|| { + ErrorKind::DrmDev(format!("Error loading connector info on {:?}", dev.dev_path())) + })?; + if let Some(enc) = con_info.current_encoder() { + let enc_info = encoder::Info::load_from_device(&dev, enc).chain_err(|| { + ErrorKind::DrmDev(format!("Error loading encoder info on {:?}", dev.dev_path())) + })?; + if let Some(crtc) = enc_info.current_crtc() { + let info = crtc::Info::load_from_device(&dev, crtc).chain_err(|| { + ErrorKind::DrmDev(format!("Error loading crtc info on {:?}", dev.dev_path())) + })?; + dev.old_state + .entry(crtc) + .or_insert((info, Vec::new())) + .1 + .push(con); + } + } + } + + Ok(LegacyDrmDevice { + dev: Rc::new(dev), + dev_id, + active, + backends: Rc::new(RefCell::new(HashMap::new())), + handler: None, + logger: log.clone(), + }) + } +} + +impl AsRawFd for LegacyDrmDevice { + fn as_raw_fd(&self) -> RawFd { + self.dev.as_raw_fd() + } +} + +impl BasicDevice for LegacyDrmDevice {} +impl ControlDevice for LegacyDrmDevice {} + +impl Device for LegacyDrmDevice { + type Surface = LegacyDrmSurface; + + fn device_id(&self) -> dev_t { + self.dev_id + } + + fn set_handler(&mut self, handler: impl DeviceHandler + 'static) { + self.handler = Some(RefCell::new(Box::new(handler))); + } + + fn clear_handler(&mut self) { + let _ = self.handler.take(); + } + + fn create_surface(&mut self, crtc: crtc::Handle) -> Result> { + if self.backends.borrow().contains_key(&crtc) { + bail!(ErrorKind::CrtcAlreadyInUse(crtc)); + } + + if !self.active.load(Ordering::SeqCst) { + bail!(ErrorKind::DeviceInactive); + } + + // Try to enumarate the current state to set the initial state variable correctly + + let crtc_info = crtc::Info::load_from_device(self, crtc) + .chain_err(|| ErrorKind::DrmDev(format!("Error loading crtc info on {:?}", self.dev_path())))?; + + let mode = crtc_info.mode(); + + let mut connectors = HashSet::new(); + let res_handles = ControlDevice::resource_handles(self).chain_err(|| { + ErrorKind::DrmDev(format!("Error loading drm resources on {:?}", self.dev_path())) + })?; + for &con in res_handles.connectors() { + let con_info = connector::Info::load_from_device(self, con).chain_err(|| { + ErrorKind::DrmDev(format!("Error loading connector info on {:?}", self.dev_path())) + })?; + if let Some(enc) = con_info.current_encoder() { + let enc_info = encoder::Info::load_from_device(self, enc).chain_err(|| { + ErrorKind::DrmDev(format!("Error loading encoder info on {:?}", self.dev_path())) + })?; + if let Some(current_crtc) = enc_info.current_crtc() { + if crtc == current_crtc { + connectors.insert(con); + } + } + } + } + + let state = State { mode, connectors }; + let backend = Rc::new(LegacyDrmSurfaceInternal { + dev: self.dev.clone(), + crtc, + state: RwLock::new(state.clone()), + pending: RwLock::new(state), + logger: self.logger.new(o!("crtc" => format!("{:?}", crtc))), + }); + + self.backends.borrow_mut().insert(crtc, Rc::downgrade(&backend)); + Ok(LegacyDrmSurface(backend)) + } + + fn process_events(&mut self) { + match crtc::receive_events(self) { + Ok(events) => { + for event in events { + if let crtc::Event::PageFlip(event) = event { + if self.active.load(Ordering::SeqCst) { + if self + .backends + .borrow() + .get(&event.crtc) + .iter() + .flat_map(|x| x.upgrade()) + .next() + .is_some() + { + trace!(self.logger, "Handling event for backend {:?}", event.crtc); + if let Some(handler) = self.handler.as_ref() { + handler.borrow_mut().vblank(event.crtc); + } + } else { + self.backends.borrow_mut().remove(&event.crtc); + } + } + } + } + } + Err(err) => { + if let Some(handler) = self.handler.as_ref() { + handler.borrow_mut().error( + ResultExt::<()>::chain_err(Err(err), || { + ErrorKind::DrmDev(format!("Error processing drm events on {:?}", self.dev_path())) + }) + .unwrap_err(), + ); + } + } + } + } + + fn resource_info(&self, handle: T::Handle) -> Result { + T::load_from_device(self, handle) + .chain_err(|| ErrorKind::DrmDev(format!("Error loading resource info on {:?}", self.dev_path()))) + } + + fn resource_handles(&self) -> Result { + ControlDevice::resource_handles(self) + .chain_err(|| ErrorKind::DrmDev(format!("Error loading resource info on {:?}", self.dev_path()))) + } +} + +impl RawDevice for LegacyDrmDevice { + type Surface = LegacyDrmSurface; +} + +impl Drop for LegacyDrmDevice { + fn drop(&mut self) { + self.clear_handler(); + } +} diff --git a/src/backend/drm/legacy/session.rs b/src/backend/drm/legacy/session.rs new file mode 100644 index 0000000..fcb7b55 --- /dev/null +++ b/src/backend/drm/legacy/session.rs @@ -0,0 +1,92 @@ +//! +//! Support to register an open [`LegacyDrmDevice`](../struct.LegacyDrmDevice.html) +//! to an open [`Session`](../../session/trait.Session.html). +//! + +use drm::control::crtc; +use drm::Device as BasicDevice; +use nix::libc::dev_t; +use nix::sys::stat; +use std::cell::RefCell; +use std::collections::HashMap; +use std::os::unix::io::{AsRawFd, RawFd}; +use std::rc::{Rc, Weak}; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::Arc; + +use super::{Dev, LegacyDrmDevice, LegacyDrmSurfaceInternal}; +use backend::session::{AsSessionObserver, SessionObserver}; + +/// [`SessionObserver`](../../session/trait.SessionObserver.html) +/// linked to the [`LegacyDrmDevice`](../struct.LegacyDrmDevice.html) +/// it was created from. +pub struct LegacyDrmDeviceObserver { + dev: Weak>, + dev_id: dev_t, + priviledged: bool, + active: Arc, + backends: Weak>>>>, + logger: ::slog::Logger, +} + +impl AsSessionObserver> for LegacyDrmDevice { + fn observer(&mut self) -> LegacyDrmDeviceObserver { + LegacyDrmDeviceObserver { + dev: Rc::downgrade(&self.dev), + dev_id: self.dev_id, + active: self.active.clone(), + priviledged: self.dev.priviledged, + backends: Rc::downgrade(&self.backends), + logger: self.logger.clone(), + } + } +} + +impl SessionObserver for LegacyDrmDeviceObserver { + fn pause(&mut self, devnum: Option<(u32, u32)>) { + if let Some((major, minor)) = devnum { + if major as u64 != stat::major(self.dev_id) || minor as u64 != stat::minor(self.dev_id) { + return; + } + } + if let Some(device) = self.dev.upgrade() { + if let Some(backends) = self.backends.upgrade() { + for surface in backends.borrow().values().filter_map(Weak::upgrade) { + // other ttys that use no cursor, might not clear it themselves. + // This makes sure our cursor won't stay visible. + let _ = crtc::clear_cursor(&*device, surface.crtc); + } + } + } + self.active.store(false, Ordering::SeqCst); + if self.priviledged { + if let Some(device) = self.dev.upgrade() { + if let Err(err) = device.drop_master() { + error!(self.logger, "Failed to drop drm master state. Error: {}", err); + } + } + } + } + + fn activate(&mut self, devnum: Option<(u32, u32, Option)>) { + if let Some((major, minor, fd)) = devnum { + if major as u64 != stat::major(self.dev_id) || minor as u64 != stat::minor(self.dev_id) { + return; + } else if let Some(fd) = fd { + info!(self.logger, "Replacing fd"); + if let Some(device) = self.dev.upgrade() { + ::nix::unistd::dup2(device.as_raw_fd(), fd) + .expect("Failed to replace file descriptor of drm device"); + } + } + } + self.active.store(true, Ordering::SeqCst); + if self.priviledged { + if let Some(device) = self.dev.upgrade() { + if let Err(err) = device.set_master() { + crit!(self.logger, "Failed to acquire drm master again. Error: {}", err); + } + } + } + } +} diff --git a/src/backend/drm/legacy/surface.rs b/src/backend/drm/legacy/surface.rs new file mode 100644 index 0000000..3ea3b5b --- /dev/null +++ b/src/backend/drm/legacy/surface.rs @@ -0,0 +1,317 @@ +pub use drm::buffer::Buffer; +use drm::control::{connector, crtc, encoder, framebuffer, Device as ControlDevice, Mode, ResourceInfo}; +use drm::Device as BasicDevice; + +use std::collections::HashSet; +use std::os::unix::io::{AsRawFd, RawFd}; +use std::rc::Rc; +use std::sync::RwLock; + +use backend::drm::{DevPath, RawSurface, Surface}; +use backend::graphics::CursorBackend; +use backend::graphics::SwapBuffersError; + +use super::{error::*, Dev}; + +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct State { + pub mode: Option, + pub connectors: HashSet, +} + +pub(super) struct LegacyDrmSurfaceInternal { + pub(super) dev: Rc>, + pub(super) crtc: crtc::Handle, + pub(super) state: RwLock, + pub(super) pending: RwLock, + pub(super) logger: ::slog::Logger, +} + +impl AsRawFd for LegacyDrmSurfaceInternal { + fn as_raw_fd(&self) -> RawFd { + self.dev.as_raw_fd() + } +} + +impl BasicDevice for LegacyDrmSurfaceInternal {} +impl ControlDevice for LegacyDrmSurfaceInternal {} + +impl<'a, A: AsRawFd + 'static> CursorBackend<'a> for LegacyDrmSurfaceInternal { + type CursorFormat = &'a Buffer; + type Error = Error; + + fn set_cursor_position(&self, x: u32, y: u32) -> Result<()> { + trace!(self.logger, "Move the cursor to {},{}", x, y); + crtc::move_cursor(self, self.crtc, (x as i32, y as i32)) + .chain_err(|| ErrorKind::DrmDev(format!("Error moving cursor on {:?}", self.dev_path()))) + } + + fn set_cursor_representation<'b>(&'b self, buffer: Self::CursorFormat, hotspot: (u32, u32)) -> Result<()> + where + 'a: 'b, + { + trace!(self.logger, "Setting the new imported cursor"); + + if crtc::set_cursor2(self, self.crtc, buffer, (hotspot.0 as i32, hotspot.1 as i32)).is_err() { + crtc::set_cursor(self, self.crtc, buffer) + .chain_err(|| ErrorKind::DrmDev(format!("Failed to set cursor on {:?}", self.dev_path())))?; + } + + Ok(()) + } +} + +impl Surface for LegacyDrmSurfaceInternal { + type Error = Error; + type Connectors = HashSet; + + fn crtc(&self) -> crtc::Handle { + self.crtc + } + + fn current_connectors(&self) -> Self::Connectors { + self.state.read().unwrap().connectors.clone() + } + + fn pending_connectors(&self) -> Self::Connectors { + self.pending.read().unwrap().connectors.clone() + } + + fn current_mode(&self) -> Option { + self.state.read().unwrap().mode.clone() + } + + fn pending_mode(&self) -> Option { + self.pending.read().unwrap().mode.clone() + } + + fn add_connector(&self, connector: connector::Handle) -> Result<()> { + let info = connector::Info::load_from_device(self, connector).chain_err(|| { + ErrorKind::DrmDev(format!("Error loading connector info on {:?}", self.dev_path())) + })?; + + let mut pending = self.pending.write().unwrap(); + + // check if the connector can handle the current mode + if info.modes().contains(pending.mode.as_ref().unwrap()) { + // check if there is a valid encoder + let encoders = info + .encoders() + .iter() + .map(|encoder| { + encoder::Info::load_from_device(self, *encoder).chain_err(|| { + ErrorKind::DrmDev(format!("Error loading encoder info on {:?}", self.dev_path())) + }) + }) + .collect::>>()?; + + // and if any encoder supports the selected crtc + let resource_handles = self.resource_handles().chain_err(|| { + ErrorKind::DrmDev(format!("Error loading resources on {:?}", self.dev_path())) + })?; + if !encoders + .iter() + .map(|encoder| encoder.possible_crtcs()) + .all(|crtc_list| resource_handles.filter_crtcs(crtc_list).contains(&self.crtc)) + { + bail!(ErrorKind::NoSuitableEncoder(info, self.crtc)); + } + + pending.connectors.insert(connector); + Ok(()) + } else { + bail!(ErrorKind::ModeNotSuitable(pending.mode.unwrap())); + } + } + + fn remove_connector(&self, connector: connector::Handle) -> Result<()> { + self.pending.write().unwrap().connectors.remove(&connector); + Ok(()) + } + + fn use_mode(&self, mode: Option) -> Result<()> { + let mut pending = self.pending.write().unwrap(); + + // check the connectors to see if this mode is supported + if let Some(mode) = mode { + for connector in &pending.connectors { + if !connector::Info::load_from_device(self, *connector) + .chain_err(|| { + ErrorKind::DrmDev(format!("Error loading connector info on {:?}", self.dev_path())) + })? + .modes() + .contains(&mode) + { + bail!(ErrorKind::ModeNotSuitable(mode)); + } + } + } + + pending.mode = mode; + + Ok(()) + } +} + +impl RawSurface for LegacyDrmSurfaceInternal { + fn commit_pending(&self) -> bool { + *self.pending.read().unwrap() != *self.state.read().unwrap() + } + + fn commit(&self, framebuffer: framebuffer::Handle) -> Result<()> { + let mut current = self.state.write().unwrap(); + let pending = self.pending.read().unwrap(); + + { + let removed = current.connectors.difference(&pending.connectors); + let added = pending.connectors.difference(¤t.connectors); + + for conn in removed { + if let Ok(info) = connector::Info::load_from_device(self, *conn) { + info!(self.logger, "Removing connector: {:?}", info.connector_type()); + } else { + info!(self.logger, "Removing unknown connector"); + } + } + + for conn in added { + if let Ok(info) = connector::Info::load_from_device(self, *conn) { + info!(self.logger, "Adding connector: {:?}", info.connector_type()); + } else { + info!(self.logger, "Adding unknown connector"); + } + } + + if current.mode != pending.mode { + info!( + self.logger, + "Setting new mode: {:?}", + pending.mode.as_ref().unwrap().name() + ); + } + } + + debug!(self.logger, "Setting screen"); + crtc::set( + self, + self.crtc, + framebuffer, + &pending + .connectors + .iter() + .map(|x| *x) + .collect::>(), + (0, 0), + pending.mode, + ) + .chain_err(|| { + ErrorKind::DrmDev(format!( + "Error setting crtc {:?} on {:?}", + self.crtc, + self.dev_path() + )) + })?; + + *current = pending.clone(); + + Ok(()) + } + + fn page_flip(&self, framebuffer: framebuffer::Handle) -> ::std::result::Result<(), SwapBuffersError> { + trace!(self.logger, "Queueing Page flip"); + + crtc::page_flip( + self, + self.crtc, + framebuffer, + &[crtc::PageFlipFlags::PageFlipEvent], + ) + .map_err(|_| SwapBuffersError::ContextLost) + } +} + +impl Drop for LegacyDrmSurfaceInternal { + fn drop(&mut self) { + // ignore failure at this point + let _ = crtc::clear_cursor(self, self.crtc); + } +} + +/// Open raw crtc utilizing legacy mode-setting +pub struct LegacyDrmSurface(pub(super) Rc>); + +impl AsRawFd for LegacyDrmSurface { + fn as_raw_fd(&self) -> RawFd { + self.0.as_raw_fd() + } +} + +impl BasicDevice for LegacyDrmSurface {} +impl ControlDevice for LegacyDrmSurface {} + +impl<'a, A: AsRawFd + 'static> CursorBackend<'a> for LegacyDrmSurface { + type CursorFormat = &'a Buffer; + type Error = Error; + + fn set_cursor_position(&self, x: u32, y: u32) -> Result<()> { + self.0.set_cursor_position(x, y) + } + + fn set_cursor_representation<'b>(&'b self, buffer: Self::CursorFormat, hotspot: (u32, u32)) -> Result<()> + where + 'a: 'b, + { + self.0.set_cursor_representation(buffer, hotspot) + } +} + +impl Surface for LegacyDrmSurface { + type Error = Error; + type Connectors = HashSet; + + 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 current_mode(&self) -> Option { + self.0.current_mode() + } + + fn pending_mode(&self) -> Option { + self.0.pending_mode() + } + + fn add_connector(&self, connector: connector::Handle) -> Result<()> { + self.0.add_connector(connector) + } + + fn remove_connector(&self, connector: connector::Handle) -> Result<()> { + self.0.remove_connector(connector) + } + + fn use_mode(&self, mode: Option) -> Result<()> { + self.0.use_mode(mode) + } +} + +impl RawSurface for LegacyDrmSurface { + fn commit_pending(&self) -> bool { + self.0.commit_pending() + } + + fn commit(&self, framebuffer: framebuffer::Handle) -> Result<()> { + self.0.commit(framebuffer) + } + + fn page_flip(&self, framebuffer: framebuffer::Handle) -> ::std::result::Result<(), SwapBuffersError> { + self.0.page_flip(framebuffer) + } +} diff --git a/src/backend/drm/mod.rs b/src/backend/drm/mod.rs index 39ec1cc..8a143b0 100644 --- a/src/backend/drm/mod.rs +++ b/src/backend/drm/mod.rs @@ -1,455 +1,217 @@ -//! Drm/Kms types and backend implementations -//! -//! This module provide a `DrmDevice` which acts as a representation for any DRM -//! device and can be used to create the second provided structure a `DrmBackend`. -//! -//! Initialization happens through the types provided by [`drm-rs`](https://docs.rs/drm/). -//! -//! Three entities are relevant for the initialization procedure. -//! -//! "Crtc"s represent scanout engines of the device pointer to one framebuffer. There responsibility -//! is to read the data of the framebuffer and export it into an "Encoder". The number of crtc's -//! represent the number of independent output devices the hardware may handle. -//! -//! An "Encoder" encodes the data of connected crtcs into a video signal for a fixed set -//! of connectors. E.g. you might have an analog encoder based on a DAG for VGA ports, but another -//! one for digital ones. Also not every encoder might be connected to every crtc. -//! -//! The last entity the "Connector" represents a port on your computer, possibly with a connected -//! monitor, TV, capture card, etc. -//! -//! The `DrmBackend` created from a `DrmDevice` represents a crtc of the device you can render to -//! and that feeds a given set of connectors, that can be manipulated at runtime. -//! -//! From these circumstances it becomes clear, that one crtc might only send it's data to a connector, -//! that is attached to any encoder that is attached to the crtc itself. It is the responsibility of the -//! user to ensure that a given set of a crtc with it's connectors is valid or an error will be thrown. -//! -//! For more details refer to the [`drm-rs` documentation](https://docs.rs/drm). //! //! -//! ## How to use it +//! This module provides Traits reprensentating open devices +//! and their surfaces to render contents. //! -//! ### Initialization +//! --- //! -//! To initialize the `DrmDevice` you need either a `RawFd` or a `File` of -//! your DRM node. The `File` is recommended as it represents the save API. +//! Initialization of devices happens through an open file descriptor +//! of a drm device. //! -//! Once you got your `DrmDevice` you can then use it to create `DrmBackend`s. -//! You will need to use the `drm` crate to provide the required types to create -//! a backend. +//! --- //! -//! ```rust,no_run -//! extern crate drm; -//! extern crate smithay; -//! extern crate wayland_server; +//! Initialization of surfaces happens through the types provided by +//! [`drm-rs`](https://docs.rs/drm/0.3.4/drm/). //! -//! use drm::Device as BasicDevice; -//! use drm::control::{Device as ControlDevice, ResourceInfo}; -//! use drm::control::connector::{Info as ConnectorInfo, State as ConnectorState}; -//! use drm::control::encoder::{Info as EncoderInfo}; -//! use std::fs::{File, OpenOptions}; -//! use std::os::unix::io::RawFd; -//! use std::os::unix::io::AsRawFd; -//! use smithay::backend::drm::{DrmDevice, DrmBackend}; +//! Four entities are relevant for the initialization procedure. //! -//! #[derive(Debug)] -//! pub struct Card(File); +//! [`crtc`](https://docs.rs/drm/0.3.4/drm/control/crtc/index.html)s represent scanout engines +//! of the device pointer to one framebuffer. +//! Their responsibility is to read the data of the framebuffer and export it into an "Encoder". +//! The number of crtc's represent the number of independant output devices the hardware may handle. //! -//! impl AsRawFd for Card { -//! fn as_raw_fd(&self) -> RawFd { -//! self.0.as_raw_fd() -//! } -//! } +//! An [`encoder`](https://docs.rs/drm/0.3.4/drm/control/encoder/index.html) encodes the data of +//! connected crtcs into a video signal for a fixed set of connectors. +//! E.g. you might have an analog encoder based on a DAG for VGA ports, but another one for digital ones. +//! Also not every encoder might be connected to every crtc. //! -//! impl BasicDevice for Card {} -//! impl ControlDevice for Card {} +//! A [`connector`](https://docs.rs/drm/0.3.4/drm/control/connector/index.html) represents a port +//! on your computer, possibly with a connected monitor, TV, capture card, etc. //! -//! # fn main() { -//! // Open the drm device -//! let mut options = OpenOptions::new(); -//! options.read(true); -//! options.write(true); -//! let mut device = DrmDevice::new( -//! Card(options.open("/dev/dri/card0").unwrap()), // try to detect it properly -//! None /*put a logger here*/ -//! ).unwrap(); +//! On surface creation a matching encoder for your `encoder`-`connector` is automatically selected, +//! if it exists, which means you still need to check your configuration. //! -//! // Get a set of all modesetting resource handles -//! let res_handles = device.resource_handles().unwrap(); +//! At last a [`Mode`](https://docs.rs/drm/0.3.4/drm/control/struct.Mode.html) needs to be selected, +//! supported by the `crtc` in question. //! -//! // Use first connected connector for this example -//! let connector_info = res_handles.connectors().iter() -//! .map(|conn| ConnectorInfo::load_from_device(&device, *conn).unwrap()) -//! .find(|conn| conn.connection_state() == ConnectorState::Connected) -//! .unwrap(); -//! -//! // Use the first encoder -//! let encoder_info = EncoderInfo::load_from_device(&device, connector_info.encoders()[0]).unwrap(); -//! -//! // use the connected crtc if any -//! let crtc = encoder_info.current_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()); -//! -//! // Use first mode (usually the highest resolution) -//! let mode = connector_info.modes()[0]; -//! -//! // Create the backend -//! let backend = device.create_backend( -//! crtc, -//! mode, -//! vec![connector_info.handle()] -//! ).unwrap(); -//! # } -//! ``` -//! -//! ### Page Flips / Tear-free video -//! Calling the usual `EglGraphicsBackend::swap_buffers` function on a -//! `DrmBackend` works the same to finish the rendering, but will return -//! `SwapBuffersError::AlreadySwapped` for any new calls until the page flip of the -//! crtc has happened. -//! -//! You can monitor the page flips by registering the `DrmDevice` as and -//! `FdEventSourceHandler` and setting a `DrmHandler` on it. You will be notified -//! whenever a page flip has happened, so you can render the next frame immediately -//! and get a tear-free representation on the display. -//! -//! You need to render at least once to successfully trigger the first event. -//! -//! ```rust,no_run -//! # extern crate drm; -//! # extern crate smithay; -//! # extern crate wayland_server; -//! # -//! # use drm::Device as BasicDevice; -//! # use drm::control::{Device as ControlDevice, ResourceInfo}; -//! # use drm::control::connector::{Info as ConnectorInfo, State as ConnectorState}; -//! use drm::control::crtc::{Handle as CrtcHandle}; -//! use drm::result::Error as DrmError; -//! # use std::fs::{File, OpenOptions}; -//! # use std::os::unix::io::RawFd; -//! # use std::os::unix::io::AsRawFd; -//! # use std::time::Duration; -//! use smithay::backend::drm::{DrmDevice, DrmBackend, DrmHandler, drm_device_bind}; -//! use smithay::backend::graphics::egl::EGLGraphicsBackend; -//! # -//! # #[derive(Debug)] -//! # pub struct Card(File); -//! # impl AsRawFd for Card { -//! # fn as_raw_fd(&self) -> RawFd { -//! # self.0.as_raw_fd() -//! # } -//! # } -//! # impl BasicDevice for Card {} -//! # impl ControlDevice for Card {} -//! # -//! # fn main() { -//! # -//! # let mut event_loop = wayland_server::calloop::EventLoop::<()>::new().unwrap(); -//! # let mut display = wayland_server::Display::new(event_loop.handle()); -//! # -//! # let mut options = OpenOptions::new(); -//! # options.read(true); -//! # options.write(true); -//! # let mut device = DrmDevice::new( -//! # Card(options.open("/dev/dri/card0").unwrap()), // try to detect it properly -//! # None /*put a logger here*/ -//! # ).unwrap(); -//! # -//! # let res_handles = device.resource_handles().unwrap(); -//! # let connector_info = res_handles.connectors().iter() -//! # .map(|conn| ConnectorInfo::load_from_device(&device, *conn).unwrap()) -//! # .find(|conn| conn.connection_state() == ConnectorState::Connected) -//! # .unwrap(); -//! # let crtc = res_handles.crtcs()[0]; -//! # let mode = connector_info.modes()[0]; -//! # let backend = device.create_backend( -//! # crtc, -//! # mode, -//! # vec![connector_info.handle()] -//! # ).unwrap(); -//! -//! struct MyDrmHandler(DrmBackend); -//! -//! impl DrmHandler for MyDrmHandler { -//! fn ready( -//! &mut self, -//! _device: &mut DrmDevice, -//! _crtc: CrtcHandle, -//! _frame: u32, -//! _duration: Duration) -//! { -//! // render surfaces and swap again -//! self.0.swap_buffers().unwrap(); -//! } -//! fn error( -//! &mut self, -//! device: &mut DrmDevice, -//! error: DrmError) -//! { -//! panic!("DrmDevice errored: {}", error); -//! } -//! } -//! -//! // render something (like clear_color) -//! backend.swap_buffers().unwrap(); -//! -//! let (_source, _device_rc) = drm_device_bind( -//! &event_loop.handle(), -//! device, -//! MyDrmHandler(backend) -//! ).map_err(|(err, _)| err).unwrap(); -//! -//! /* And then run the event loop once all your setup is done */ -//! # } -//! ``` -use backend::graphics::egl::{ - context::{EGLContext, GlAttributes}, - error::Result as EGLResult, - native::Gbm, - wayland::{EGLDisplay, EGLWaylandExtensions}, -}; -#[cfg(feature = "backend_session")] -use backend::session::{AsSessionObserver, SessionObserver}; -use drm::{ - control::{connector, crtc, encoder, framebuffer, Device as ControlDevice, Mode, ResourceInfo}, - result::Error as DrmError, +pub use drm::{ + buffer::Buffer, + control::{ + connector, crtc, encoder, framebuffer, Device as ControlDevice, Mode, ResourceHandles, ResourceInfo, + }, Device as BasicDevice, }; -use gbm::{BufferObject, Device as GbmDevice}; -use nix::{ - self, - sys::stat::{self, dev_t, fstat}, -}; -use std::{ - cell::RefCell, - collections::HashMap, - hash::{Hash, Hasher}, - io::Error as IoError, - os::unix::io::{AsRawFd, RawFd}, - path::PathBuf, - rc::{Rc, Weak}, - sync::{ - atomic::{AtomicBool, Ordering}, - Arc, Once, ONCE_INIT, - }, - time::Duration, -}; +pub use nix::libc::dev_t; -use wayland_server::{ - calloop::{ - generic::{EventedRawFd, Generic}, - mio::Ready, - LoopHandle, Source, - }, - Display, -}; +use std::error::Error; +use std::iter::IntoIterator; +use std::os::unix::io::AsRawFd; +use std::path::PathBuf; -mod backend; -pub mod error; +use wayland_server::calloop::generic::{EventedFd, Generic}; +use wayland_server::calloop::mio::Ready; +pub use wayland_server::calloop::InsertError; +use wayland_server::calloop::{LoopHandle, Source}; -pub use self::backend::DrmBackend; -use self::{backend::DrmBackendInternal, error::*}; +use super::graphics::SwapBuffersError; -static LOAD: Once = ONCE_INIT; +#[cfg(feature = "backend_drm_egl")] +pub mod egl; +#[cfg(feature = "backend_drm_gbm")] +pub mod gbm; +#[cfg(feature = "backend_drm_legacy")] +pub mod legacy; -/// Representation of an open DRM device node to create rendering backends -pub struct DrmDevice { - context: Rc, GbmDevice>>, - old_state: HashMap)>, - device_id: dev_t, - backends: Rc>>>>, - active: Arc, - privileged: bool, - logger: ::slog::Logger, +/// Trait to receive events of a bound [`Device`](trait.Device.html) +/// +/// See [`device_bind`](fn.device_bind.html) +pub trait DeviceHandler { + /// The [`Device`](trait.Device.html) type this handler can handle + type Device: Device + ?Sized; + + /// A vblank blank event on the provided crtc has happend + fn vblank(&mut self, crtc: crtc::Handle); + /// An error happend while processing events + fn error(&mut self, error: <<::Device as Device>::Surface as Surface>::Error); } -impl DrmDevice { - /// Create a new `DrmDevice` from an open DRM node +/// An open drm device +pub trait Device: AsRawFd + DevPath { + /// Associated [`Surface`](trait.Surface.html) of this `Device` type + type Surface: Surface; + + /// Returns the `id` of this device node. + fn device_id(&self) -> dev_t; + + /// Assigns a `DeviceHandler` called during event processing. /// - /// Returns an error if the file is no valid DRM node or context creation was not - /// successful. - pub fn new(dev: A, logger: L) -> Result - where - L: Into>, - { - DrmDevice::new_with_gl_attr( - dev, - GlAttributes { - version: None, - profile: None, - debug: cfg!(debug_assertions), - vsync: true, - }, - logger, - ) - } + /// See [`device_bind`](fn.device_bind.html) and [`DeviceHandler`](trait.DeviceHandler.html) + fn set_handler(&mut self, handler: impl DeviceHandler + 'static); + /// Clear a set [`DeviceHandler`](trait.DeviceHandler.html), if any + fn clear_handler(&mut self); - /// Create a new `DrmDevice` from an open DRM node and given `GlAttributes` + /// Creates a new rendering surface. /// - /// Returns an error if the file is no valid DRM node or context creation was not - /// successful. - pub fn new_with_gl_attr(dev: A, attributes: GlAttributes, logger: L) -> Result - where - L: Into>, - { - let log = ::slog_or_stdlog(logger).new(o!("smithay_module" => "backend_drm")); - - /* GBM will load a dri driver, but even though they need symbols from - * libglapi, in some version of Mesa they are not linked to it. Since - * only the gl-renderer module links to it, these symbols won't be globally available, - * and loading the DRI driver fails. - * Workaround this by dlopen()'ing libglapi with RTLD_GLOBAL. - */ - LOAD.call_once(|| unsafe { - nix::libc::dlopen( - "libglapi.so.0".as_ptr() as *const _, - nix::libc::RTLD_LAZY | nix::libc::RTLD_GLOBAL, - ); - }); - - let device_id = fstat(dev.as_raw_fd()) - .chain_err(|| ErrorKind::UnableToGetDeviceId)? - .st_rdev; - - let mut drm = DrmDevice { - // Open the gbm device from the DRM device and create a context based on that - context: Rc::new( - EGLContext::new( - { - debug!(log, "Creating gbm device"); - let gbm = GbmDevice::new(dev).chain_err(|| ErrorKind::GbmInitFailed)?; - debug!(log, "Creating egl context from gbm device"); - gbm - }, - attributes, - Default::default(), - log.clone(), - ).map_err(Error::from)?, - ), - backends: Rc::new(RefCell::new(HashMap::new())), - device_id, - old_state: HashMap::new(), - active: Arc::new(AtomicBool::new(true)), - privileged: true, - logger: log.clone(), - }; - - info!(log, "DrmDevice initializing"); - - // we want to mode-set, so we better be the master, if we run via a tty session - if drm.set_master().is_err() { - warn!(log, "Unable to become drm master, assuming unprivileged mode"); - drm.privileged = false; - }; - - let res_handles = drm.resource_handles().chain_err(|| { - ErrorKind::DrmDev(format!("Error loading drm resources on {:?}", drm.dev_path())) - })?; - for &con in res_handles.connectors() { - let con_info = connector::Info::load_from_device(&drm, con).chain_err(|| { - ErrorKind::DrmDev(format!("Error loading connector info on {:?}", drm.dev_path())) - })?; - if let Some(enc) = con_info.current_encoder() { - let enc_info = encoder::Info::load_from_device(&drm, enc).chain_err(|| { - ErrorKind::DrmDev(format!("Error loading encoder info on {:?}", drm.dev_path())) - })?; - if let Some(crtc) = enc_info.current_crtc() { - let info = crtc::Info::load_from_device(&drm, crtc).chain_err(|| { - ErrorKind::DrmDev(format!("Error loading crtc info on {:?}", drm.dev_path())) - })?; - drm.old_state - .entry(crtc) - .or_insert((info, Vec::new())) - .1 - .push(con); - } - } - } - - Ok(drm) - } - - /// Create a new backend on a given crtc with a given `Mode` for a given amount - /// of `connectors` (mirroring). + /// Initialization of surfaces happens through the types provided by + /// [`drm-rs`](https://docs.rs/drm/0.3.4/drm/). /// - /// Errors if initialization fails or the mode is not available on all given - /// connectors. - pub fn create_backend( + /// [`crtc`](https://docs.rs/drm/0.3.4/drm/control/crtc/index.html)s represent scanout engines + /// of the device pointer to one framebuffer. + /// Their responsibility is to read the data of the framebuffer and export it into an "Encoder". + /// The number of crtc's represent the number of independant output devices the hardware may handle. + fn create_surface( &mut self, - crtc: crtc::Handle, - mode: Mode, - connectors: I, - ) -> Result> - where - I: Into>, - { - if self.backends.borrow().contains_key(&crtc) { - bail!(ErrorKind::CrtcAlreadyInUse(crtc)); - } + ctrc: crtc::Handle, + ) -> Result::Error>; - if !self.active.load(Ordering::SeqCst) { - bail!(ErrorKind::DeviceInactive); - } + /// Processes any open events of the underlying file descriptor. + /// + /// You should not call this function manually, but rather use + /// [`device_bind`](fn.device_bind.html) to register the device + /// to an [`EventLoop`](https://docs.rs/calloop/0.4.2/calloop/struct.EventLoop.html) + /// to synchronize your rendering to the vblank events of the open crtc's + fn process_events(&mut self); - // check if the given connectors and crtc match - let connectors = connectors.into(); + /// Load the resource from a `Device` given its + /// [`ResourceHandle`](https://docs.rs/drm/0.3.4/drm/control/trait.ResourceHandle.html) + fn resource_info( + &self, + handle: T::Handle, + ) -> Result::Error>; - // check if we have an encoder for every connector and the mode mode - for connector in &connectors { - let con_info = connector::Info::load_from_device(self, *connector).chain_err(|| { - ErrorKind::DrmDev(format!("Error loading connector info on {:?}", self.dev_path())) - })?; - - // check the mode - if !con_info.modes().contains(&mode) { - bail!(ErrorKind::ModeNotSuitable(mode)); - } - - // check for every connector which encoders it does support - let encoders = con_info - .encoders() - .iter() - .map(|encoder| { - encoder::Info::load_from_device(self, *encoder).chain_err(|| { - ErrorKind::DrmDev(format!("Error loading encoder info on {:?}", self.dev_path())) - }) - }).collect::>>()?; - - // and if any encoder supports the selected crtc - let resource_handles = self.resource_handles().chain_err(|| { - ErrorKind::DrmDev(format!("Error loading drm resources on {:?}", self.dev_path())) - })?; - if !encoders - .iter() - .map(|encoder| encoder.possible_crtcs()) - .any(|crtc_list| resource_handles.filter_crtcs(crtc_list).contains(&crtc)) - { - bail!(ErrorKind::NoSuitableEncoder(con_info, crtc)) - } - } - - // configuration is valid, the kernel will figure out the rest - - let logger = self.logger.new(o!("crtc" => format!("{:?}", crtc))); - let backend = DrmBackend::new(self.context.clone(), crtc, mode, connectors, logger)?; - self.backends.borrow_mut().insert(crtc, backend.weak()); - Ok(backend) - } - - /// Returns an internal device id, that is unique per boot per system - pub fn device_id(&self) -> u64 { - self.device_id - } + /// Attempts to acquire a copy of the `Device`'s + /// [`ResourceHandles`](https://docs.rs/drm/0.3.4/drm/control/struct.ResourceHandles.html) + fn resource_handles(&self) -> Result::Error>; } -/// Trait for types representing open devices +/// Marker trait for `Device`s able to provide [`RawSurface`](trait.RawSurface.html)s +pub trait RawDevice: Device::Surface> { + /// Associated [`RawSurface`](trait.RawSurface.html) of this `RawDevice` type + type Surface: RawSurface; +} + +/// An open crtc that can be used for rendering +pub trait Surface { + /// Type repesenting a collection of + /// [`connector`](https://docs.rs/drm/0.3.4/drm/control/connector/index.html)s + /// returned by [`current_connectors`](#method.current_connectors) and + /// [`pending_connectors`](#method.pending_connectors) + type Connectors: IntoIterator; + /// Error type returned by methods of this trait + type Error: Error + Send; + + /// Returns the underlying [`crtc`](https://docs.rs/drm/0.3.4/drm/control/crtc/index.html) of this surface + fn crtc(&self) -> crtc::Handle; + /// Currently used [`connector`](https://docs.rs/drm/0.3.4/drm/control/connector/index.html)s of this `Surface` + fn current_connectors(&self) -> Self::Connectors; + /// Returns the pending [`connector`](https://docs.rs/drm/0.3.4/drm/control/connector/index.html)s + /// used after the next `commit` of this `Surface` + /// + /// *Note*: Only on a [`RawSurface`](trait.RawSurface.html) you may directly trigger + /// a [`commit`](trait.RawSurface.html#method.commit). Other `Surface`s provide their + /// own methods that *may* trigger a commit, you will need to read their docs. + fn pending_connectors(&self) -> Self::Connectors; + /// Tries to add a new [`connector`](https://docs.rs/drm/0.3.4/drm/control/connector/index.html) + /// to be used after the next commit. + /// + /// Fails if the `connector` is not compatible with the underlying [`crtc`](https://docs.rs/drm/0.3.4/drm/control/crtc/index.html) + /// (e.g. no suitable [`encoder`](https://docs.rs/drm/0.3.4/drm/control/encoder/index.html) may be found) + /// or is not compatible with the currently pending + /// [`Mode`](https://docs.rs/drm/0.3.4/drm/control/struct.Mode.html). + fn add_connector(&self, connector: connector::Handle) -> Result<(), Self::Error>; + /// Tries to mark a [`connector`](https://docs.rs/drm/0.3.4/drm/control/connector/index.html) + /// for removal on the next commit. + fn remove_connector(&self, connector: connector::Handle) -> Result<(), Self::Error>; + /// Returns the currently active [`Mode`](https://docs.rs/drm/0.3.4/drm/control/struct.Mode.html) + /// of the underlying [`crtc`](https://docs.rs/drm/0.3.4/drm/control/crtc/index.html) + /// if any. + fn current_mode(&self) -> Option; + /// Returns the currently pending [`Mode`](https://docs.rs/drm/0.3.4/drm/control/struct.Mode.html) + /// to be used after the next commit, if any. + fn pending_mode(&self) -> Option; + /// Tries to set a new [`Mode`](https://docs.rs/drm/0.3.4/drm/control/struct.Mode.html) + /// to be used after the next commit. + /// + /// Fails if the mode is not compatible with the underlying + /// [`crtc`](https://docs.rs/drm/0.3.4/drm/control/crtc/index.html) or any of the + /// pending [`connector`](https://docs.rs/drm/0.3.4/drm/control/connector/index.html)s. + /// + /// *Note*: Only on a [`RawSurface`](trait.RawSurface.html) you may directly trigger + /// a [`commit`](trait.RawSurface.html#method.commit). Other `Surface`s provide their + /// own methods that *may* trigger a commit, you will need to read their docs. + fn use_mode(&self, mode: Option) -> Result<(), Self::Error>; +} + +/// An open bare crtc without any rendering abstractions +pub trait RawSurface: Surface + ControlDevice + BasicDevice { + /// Returns true whenever any state changes are pending to be commited + /// + /// The following functions may trigger a pending commit: + /// - [`add_connector`](trait.Surface.html#method.add_connector) + /// - [`remove_connector`](trait.Surface.html#method.remove_connector) + /// - [`use_mode`](trait.Surface.html#method.use_mode) + fn commit_pending(&self) -> bool; + /// Commit the pending state rendering a given framebuffer. + /// + /// *Note*: This will trigger a full modeset on the underlying device, + /// potentially causing some flickering. Check before performing this + /// operation if a commit really is necessary using [`commit_pending`](#method.commit_pending). + /// + /// This operation is blocking until the crtc is in the desired state. + fn commit(&self, framebuffer: framebuffer::Handle) -> Result<(), ::Error>; + /// Page-flip the underlying [`crtc`](https://docs.rs/drm/0.3.4/drm/control/crtc/index.html) + /// to a new given [`framebuffer`]. + /// + /// This will not cause the crtc to modeset. + /// + /// This operation is not blocking and will produce a `vblank` event once swapping is done. + /// Make sure to [set a `DeviceHandler`](trait.Device.html#method.set_handler) and + /// [register the belonging `Device`](fn.device_bind.html) before to receive the event in time. + fn page_flip(&self, framebuffer: framebuffer::Handle) -> Result<(), SwapBuffersError>; +} + +/// Trait representing open devices that *may* return a `Path` pub trait DevPath { /// Returns the path of the open device if possible fn dev_path(&self) -> Option; @@ -463,243 +225,22 @@ impl DevPath for A { } } -impl PartialEq for DrmDevice { - fn eq(&self, other: &DrmDevice) -> bool { - self.device_id == other.device_id - } -} -impl Eq for DrmDevice {} - -impl Hash for DrmDevice { - fn hash(&self, state: &mut H) { - self.device_id.hash(state); - } -} - -// for users convenience and FdEventSource registering -impl AsRawFd for DrmDevice { - fn as_raw_fd(&self) -> RawFd { - self.context.as_raw_fd() - } -} - -impl BasicDevice for DrmDevice {} -impl ControlDevice for DrmDevice {} - -impl EGLWaylandExtensions for DrmDevice { - fn bind_wl_display(&self, display: &Display) -> EGLResult { - self.context.bind_wl_display(display) - } -} - -impl Drop for DrmDevice { - fn drop(&mut self) { - if Rc::strong_count(&self.context) > 1 { - panic!("Pending DrmBackends. You need to free all backends before the DrmDevice gets destroyed"); - } - for (handle, (info, connectors)) in self.old_state.drain() { - if let Err(err) = crtc::set( - &*self.context, - handle, - info.fb(), - &connectors, - info.position(), - info.mode(), - ) { - error!(self.logger, "Failed to reset crtc ({:?}). Error: {}", handle, err); - } - } - if self.privileged { - if let Err(err) = self.drop_master() { - error!(self.logger, "Failed to drop drm master state. Error: {}", err); - } - } - } -} - -/// Handler for DRM node events +/// Bind a `Device` to an `EventLoop`, /// -/// See module-level documentation for its use -pub trait DrmHandler { - /// The `DrmBackend` of crtc has finished swapping buffers and new frame can now - /// (and should be immediately) be rendered. - fn ready(&mut self, device: &mut DrmDevice, crtc: crtc::Handle, frame: u32, duration: Duration); - /// The `DrmDevice` has thrown an error. - /// - /// The related backends are most likely *not* usable anymore and - /// the whole stack has to be recreated.. - fn error(&mut self, device: &mut DrmDevice, error: DrmError); -} - -/// Bind a `DrmDevice` to an `EventLoop`, -/// -/// This will cause it to recieve events and feed them into an `DrmHandler` -pub fn drm_device_bind( +/// This will cause it to recieve events and feed them into a previously +/// set [`DeviceHandler`](trait.DeviceHandler.html). +pub fn device_bind( handle: &LoopHandle, - device: DrmDevice, - mut handler: H, -) -> ::std::result::Result<(Source>, Rc>>), (IoError, DrmDevice)> + device: D, +) -> ::std::result::Result>>, InsertError>>> where - A: ControlDevice + 'static, - H: DrmHandler + 'static, + D: Device, + Data: 'static, { - let fd = device.as_raw_fd(); - let device = Rc::new(RefCell::new(device)); - - let mut source = Generic::from_raw_fd(fd); + let mut source = Generic::from_fd_source(device); source.set_interest(Ready::readable()); - match handle.insert_source(source, { - let device = device.clone(); - move |_evt, _| { - let mut device = device.borrow_mut(); - process_events(&mut *device, &mut handler); - } - }) { - Ok(source) => Ok((source, device)), - Err(e) => { - let device = Rc::try_unwrap(device).unwrap_or_else(|_| unreachable!()); - Err((e.into(), device.into_inner())) - } - } -} - -fn process_events(device: &mut DrmDevice, handler: &mut H) -where - A: ControlDevice + 'static, - H: DrmHandler + 'static, -{ - match crtc::receive_events(&*device) { - Ok(events) => for event in events { - if let crtc::Event::PageFlip(event) = event { - if device.active.load(Ordering::SeqCst) { - let backends = device.backends.borrow().clone(); - if let Some(backend) = backends.get(&event.crtc).iter().flat_map(|x| x.upgrade()).next() { - // we can now unlock the buffer - backend.unlock_buffer(); - trace!(device.logger, "Handling event for backend {:?}", event.crtc); - // and then call the user to render the next frame - handler.ready(device, event.crtc, event.frame, event.duration); - } else { - device.backends.borrow_mut().remove(&event.crtc); - } - } - } - }, - Err(err) => handler.error(device, err), - } -} - -/// `SessionObserver` linked to the `DrmDevice` it was created from. -pub struct DrmDeviceObserver { - context: Weak, GbmDevice>>, - device_id: dev_t, - backends: Rc>>>>, - old_state: HashMap)>, - active: Arc, - privileged: bool, - logger: ::slog::Logger, -} - -#[cfg(feature = "backend_session")] -impl AsSessionObserver> for DrmDevice { - fn observer(&mut self) -> DrmDeviceObserver { - DrmDeviceObserver { - context: Rc::downgrade(&self.context), - device_id: self.device_id, - backends: self.backends.clone(), - old_state: self.old_state.clone(), - active: self.active.clone(), - privileged: self.privileged, - logger: self.logger.clone(), - } - } -} - -#[cfg(feature = "backend_session")] -impl SessionObserver for DrmDeviceObserver { - fn pause(&mut self, devnum: Option<(u32, u32)>) { - if let Some((major, minor)) = devnum { - if major as u64 != stat::major(self.device_id) || minor as u64 != stat::minor(self.device_id) { - return; - } - } - if let Some(device) = self.context.upgrade() { - for (handle, &(ref info, ref connectors)) in &self.old_state { - if let Err(err) = crtc::set( - &*device, - *handle, - info.fb(), - connectors, - info.position(), - info.mode(), - ) { - error!(self.logger, "Failed to reset crtc ({:?}). Error: {}", handle, err); - } - } - } - self.active.store(false, Ordering::SeqCst); - if self.privileged { - if let Some(device) = self.context.upgrade() { - if let Err(err) = device.drop_master() { - error!(self.logger, "Failed to drop drm master state. Error: {}", err); - } - } - } - } - - fn activate(&mut self, devnum: Option<(u32, u32, Option)>) { - if let Some((major, minor, fd)) = devnum { - if major as u64 != stat::major(self.device_id) || minor as u64 != stat::minor(self.device_id) { - return; - } else if let Some(fd) = fd { - info!(self.logger, "Replacing fd"); - if let Some(device) = self.context.upgrade() { - nix::unistd::dup2(device.as_raw_fd(), fd) - .expect("Failed to replace file descriptor of drm device"); - } - } - } - self.active.store(true, Ordering::SeqCst); - if self.privileged { - if let Some(device) = self.context.upgrade() { - if let Err(err) = device.set_master() { - crit!(self.logger, "Failed to acquire drm master again. Error: {}", err); - } - } - } - let mut crtcs = Vec::new(); - for (crtc, backend) in self.backends.borrow().iter() { - if let Some(backend) = backend.upgrade() { - backend.unlock_buffer(); - if let Err(err) = backend.page_flip(None) { - error!( - self.logger, - "Failed to activate crtc ({:?}) again. Error: {}", crtc, err - ); - } - // reset cursor - { - let &(ref cursor, ref hotspot): &(BufferObject<()>, (u32, u32)) = - unsafe { &*backend.cursor.as_ptr() }; - if crtc::set_cursor2( - &*backend.context, - *crtc, - cursor, - ((*hotspot).0 as i32, (*hotspot).1 as i32), - ).is_err() - { - if let Err(err) = crtc::set_cursor(&*backend.context, *crtc, cursor) { - error!(self.logger, "Failed to reset cursor. Error: {}", err); - } - } - } - } else { - crtcs.push(*crtc); - } - } - for crtc in crtcs { - self.backends.borrow_mut().remove(&crtc); - } - } + handle.insert_source(source, |evt, _| { + evt.source.borrow_mut().0.process_events(); + }) } diff --git a/src/backend/graphics/egl/context.rs b/src/backend/egl/context.rs similarity index 89% rename from src/backend/graphics/egl/context.rs rename to src/backend/egl/context.rs index 4c8f1f8..f0767db 100644 --- a/src/backend/graphics/egl/context.rs +++ b/src/backend/egl/context.rs @@ -1,52 +1,30 @@ //! EGL context related structs -use super::{error::*, ffi, native, EGLSurface, PixelFormat}; -#[cfg(feature = "backend_drm")] -use drm::control::Device as ControlDevice; -#[cfg(feature = "backend_drm")] -use drm::Device as BasicDevice; -#[cfg(feature = "backend_drm")] -use gbm::Device as GbmDevice; +use super::{error::*, ffi, native, EGLSurface}; +use backend::graphics::PixelFormat; use nix::libc::{c_int, c_void}; use slog; -#[cfg(feature = "backend_drm")] -use std::os::unix::io::{AsRawFd, RawFd}; use std::{ + cell::{Ref, RefCell, RefMut}, ffi::{CStr, CString}, marker::PhantomData, - mem, - ops::{Deref, DerefMut}, - ptr, + mem, ptr, rc::Rc, }; /// EGL context for rendering pub struct EGLContext> { - native: N, + native: RefCell, pub(crate) context: Rc, pub(crate) display: Rc, pub(crate) config_id: ffi::egl::types::EGLConfig, pub(crate) surface_attributes: Vec, pixel_format: PixelFormat, pub(crate) wl_drm_support: bool, - pub(crate) egl_to_texture_support: bool, logger: slog::Logger, _backend: PhantomData, } -impl> Deref for EGLContext { - type Target = N; - fn deref(&self) -> &N { - &self.native - } -} - -impl> DerefMut for EGLContext { - fn deref_mut(&mut self) -> &mut N { - &mut self.native - } -} - impl> EGLContext { /// Create a new `EGLContext` from a given `NativeDisplay` pub fn new( @@ -60,25 +38,17 @@ impl> EGLContext { { let log = ::slog_or_stdlog(logger.into()).new(o!("smithay_module" => "renderer_egl")); let ptr = native.ptr()?; - let ( - context, - display, - config_id, - surface_attributes, - pixel_format, - wl_drm_support, - egl_to_texture_support, - ) = unsafe { EGLContext::::new_internal(ptr, attributes, reqs, log.clone()) }?; + let (context, display, config_id, surface_attributes, pixel_format, wl_drm_support) = + unsafe { EGLContext::::new_internal(ptr, attributes, reqs, log.clone()) }?; Ok(EGLContext { - native, + native: RefCell::new(native), context, display, config_id, surface_attributes, pixel_format, wl_drm_support, - egl_to_texture_support, logger: log, _backend: PhantomData, }) @@ -96,7 +66,6 @@ impl> EGLContext { Vec, PixelFormat, bool, - bool, )> { // If no version is given, try OpenGLES 3.0, if available, // fallback to 2.0 otherwise @@ -136,6 +105,7 @@ impl> EGLContext { { f }; + ffi::egl::load_with(|sym| { let name = CString::new(sym).unwrap(); let symbol = ffi::egl::LIB.get::<*mut c_void>(name.as_bytes()); @@ -153,7 +123,6 @@ impl> EGLContext { ffi::egl::BindWaylandDisplayWL::load_with(&proc_address); ffi::egl::UnbindWaylandDisplayWL::load_with(&proc_address); ffi::egl::QueryWaylandBufferWL::load_with(&proc_address); - ffi::gl::load_with(&proc_address); }); // the first step is to query the list of extensions without any display, if supported @@ -435,17 +404,6 @@ impl> EGLContext { // make current and get list of gl extensions ffi::egl::MakeCurrent(display as *const _, ptr::null(), ptr::null(), context as *const _); - // the list of gl extensions supported by the context - let gl_extensions = { - let data = CStr::from_ptr(ffi::gl::GetString(ffi::gl::EXTENSIONS) as *const _) - .to_bytes() - .to_vec(); - let list = String::from_utf8(data).unwrap(); - list.split(' ').map(|e| e.to_string()).collect::>() - }; - - info!(log, "GL Extensions: {:?}", gl_extensions); - Ok(( Rc::new(context as *const _), Rc::new(display as *const _), @@ -453,25 +411,21 @@ impl> EGLContext { surface_attributes, desc, extensions.iter().any(|s| *s == "EGL_WL_bind_wayland_display"), - gl_extensions - .iter() - .any(|s| *s == "GL_OES_EGL_image" || *s == "GL_OES_EGL_image_base"), )) } /// Creates a surface for rendering pub fn create_surface(&self, args: N::Arguments) -> Result> { trace!(self.logger, "Creating EGL window surface."); - let res = EGLSurface::new( - self, - self.native - .create_surface(args) - .chain_err(|| ErrorKind::SurfaceCreationFailed)?, - ); - if res.is_ok() { + let surface = self + .native + .borrow_mut() + .create_surface(args) + .chain_err(|| ErrorKind::SurfaceCreationFailed)?; + EGLSurface::new(self, surface).map(|x| { debug!(self.logger, "EGL surface successfully created"); - } - res + x + }) } /// Returns the address of an OpenGL function. @@ -492,6 +446,25 @@ impl> EGLContext { pub fn get_pixel_format(&self) -> PixelFormat { self.pixel_format } + + /// Borrow the underlying native display. + /// + /// This follows the same semantics as `std::cell:RefCell`. + /// Multiple read-only borrows are possible. Borrowing the + /// backend while there is a mutable reference will panic. + pub fn borrow(&self) -> Ref { + self.native.borrow() + } + + /// Borrow the underlying native display mutably. + /// + /// This follows the same semantics as `std::cell:RefCell`. + /// Holding any other borrow while trying to borrow the backend + /// mutably will panic. Note that EGL will borrow the display + /// mutably during surface creation. + pub fn borrow_mut(&self) -> RefMut { + self.native.borrow_mut() + } } unsafe impl + Send> Send for EGLContext {} @@ -508,18 +481,6 @@ impl> Drop for EGLContext } } -#[cfg(feature = "backend_drm")] -impl AsRawFd for EGLContext, GbmDevice> { - fn as_raw_fd(&self) -> RawFd { - self.native.as_raw_fd() - } -} - -#[cfg(feature = "backend_drm")] -impl BasicDevice for EGLContext, GbmDevice> {} -#[cfg(feature = "backend_drm")] -impl ControlDevice for EGLContext, GbmDevice> {} - /// Attributes to use when creating an OpenGL context. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct GlAttributes { diff --git a/src/backend/graphics/egl/error.rs b/src/backend/egl/error.rs similarity index 100% rename from src/backend/graphics/egl/error.rs rename to src/backend/egl/error.rs diff --git a/src/backend/graphics/egl/ffi.rs b/src/backend/egl/ffi.rs similarity index 97% rename from src/backend/graphics/egl/ffi.rs rename to src/backend/egl/ffi.rs index f5ace9f..b43d1fe 100644 --- a/src/backend/graphics/egl/ffi.rs +++ b/src/backend/egl/ffi.rs @@ -13,11 +13,6 @@ pub type NativeDisplayType = *const c_void; pub type NativePixmapType = *const c_void; pub type NativeWindowType = *const c_void; -#[cfg_attr(feature = "cargo-clippy", allow(clippy))] -pub mod gl { - include!(concat!(env!("OUT_DIR"), "/gl_bindings.rs")); -} - #[cfg_attr(feature = "cargo-clippy", allow(clippy))] pub mod egl { use super::*; diff --git a/src/backend/graphics/egl/wayland.rs b/src/backend/egl/mod.rs similarity index 75% rename from src/backend/graphics/egl/wayland.rs rename to src/backend/egl/mod.rs index 53dcb46..c5d29bd 100644 --- a/src/backend/graphics/egl/wayland.rs +++ b/src/backend/egl/mod.rs @@ -1,3 +1,10 @@ +//! Common traits and types for egl rendering +//! +//! Large parts of this module are taken from +//! https://github.com/tomaka/glutin/tree/044e651edf67a2029eecc650dd42546af1501414/src/api/egl/ +//! +//! It therefore falls under glutin's Apache 2.0 license +//! (see https://github.com/tomaka/glutin/tree/044e651edf67a2029eecc650dd42546af1501414/LICENSE) //! Wayland specific EGL functionality - EGL based `WlBuffer`s. //! //! The types of this module can be used to initialize hardware acceleration rendering @@ -10,13 +17,11 @@ //! You may then use the resulting `EGLDisplay` to receive `EGLImages` of an EGL-based `WlBuffer` //! for rendering. -use backend::graphics::egl::{ - error::*, - ffi::{self, egl::types::EGLImage}, - native, EGLContext, EglExtensionNotSupportedError, -}; +#[cfg(feature = "renderer_gl")] +use backend::graphics::gl::ffi as gl_ffi; use nix::libc::c_uint; use std::{ + ffi::CStr, fmt, rc::{Rc, Weak}, }; @@ -24,8 +29,47 @@ use wayland_server::{ protocol::wl_buffer::{self, WlBuffer}, Display, Resource, }; +#[cfg(feature = "native_lib")] use wayland_sys::server::wl_display; +pub mod context; +pub use self::context::EGLContext; +pub mod error; +use self::error::*; + +#[allow(non_camel_case_types, dead_code, unused_mut, non_upper_case_globals)] +pub mod ffi; +use self::ffi::egl::types::EGLImage; + +pub mod native; +pub mod surface; +pub use self::surface::EGLSurface; + +/// Error that can happen on optional EGL features +#[derive(Debug, Clone, PartialEq)] +pub struct EglExtensionNotSupportedError(&'static [&'static str]); + +impl fmt::Display for EglExtensionNotSupportedError { + fn fmt(&self, formatter: &mut fmt::Formatter) -> ::std::result::Result<(), fmt::Error> { + write!( + formatter, + "None of the following EGL extensions is supported by the underlying EGL implementation, + at least one is required: {:?}", + self.0 + ) + } +} + +impl ::std::error::Error for EglExtensionNotSupportedError { + fn description(&self) -> &str { + "The required EGL extension is not supported by the underlying EGL implementation" + } + + fn cause(&self) -> Option<&::std::error::Error> { + None + } +} + /// Error that can occur when accessing an EGL buffer pub enum BufferAccessError { /// The corresponding Context is not alive anymore @@ -104,6 +148,8 @@ pub enum TextureCreationError { /// application on sleep and wakes it up later. However any OpenGL implementation /// can theoretically lose the context at any time. ContextLost, + /// Required OpenGL Extension for texture creation is missing + GLExtensionNotSupported(&'static str), /// Failed to bind the `EGLImage` to the given texture /// /// The given argument is the GL error code @@ -116,6 +162,9 @@ impl fmt::Display for TextureCreationError { match *self { TextureCreationError::ContextLost => write!(formatter, "{}", self.description()), TextureCreationError::PlaneIndexOutOfBounds => write!(formatter, "{}", self.description()), + TextureCreationError::GLExtensionNotSupported(ext) => { + write!(formatter, "{}: {:}", self.description(), ext) + } TextureCreationError::TextureBindingFailed(code) => { write!(formatter, "{}. Gl error code: {:?}", self.description(), code) } @@ -128,6 +177,9 @@ impl ::std::error::Error for TextureCreationError { match *self { TextureCreationError::ContextLost => "The context has been lost, it needs to be recreated", TextureCreationError::PlaneIndexOutOfBounds => "This buffer is not managed by EGL", + TextureCreationError::GLExtensionNotSupported(_) => { + "Required OpenGL Extension for texture creation is missing" + } TextureCreationError::TextureBindingFailed(_) => "Failed to create EGLImages from the buffer", } } @@ -180,6 +232,10 @@ pub struct EGLImages { pub format: Format, images: Vec, buffer: Resource, + #[cfg(feature = "renderer_gl")] + gl: gl_ffi::Gles2, + #[cfg(feature = "renderer_gl")] + egl_to_texture_support: bool, } impl EGLImages { @@ -195,17 +251,22 @@ impl EGLImages { /// # Unsafety /// /// The given `tex_id` needs to be a valid GL texture otherwise undefined behavior might occur. + #[cfg(feature = "renderer_gl")] pub unsafe fn bind_to_texture( &self, plane: usize, tex_id: c_uint, ) -> ::std::result::Result<(), TextureCreationError> { if self.display.upgrade().is_some() { + if !self.egl_to_texture_support { + return Err(TextureCreationError::GLExtensionNotSupported("GL_OES_EGL_image")); + } + let mut old_tex_id: i32 = 0; - ffi::gl::GetIntegerv(ffi::gl::TEXTURE_BINDING_2D, &mut old_tex_id); - ffi::gl::BindTexture(ffi::gl::TEXTURE_2D, tex_id); - ffi::gl::EGLImageTargetTexture2DOES( - ffi::gl::TEXTURE_2D, + self.gl.GetIntegerv(gl_ffi::TEXTURE_BINDING_2D, &mut old_tex_id); + self.gl.BindTexture(gl_ffi::TEXTURE_2D, tex_id); + self.gl.EGLImageTargetTexture2DOES( + gl_ffi::TEXTURE_2D, *self .images .get(plane) @@ -215,7 +276,7 @@ impl EGLImages { ffi::egl::SUCCESS => Ok(()), err => Err(TextureCreationError::TextureBindingFailed(err)), }; - ffi::gl::BindTexture(ffi::gl::TEXTURE_2D, old_tex_id as u32); + self.gl.BindTexture(gl_ffi::TEXTURE_2D, old_tex_id as u32); res } else { Err(TextureCreationError::ContextLost) @@ -238,7 +299,8 @@ impl Drop for EGLImages { /// Trait any backend type may implement that allows binding a `wayland_server::Display` /// to create an `EGLDisplay` for EGL-based `WlBuffer`s. -pub trait EGLWaylandExtensions { +#[cfg(feature = "native_lib")] +pub trait EGLGraphicsBackend { /// Binds this EGL context to the given Wayland display. /// /// This will allow clients to utilize EGL to create hardware-accelerated @@ -257,15 +319,42 @@ pub trait EGLWaylandExtensions { /// Type to receive `EGLImages` for EGL-based `WlBuffer`s. /// -/// Can be created by using `EGLWaylandExtensions::bind_wl_display`. -pub struct EGLDisplay(Weak, *mut wl_display); +/// Can be created by using `EGLGraphicsBackend::bind_wl_display`. +#[cfg(feature = "native_lib")] +pub struct EGLDisplay { + egl: Weak, + wayland: *mut wl_display, + #[cfg(feature = "renderer_gl")] + gl: gl_ffi::Gles2, + #[cfg(feature = "renderer_gl")] + egl_to_texture_support: bool, +} +#[cfg(feature = "native_lib")] impl EGLDisplay { fn new>( context: &EGLContext, display: *mut wl_display, ) -> EGLDisplay { - EGLDisplay(Rc::downgrade(&context.display), display) + #[cfg(feature = "renderer_gl")] + let gl = gl_ffi::Gles2::load_with(|s| unsafe { context.get_proc_address(s) as *const _ }); + + EGLDisplay { + egl: Rc::downgrade(&context.display), + wayland: display, + #[cfg(feature = "renderer_gl")] + egl_to_texture_support: { + // the list of gl extensions supported by the context + let data = unsafe { CStr::from_ptr(gl.GetString(gl_ffi::EXTENSIONS) as *const _) } + .to_bytes() + .to_vec(); + let list = String::from_utf8(data).unwrap(); + list.split(' ') + .any(|s| s == "GL_OES_EGL_image" || s == "GL_OES_EGL_image_base") + }, + #[cfg(feature = "renderer_gl")] + gl, + } } /// Try to receive `EGLImages` from a given `WlBuffer`. @@ -277,7 +366,7 @@ impl EGLDisplay { &self, buffer: Resource, ) -> ::std::result::Result { - if let Some(display) = self.0.upgrade() { + if let Some(display) = self.egl.upgrade() { let mut format: i32 = 0; if unsafe { ffi::egl::QueryWaylandBufferWL( @@ -368,6 +457,10 @@ impl EGLDisplay { format, images, buffer, + #[cfg(feature = "renderer_gl")] + gl: self.gl.clone(), + #[cfg(feature = "renderer_gl")] + egl_to_texture_support: self.egl_to_texture_support, }) } else { Err(BufferAccessError::ContextLost) @@ -375,34 +468,34 @@ impl EGLDisplay { } } +#[cfg(feature = "native_lib")] impl Drop for EGLDisplay { fn drop(&mut self) { - if let Some(display) = self.0.upgrade() { - if !self.1.is_null() { + if let Some(display) = self.egl.upgrade() { + if !self.wayland.is_null() { unsafe { - ffi::egl::UnbindWaylandDisplayWL(*display, self.1 as *mut _); + ffi::egl::UnbindWaylandDisplayWL(*display, self.wayland as *mut _); } } } } } -impl EGLWaylandExtensions for Rc { +#[cfg(feature = "native_lib")] +impl EGLGraphicsBackend for Rc { fn bind_wl_display(&self, display: &Display) -> Result { (**self).bind_wl_display(display) } } -impl> EGLWaylandExtensions for EGLContext { +#[cfg(feature = "native_lib")] +impl> EGLGraphicsBackend for EGLContext { fn bind_wl_display(&self, display: &Display) -> Result { if !self.wl_drm_support { bail!(ErrorKind::EglExtensionNotSupported(&[ "EGL_WL_bind_wayland_display" ])); } - if !self.egl_to_texture_support { - bail!(ErrorKind::EglExtensionNotSupported(&["GL_OES_EGL_image"])); - } let res = unsafe { ffi::egl::BindWaylandDisplayWL(*self.display, display.c_ptr() as *mut _) }; if res == 0 { bail!(ErrorKind::OtherEGLDisplayAlreadyBound); diff --git a/src/backend/graphics/egl/native.rs b/src/backend/egl/native.rs similarity index 64% rename from src/backend/graphics/egl/native.rs rename to src/backend/egl/native.rs index 1089804..1edc445 100644 --- a/src/backend/graphics/egl/native.rs +++ b/src/backend/egl/native.rs @@ -1,16 +1,11 @@ //! Type safe native types for safe context/surface creation use super::{error::*, ffi}; -#[cfg(feature = "backend_drm")] -use backend::drm::error::{Error as DrmError, ErrorKind as DrmErrorKind, Result as DrmResult}; -#[cfg(feature = "backend_drm")] -use gbm::{AsRaw, BufferObjectFlags, Device as GbmDevice, Format as GbmFormat, Surface as GbmSurface}; -#[cfg(feature = "backend_drm")] -use std::marker::PhantomData; -#[cfg(feature = "backend_drm")] -use std::os::unix::io::AsRawFd; -#[cfg(any(feature = "backend_drm", feature = "backend_winit"))] +use backend::graphics::SwapBuffersError; + +#[cfg(feature = "backend_winit")] use std::ptr; + #[cfg(feature = "backend_winit")] use wayland_client::egl as wegl; #[cfg(feature = "backend_winit")] @@ -95,38 +90,6 @@ impl Backend for X11 { } } } -#[cfg(feature = "backend_drm")] -/// Gbm backend type -pub struct Gbm { - _userdata: PhantomData, -} -#[cfg(feature = "backend_drm")] -impl Backend for Gbm { - type Surface = GbmSurface; - - unsafe fn get_display( - display: ffi::NativeDisplayType, - has_dp_extension: F, - log: ::slog::Logger, - ) -> ffi::egl::types::EGLDisplay - where - F: Fn(&str) -> bool, - { - if has_dp_extension("EGL_KHR_platform_gbm") && ffi::egl::GetPlatformDisplay::is_loaded() { - trace!(log, "EGL Display Initialization via EGL_KHR_platform_gbm"); - ffi::egl::GetPlatformDisplay(ffi::egl::PLATFORM_GBM_KHR, display as *mut _, ptr::null()) - } else if has_dp_extension("EGL_MESA_platform_gbm") && ffi::egl::GetPlatformDisplayEXT::is_loaded() { - trace!(log, "EGL Display Initialization via EGL_MESA_platform_gbm"); - ffi::egl::GetPlatformDisplayEXT(ffi::egl::PLATFORM_GBM_MESA, display as *mut _, ptr::null()) - } else if has_dp_extension("EGL_MESA_platform_gbm") && ffi::egl::GetPlatformDisplay::is_loaded() { - trace!(log, "EGL Display Initialization via EGL_MESA_platform_gbm"); - ffi::egl::GetPlatformDisplay(ffi::egl::PLATFORM_GBM_MESA, display as *mut _, ptr::null()) - } else { - trace!(log, "Default EGL Display Initialization via GetDisplay"); - ffi::egl::GetDisplay(display as *mut _) - } - } -} /// Trait for types returning Surfaces which can be used to initialize `EGLSurface`s /// @@ -144,7 +107,7 @@ pub unsafe trait NativeDisplay { /// Return a raw pointer EGL will accept for context creation. fn ptr(&self) -> Result; /// Create a surface - fn create_surface(&self, args: Self::Arguments) -> ::std::result::Result; + fn create_surface(&mut self, args: Self::Arguments) -> ::std::result::Result; } #[cfg(feature = "backend_winit")] @@ -162,7 +125,7 @@ unsafe impl NativeDisplay for WinitWindow { .ok_or(ErrorKind::NonMatchingBackend("X11").into()) } - fn create_surface(&self, _args: ()) -> Result { + fn create_surface(&mut self, _args: ()) -> Result { self.get_xlib_window() .map(XlibWindow) .ok_or(ErrorKind::NonMatchingBackend("X11").into()) @@ -184,7 +147,7 @@ unsafe impl NativeDisplay for WinitWindow { .ok_or(ErrorKind::NonMatchingBackend("Wayland").into()) } - fn create_surface(&self, _args: ()) -> Result { + fn create_surface(&mut self, _args: ()) -> Result { if let Some(surface) = self.get_wayland_surface() { let size = self.get_inner_size().unwrap(); Ok(unsafe { @@ -196,41 +159,7 @@ unsafe impl NativeDisplay for WinitWindow { } } -#[cfg(feature = "backend_drm")] -/// Arguments necessary to construct a `GbmSurface` -pub struct GbmSurfaceArguments { - /// Size of the surface - pub size: (u32, u32), - /// Pixel format of the surface - pub format: GbmFormat, - /// Flags for surface creation - pub flags: BufferObjectFlags, -} - -#[cfg(feature = "backend_drm")] -unsafe impl NativeDisplay> for GbmDevice { - type Arguments = GbmSurfaceArguments; - type Error = DrmError; - - fn is_backend(&self) -> bool { - true - } - - fn ptr(&self) -> Result { - Ok(self.as_raw() as *const _) - } - - fn create_surface(&self, args: GbmSurfaceArguments) -> DrmResult> { - use backend::drm::error::ResultExt as DrmResultExt; - - DrmResultExt::chain_err( - GbmDevice::create_surface(self, args.size.0, args.size.1, args.format, args.flags), - || DrmErrorKind::GbmInitFailed, - ) - } -} - -/// Trait for types returning valid surface pointers for initializing EGL +/// Trait for types returning valid surface pointers for initializing egl /// /// ## Unsafety /// @@ -238,6 +167,32 @@ unsafe impl NativeDisplay> for GbmDevic pub unsafe trait NativeSurface { /// Return a raw pointer egl will accept for surface creation. fn ptr(&self) -> ffi::NativeWindowType; + + /// Will be called to check if any internal resources will need + /// to be recreated. Old resources must be used until `recreate` + /// was called. + /// + /// Only needs to be recreated, if this shall sometimes 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) -> bool { + true + } + + /// Adds additional semantics when calling EGLSurface::swap_buffers + /// + /// Only implement if required by the backend. + fn swap_buffers(&self) -> ::std::result::Result<(), SwapBuffersError> { + Ok(()) + } } #[cfg(feature = "backend_winit")] @@ -253,10 +208,3 @@ unsafe impl NativeSurface for wegl::WlEglSurface { self.ptr() as *const _ } } - -#[cfg(feature = "backend_drm")] -unsafe impl NativeSurface for GbmSurface { - fn ptr(&self) -> ffi::NativeWindowType { - self.as_raw() as *const _ - } -} diff --git a/src/backend/graphics/egl/surface.rs b/src/backend/egl/surface.rs similarity index 63% rename from src/backend/graphics/egl/surface.rs rename to src/backend/egl/surface.rs index fce3cd4..df3ac31 100644 --- a/src/backend/graphics/egl/surface.rs +++ b/src/backend/egl/surface.rs @@ -1,7 +1,10 @@ //! EGL surface related structs -use super::{error::*, ffi, native, EGLContext, SwapBuffersError}; +use super::{error::*, ffi, native, EGLContext}; +use backend::graphics::SwapBuffersError; +use nix::libc::c_int; use std::{ + cell::Cell, ops::{Deref, DerefMut}, rc::{Rc, Weak}, }; @@ -11,7 +14,9 @@ pub struct EGLSurface { context: Weak, display: Weak, native: N, - surface: ffi::egl::types::EGLSurface, + surface: Cell, + config_id: ffi::egl::types::EGLConfig, + surface_attributes: Vec, } impl Deref for EGLSurface { @@ -49,26 +54,48 @@ impl EGLSurface { context: Rc::downgrade(&context.context), display: Rc::downgrade(&context.display), native, - surface, + surface: Cell::new(surface), + config_id: context.config_id, + surface_attributes: context.surface_attributes.clone(), }) } /// Swaps buffers at the end of a frame. pub fn swap_buffers(&self) -> ::std::result::Result<(), SwapBuffersError> { - if let Some(display) = self.display.upgrade() { - let ret = unsafe { ffi::egl::SwapBuffers((*display) as *const _, self.surface as *const _) }; + let surface = self.surface.get(); - if ret == 0 { - match unsafe { ffi::egl::GetError() } as u32 { - ffi::egl::CONTEXT_LOST => Err(SwapBuffersError::ContextLost), - err => Err(SwapBuffersError::Unknown(err)), + if !surface.is_null() { + if let Some(display) = self.display.upgrade() { + let ret = unsafe { ffi::egl::SwapBuffers((*display) as *const _, surface as *const _) }; + + if ret == 0 { + match unsafe { ffi::egl::GetError() } as u32 { + ffi::egl::CONTEXT_LOST => return Err(SwapBuffersError::ContextLost), + err => return Err(SwapBuffersError::Unknown(err)), + }; + } else { + self.native.swap_buffers()?; } } else { - Ok(()) + return Err(SwapBuffersError::ContextLost); + } + }; + + if self.native.needs_recreation() || surface.is_null() { + if let Some(display) = self.display.upgrade() { + self.native.recreate(); + self.surface.set(unsafe { + ffi::egl::CreateWindowSurface( + *display, + self.config_id, + self.native.ptr(), + self.surface_attributes.as_ptr(), + ) + }); } - } else { - Err(SwapBuffersError::ContextLost) } + + Ok(()) } /// Makes the OpenGL context the current context in the current thread. @@ -81,8 +108,8 @@ impl EGLSurface { if let (Some(display), Some(context)) = (self.display.upgrade(), self.context.upgrade()) { let ret = ffi::egl::MakeCurrent( (*display) as *const _, - self.surface as *const _, - self.surface as *const _, + self.surface.get() as *const _, + self.surface.get() as *const _, (*context) as *const _, ); @@ -103,8 +130,8 @@ impl EGLSurface { pub fn is_current(&self) -> bool { if self.context.upgrade().is_some() { unsafe { - ffi::egl::GetCurrentSurface(ffi::egl::DRAW as _) == self.surface as *const _ - && ffi::egl::GetCurrentSurface(ffi::egl::READ as _) == self.surface as *const _ + ffi::egl::GetCurrentSurface(ffi::egl::DRAW as _) == self.surface.get() as *const _ + && ffi::egl::GetCurrentSurface(ffi::egl::READ as _) == self.surface.get() as *const _ } } else { false @@ -116,7 +143,7 @@ impl Drop for EGLSurface { fn drop(&mut self) { if let Some(display) = self.display.upgrade() { unsafe { - ffi::egl::DestroySurface((*display) as *const _, self.surface as *const _); + ffi::egl::DestroySurface((*display) as *const _, self.surface.get() as *const _); } } } diff --git a/src/backend/graphics/cursor.rs b/src/backend/graphics/cursor.rs new file mode 100644 index 0000000..7140bd5 --- /dev/null +++ b/src/backend/graphics/cursor.rs @@ -0,0 +1,34 @@ +/// Functions to render cursors on any graphics backend independently from it's rendering techique. +pub trait CursorBackend<'a> { + /// Format representing the image drawn for the cursor. + type CursorFormat: 'a; + + /// Error the underlying backend throws if operations fail + type Error; + + /// Sets the cursor position and therefore updates the drawn cursors position. + /// Useful as well for e.g. pointer wrapping. + /// + /// Not guaranteed to be supported on every backend. The result usually + /// depends on the backend, the cursor might be "owned" by another more priviledged + /// compositor (running nested). + /// + /// In these cases setting the position is actually not required, as movement is done + /// by the higher compositor and not by the backend. It is still good practice to update + /// the position after every recieved event, but don't rely on pointer wrapping working. + /// + fn set_cursor_position(&self, x: u32, y: u32) -> Result<(), Self::Error>; + + /// Set the cursor drawn on the `CursorBackend`. + /// + /// The format is entirely dictated by the concrete implementation and might range + /// from raw image buffers over a fixed list of possible cursor types to simply the + /// void type () to represent no possible customization of the cursor itself. + fn set_cursor_representation<'b>( + &'b self, + cursor: Self::CursorFormat, + hotspot: (u32, u32), + ) -> Result<(), Self::Error> + where + 'a: 'b; +} diff --git a/src/backend/graphics/egl/mod.rs b/src/backend/graphics/egl/mod.rs deleted file mode 100644 index 2d04696..0000000 --- a/src/backend/graphics/egl/mod.rs +++ /dev/null @@ -1,155 +0,0 @@ -//! Common traits and types for EGL rendering - -// Large parts of this module are taken from -// https://github.com/tomaka/glutin/tree/044e651edf67a2029eecc650dd42546af1501414/src/api/egl/ -// -// It therefore falls under glutin's Apache 2.0 license -// (see https://github.com/tomaka/glutin/tree/044e651edf67a2029eecc650dd42546af1501414/LICENSE) - -use super::GraphicsBackend; -use nix::libc::c_void; -use std::fmt; - -pub mod context; -pub use self::context::EGLContext; -pub mod error; -#[allow( - non_camel_case_types, - dead_code, - unused_mut, - non_upper_case_globals -)] -pub mod ffi; -pub mod native; -pub mod surface; -pub use self::surface::EGLSurface; -pub mod wayland; -pub use self::wayland::{BufferAccessError, EGLImages, EGLWaylandExtensions}; - -/// Error that can happen when swapping buffers. -#[derive(Debug, Clone, PartialEq)] -pub enum SwapBuffersError { - /// The OpenGL context has been lost and needs to be recreated. - /// - /// All the objects associated to it (textures, buffers, programs, etc.) - /// need to be recreated from scratch. - /// - /// Operations will have no effect. Functions that read textures, buffers, etc. - /// from OpenGL will return uninitialized data instead. - /// - /// A context loss usually happens on mobile devices when the user puts the - /// application to sleep and wakes it up later. However any OpenGL implementation - /// can theoretically lose the context at any time. - ContextLost, - /// The buffers have already been swapped. - /// - /// This error can be returned when `swap_buffers` has been called multiple times - /// without any modification in between. - AlreadySwapped, - /// Unknown GL error - Unknown(u32), -} - -impl fmt::Display for SwapBuffersError { - fn fmt(&self, formatter: &mut fmt::Formatter) -> ::std::result::Result<(), fmt::Error> { - use std::error::Error; - write!(formatter, "{}", self.description()) - } -} - -impl ::std::error::Error for SwapBuffersError { - fn description(&self) -> &str { - match *self { - SwapBuffersError::ContextLost => "The context has been lost, it needs to be recreated", - SwapBuffersError::AlreadySwapped => { - "Buffers are already swapped, swap_buffers was called too many times" - } - SwapBuffersError::Unknown(_) => "Unknown Open GL error occurred", - } - } - - fn cause(&self) -> Option<&::std::error::Error> { - None - } -} - -/// Error that can happen on optional EGL features -#[derive(Debug, Clone, PartialEq)] -pub struct EglExtensionNotSupportedError(&'static [&'static str]); - -impl fmt::Display for EglExtensionNotSupportedError { - fn fmt(&self, formatter: &mut fmt::Formatter) -> ::std::result::Result<(), fmt::Error> { - write!( - formatter, - "None of the following EGL extensions is supported by the underlying EGL implementation, - at least one is required: {:?}", - self.0 - ) - } -} - -impl ::std::error::Error for EglExtensionNotSupportedError { - fn description(&self) -> &str { - "The required EGL extension is not supported by the underlying EGL implementation" - } - - fn cause(&self) -> Option<&::std::error::Error> { - None - } -} - -/// Describes the pixel format of the main framebuffer -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct PixelFormat { - /// is the format hardware accelerated - pub hardware_accelerated: bool, - /// number of bits used for colors - pub color_bits: u8, - /// number of bits used for alpha channel - pub alpha_bits: u8, - /// number of bits used for depth channel - pub depth_bits: u8, - /// number of bits used for stencil buffer - pub stencil_bits: u8, - /// is stereoscopy enabled - pub stereoscopy: bool, - /// is double buffering enabled - pub double_buffer: bool, - /// number of samples used for multisampling if enabled - pub multisampling: Option, - /// is srgb enabled - pub srgb: bool, -} - -/// Trait that describes objects that have an OpenGL context -/// and can be used to render upon -pub trait EGLGraphicsBackend: GraphicsBackend { - /// Swaps buffers at the end of a frame. - fn swap_buffers(&self) -> ::std::result::Result<(), SwapBuffersError>; - - /// Returns the address of an OpenGL function. - /// - /// Supposes that the context has been made current before this function is called. - unsafe fn get_proc_address(&self, symbol: &str) -> *const c_void; - - /// Returns the dimensions of the window, or screen, etc in points. - /// - /// That are the scaled pixels of the underlying graphics backend. - /// For nested compositors this will respect the scaling of the root compositor. - /// For drawing directly onto hardware this unit will be equal to actual pixels. - fn get_framebuffer_dimensions(&self) -> (u32, u32); - - /// Returns `true` if the OpenGL context is the current one in the thread. - fn is_current(&self) -> bool; - - /// Makes the OpenGL context the current context in the current thread. - /// - /// # Unsafety - /// - /// This function is marked unsafe, because the context cannot be made current - /// on multiple threads. - unsafe fn make_current(&self) -> ::std::result::Result<(), SwapBuffersError>; - - /// Returns the pixel format of the main framebuffer of the context. - fn get_pixel_format(&self) -> PixelFormat; -} diff --git a/src/backend/graphics/errors.rs b/src/backend/graphics/errors.rs new file mode 100644 index 0000000..fa9e9a7 --- /dev/null +++ b/src/backend/graphics/errors.rs @@ -0,0 +1,45 @@ +use std::error::Error; +use std::fmt; + +/// Error that can happen when swapping buffers. +#[derive(Debug, Clone, PartialEq)] +pub enum SwapBuffersError { + /// The corresponding context has been lost and needs to be recreated. + /// + /// All the objects associated to it (textures, buffers, programs, etc.) + /// need to be recreated from scratch. + /// + /// Operations will have no effect. Functions that read textures, buffers, etc. + /// will return uninitialized data instead. + ContextLost, + /// The buffers have already been swapped. + /// + /// This error can be returned when `swap_buffers` has been called multiple times + /// without any modification in between. + AlreadySwapped, + /// Unknown error + Unknown(u32), +} + +impl fmt::Display for SwapBuffersError { + fn fmt(&self, formatter: &mut fmt::Formatter) -> Result<(), fmt::Error> { + use std::error::Error; + write!(formatter, "{}", self.description()) + } +} + +impl Error for SwapBuffersError { + fn description(&self) -> &str { + match *self { + SwapBuffersError::ContextLost => "The context has been lost, it needs to be recreated", + SwapBuffersError::AlreadySwapped => { + "Buffers are already swapped, swap_buffers was called too many times" + } + SwapBuffersError::Unknown(_) => "Unknown error occurred", + } + } + + fn cause(&self) -> Option<&Error> { + None + } +} diff --git a/src/backend/graphics/format.rs b/src/backend/graphics/format.rs new file mode 100644 index 0000000..96bef69 --- /dev/null +++ b/src/backend/graphics/format.rs @@ -0,0 +1,22 @@ +/// Describes the pixel format of a framebuffer +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct PixelFormat { + /// is the format hardware accelerated + pub hardware_accelerated: bool, + /// number of bits used for colors + pub color_bits: u8, + /// number of bits used for alpha channel + pub alpha_bits: u8, + /// number of bits used for depth channel + pub depth_bits: u8, + /// number of bits used for stencil buffer + pub stencil_bits: u8, + /// is stereoscopy enabled + pub stereoscopy: bool, + /// is double buffering enabled + pub double_buffer: bool, + /// number of samples used for multisampling if enabled + pub multisampling: Option, + /// is srgb enabled + pub srgb: bool, +} diff --git a/src/backend/graphics/gl.rs b/src/backend/graphics/gl.rs new file mode 100644 index 0000000..b50e4ba --- /dev/null +++ b/src/backend/graphics/gl.rs @@ -0,0 +1,55 @@ +//! Gl rendering types + +use nix::libc::c_void; + +use super::{PixelFormat, SwapBuffersError}; + +#[cfg_attr(feature = "cargo-clippy", allow(clippy))] +#[allow(missing_docs)] +pub(crate) mod ffi { + include!(concat!(env!("OUT_DIR"), "/gl_bindings.rs")); +} + +pub use self::ffi::Gles2; + +/// Trait that describes objects that have an OpenGL context +/// and can be used to render upon +pub trait GLGraphicsBackend { + /// Swaps buffers at the end of a frame. + fn swap_buffers(&self) -> Result<(), SwapBuffersError>; + + /// Returns the address of an OpenGL function. + /// + /// Supposes that the context has been made current before this function is called. + unsafe fn get_proc_address(&self, symbol: &str) -> *const c_void; + + /// Returns the dimensions of the window, or screen, etc in points. + /// + /// These are the actual pixels of the underlying graphics backend. + /// For nested compositors you will need to handle the scaling + /// of the root compositor yourself, if you want to. + fn get_framebuffer_dimensions(&self) -> (u32, u32); + + /// Returns true if the OpenGL context is the current one in the thread. + fn is_current(&self) -> bool; + + /// Makes the OpenGL context the current context in the current thread. + /// + /// # Unsafety + /// + /// This function is marked unsafe, because the context cannot be made current + /// on multiple threads. + unsafe fn make_current(&self) -> Result<(), SwapBuffersError>; + + /// Returns the pixel format of the main framebuffer of the context. + fn get_pixel_format(&self) -> PixelFormat; +} + +/// Loads a Raw GLES Interface for a given `GLGraphicsBackend` +/// +/// This remains valid as long as the underlying `GLGraphicsBackend` is alive +/// and may only be used in combination with the backend. Using this with any +/// other gl context *may* cause undefined behavior. +pub fn load_raw_gl(backend: &B) -> Gles2 { + Gles2::load_with(|s| unsafe { backend.get_proc_address(s) as *const _ }) +} diff --git a/src/backend/graphics/glium.rs b/src/backend/graphics/glium.rs index 4c242fe..ab83d86 100644 --- a/src/backend/graphics/glium.rs +++ b/src/backend/graphics/glium.rs @@ -1,10 +1,6 @@ //! Glium compatibility module -use backend::graphics::egl::{ - error::Result as EGLResult, - wayland::{EGLDisplay, EGLWaylandExtensions}, - EGLGraphicsBackend, SwapBuffersError, -}; +use backend::graphics::{gl::GLGraphicsBackend, SwapBuffersError}; use glium::{ backend::{Backend, Context, Facade}, debug::DebugCallbackBehavior, @@ -15,7 +11,6 @@ use std::{ os::raw::c_void, rc::Rc, }; -use wayland_server::Display; impl From for GliumSwapBuffersError { fn from(error: SwapBuffersError) -> Self { @@ -28,14 +23,14 @@ impl From for GliumSwapBuffersError { } /// Wrapper to expose `Glium` compatibility -pub struct GliumGraphicsBackend { +pub struct GliumGraphicsBackend { context: Rc, backend: Rc>, } -struct InternalBackend(RefCell); +struct InternalBackend(RefCell); -impl GliumGraphicsBackend { +impl GliumGraphicsBackend { fn new(backend: T) -> GliumGraphicsBackend { let internal = Rc::new(InternalBackend(RefCell::new(backend))); @@ -79,27 +74,19 @@ impl GliumGraphicsBackend { } } -impl Facade for GliumGraphicsBackend { +impl Facade for GliumGraphicsBackend { fn get_context(&self) -> &Rc { &self.context } } -impl From for GliumGraphicsBackend { +impl From for GliumGraphicsBackend { fn from(backend: T) -> Self { GliumGraphicsBackend::new(backend) } } -impl EGLWaylandExtensions - for GliumGraphicsBackend -{ - fn bind_wl_display(&self, display: &Display) -> EGLResult { - (*self.backend).0.borrow().bind_wl_display(display) - } -} - -unsafe impl Backend for InternalBackend { +unsafe impl Backend for InternalBackend { fn swap_buffers(&self) -> Result<(), GliumSwapBuffersError> { self.0.borrow().swap_buffers().map_err(Into::into) } diff --git a/src/backend/graphics/mod.rs b/src/backend/graphics/mod.rs index e9470e8..188e1c0 100644 --- a/src/backend/graphics/mod.rs +++ b/src/backend/graphics/mod.rs @@ -2,40 +2,18 @@ //! //! Note: Not every API may be supported by every backend -/// General functions any graphics backend should support independently from it's rendering -/// technique. -pub trait GraphicsBackend { - /// Format representing the image drawn for the cursor. - type CursorFormat; +mod errors; +pub use self::errors::*; - /// Error the underlying backend throws if operations fail - type Error; +mod cursor; +pub use self::cursor::*; - /// Sets the cursor position and therefor updates the drawn cursors position. - /// Useful as well for e.g. pointer wrapping. - /// - /// Not guaranteed to be supported on every backend. The result usually - /// depends on the backend, the cursor might be "owned" by another more privileged - /// compositor (running nested). - /// - /// In these cases setting the position is actually not required, as movement is done - /// by the higher compositor and not by the backend. It is still good practice to update - /// the position after every received event, but don't rely on pointer wrapping working. - fn set_cursor_position(&self, x: u32, y: u32) -> Result<(), Self::Error>; +mod format; +pub use self::format::*; - /// Set the cursor drawn on the `GraphicsBackend`. - /// - /// The format is entirely dictated by the concrete implementation and might range - /// from raw image buffers over a fixed list of possible cursor types to simply the - /// void type `()` to represent no possible customization of the cursor itself. - fn set_cursor_representation( - &self, - cursor: &Self::CursorFormat, - hotspot: (u32, u32), - ) -> Result<(), Self::Error>; -} - -pub mod egl; +#[cfg(feature = "renderer_gl")] +pub mod gl; #[cfg(feature = "renderer_glium")] pub mod glium; +#[cfg(feature = "renderer_software")] pub mod software; diff --git a/src/backend/graphics/software.rs b/src/backend/graphics/software.rs index a7570bd..700f292 100644 --- a/src/backend/graphics/software.rs +++ b/src/backend/graphics/software.rs @@ -1,11 +1,10 @@ //! Common traits and types used for software rendering on graphics backends -use super::GraphicsBackend; use std::error::Error; use wayland_server::protocol::wl_shm::Format; /// Trait that describes objects providing a software rendering implementation -pub trait CpuGraphicsBackend: GraphicsBackend { +pub trait CpuGraphicsBackend { /// Render a given buffer of a given format at a specified place in the framebuffer /// /// # Error diff --git a/src/backend/libinput.rs b/src/backend/libinput.rs index 6a5407e..d700b5a 100644 --- a/src/backend/libinput.rs +++ b/src/backend/libinput.rs @@ -6,26 +6,25 @@ use backend::{input as backend, input::Axis}; use input as libinput; use input::event; +#[cfg(feature = "backend_session")] +use std::path::Path; use std::{ - cell::RefCell, collections::hash_map::{DefaultHasher, Entry, HashMap}, hash::{Hash, Hasher}, io::Error as IoError, - os::unix::io::RawFd, - path::Path, - rc::Rc, + os::unix::io::{AsRawFd, RawFd}, }; use wayland_server::calloop::{ - generic::{EventedRawFd, Generic}, + generic::{EventedFd, Generic}, mio::Ready, - LoopHandle, Source, + InsertError, LoopHandle, Source, }; // No idea if this is the same across unix platforms // Lets make this linux exclusive for now, once someone tries to build it for // any BSD-like system, they can verify if this is right and make a PR to change this. -#[cfg(any(target_os = "linux", target_os = "android"))] +#[cfg(all(any(target_os = "linux", target_os = "android"), feature = "backend_session"))] const INPUT_MAJOR: u32 = 13; /// Libinput based `InputBackend`. @@ -446,16 +445,18 @@ impl backend::InputBackend for LibinputInputBackend { libinput::Event::Keyboard(keyboard_event) => { use input::event::keyboard::*; match keyboard_event { - KeyboardEvent::Key(key_event) => if let Some(ref mut handler) = self.handler { - let device_seat = key_event.device().seat(); - if let Some(ref seat) = self.seats.get(&device_seat) { - trace!(self.logger, "Calling on_keyboard_key with {:?}", key_event); - handler.on_keyboard_key(seat, key_event); - } else { - warn!(self.logger, "Received key event of non existing Seat"); - continue; + KeyboardEvent::Key(key_event) => { + if let Some(ref mut handler) = self.handler { + let device_seat = key_event.device().seat(); + if let Some(ref seat) = self.seats.get(&device_seat) { + trace!(self.logger, "Calling on_keyboard_key with {:?}", key_event); + handler.on_keyboard_key(seat, key_event); + } else { + warn!(self.logger, "Received key event of non existing Seat"); + continue; + } } - }, + } } } libinput::Event::Pointer(pointer_event) => { @@ -590,6 +591,12 @@ impl libinput::LibinputInterface for LibinputSessionInterface { } } +impl AsRawFd for LibinputInputBackend { + fn as_raw_fd(&self) -> RawFd { + self.context.as_raw_fd() + } +} + /// Binds a `LibinputInputBackend` to a given `EventLoop`. /// /// Automatically feeds the backend with incoming events without any manual calls to @@ -597,22 +604,19 @@ impl libinput::LibinputInterface for LibinputSessionInterface { pub fn libinput_bind( backend: LibinputInputBackend, handle: LoopHandle, -) -> ::std::result::Result>, (IoError, LibinputInputBackend)> { - let mut source = Generic::from_raw_fd(unsafe { backend.context.fd() }); +) -> ::std::result::Result< + Source>>, + InsertError>>, +> { + let mut source = Generic::from_fd_source(backend); source.set_interest(Ready::readable()); - let backend = Rc::new(RefCell::new(backend)); - let fail_backend = backend.clone(); - handle - .insert_source(source, move |_, _| { - use backend::input::InputBackend; - if let Err(error) = backend.borrow_mut().dispatch_new_events() { - warn!(backend.borrow().logger, "Libinput errored: {}", error); - } - }).map_err(move |e| { - // the backend in the closure should already have been dropped - let backend = Rc::try_unwrap(fail_backend) - .unwrap_or_else(|_| unreachable!()) - .into_inner(); - (e.into(), backend) - }) + + handle.insert_source(source, move |evt, _| { + use backend::input::InputBackend; + + let mut backend = evt.source.borrow_mut(); + if let Err(error) = backend.0.dispatch_new_events() { + warn!(backend.0.logger, "Libinput errored: {}", error); + } + }) } diff --git a/src/backend/mod.rs b/src/backend/mod.rs index 328f605..ba94a50 100644 --- a/src/backend/mod.rs +++ b/src/backend/mod.rs @@ -19,6 +19,8 @@ pub mod input; #[cfg(feature = "backend_drm")] pub mod drm; +#[cfg(feature = "backend_egl")] +pub mod egl; #[cfg(feature = "backend_libinput")] pub mod libinput; #[cfg(feature = "backend_session")] diff --git a/src/backend/session/auto.rs b/src/backend/session/auto.rs index d5612c8..b192076 100644 --- a/src/backend/session/auto.rs +++ b/src/backend/session/auto.rs @@ -31,7 +31,7 @@ use super::logind::{self, logind_session_bind, BoundLogindSession, LogindSession, LogindSessionNotifier}; use super::{ direct::{self, direct_session_bind, BoundDirectSession, DirectSession, DirectSessionNotifier}, - AsErrno, AsSessionObserver, Session, SessionNotifier, SessionObserver, + AsErrno, Session, SessionNotifier, SessionObserver, }; use nix::fcntl::OFlag; use std::{cell::RefCell, io::Error as IoError, os::unix::io::RawFd, path::Path, rc::Rc}; @@ -114,6 +114,7 @@ impl AutoSession { } } + /// Tries to create a new session via the best available interface. #[cfg(not(feature = "backend_session_logind"))] pub fn new(logger: L) -> Option<(AutoSession, AutoSessionNotifier)> where @@ -202,10 +203,7 @@ impl Session for AutoSession { impl SessionNotifier for AutoSessionNotifier { type Id = AutoId; - fn register>( - &mut self, - signal: &mut A, - ) -> Self::Id { + fn register(&mut self, signal: S) -> Self::Id { match *self { #[cfg(feature = "backend_session_logind")] AutoSessionNotifier::Logind(ref mut logind) => { @@ -231,21 +229,6 @@ impl SessionNotifier for AutoSessionNotifier { _ => unreachable!(), } } - - fn is_active(&self) -> bool { - match *self { - #[cfg(feature = "backend_session_logind")] - AutoSessionNotifier::Logind(ref logind) => logind.is_active(), - AutoSessionNotifier::Direct(ref direct) => direct.is_active(), - } - } - fn seat(&self) -> &str { - match *self { - #[cfg(feature = "backend_session_logind")] - AutoSessionNotifier::Logind(ref logind) => logind.seat(), - AutoSessionNotifier::Direct(ref direct) => direct.seat(), - } - } } impl BoundAutoSession { diff --git a/src/backend/session/dbus/logind.rs b/src/backend/session/dbus/logind.rs index 147abd0..9450bc6 100644 --- a/src/backend/session/dbus/logind.rs +++ b/src/backend/session/dbus/logind.rs @@ -30,7 +30,7 @@ //! automatically by the `UdevBackend`, if not done manually). //! ``` -use backend::session::{AsErrno, AsSessionObserver, Session, SessionNotifier, SessionObserver}; +use backend::session::{AsErrno, Session, SessionNotifier, SessionObserver}; use dbus::{ BusName, BusType, Connection, ConnectionItem, ConnectionItems, Interface, Member, Message, MessageItem, OwnedFd, Path as DbusPath, Watch, WatchEvent, @@ -101,7 +101,8 @@ impl LogindSession { "org.freedesktop.login1.Manager", "GetSession", Some(vec![session_id.clone().into()]), - )?.get1::>() + )? + .get1::>() .chain_err(|| ErrorKind::UnexpectedMethodReturn)?; // Match all signals that we want to receive and handle @@ -342,7 +343,8 @@ impl Session for LogindSession { (major(stat.st_rdev) as u32).into(), (minor(stat.st_rdev) as u32).into(), ]), - )?.get2::(); + )? + .get2::(); let fd = fd.chain_err(|| ErrorKind::UnexpectedMethodReturn)?.into_fd(); Ok(fd) } else { @@ -363,7 +365,8 @@ impl Session for LogindSession { (major(stat.st_rdev) as u32).into(), (minor(stat.st_rdev) as u32).into(), ]), - ).map(|_| ()) + ) + .map(|_| ()) } else { bail!(ErrorKind::SessionLost) } @@ -390,7 +393,8 @@ impl Session for LogindSession { "org.freedesktop.login1.Seat", "SwitchTo", Some(vec![(vt_num as u32).into()]), - ).map(|_| ()) + ) + .map(|_| ()) } else { bail!(ErrorKind::SessionLost) } @@ -404,27 +408,13 @@ pub struct Id(usize); impl SessionNotifier for LogindSessionNotifier { type Id = Id; - fn register>( - &mut self, - signal: &mut A, - ) -> Self::Id { - self.internal - .signals - .borrow_mut() - .push(Some(Box::new(signal.observer()))); + fn register(&mut self, signal: S) -> Self::Id { + self.internal.signals.borrow_mut().push(Some(Box::new(signal))); Id(self.internal.signals.borrow().len() - 1) } fn unregister(&mut self, signal: Id) { self.internal.signals.borrow_mut()[signal.0] = None; } - - fn is_active(&self) -> bool { - self.internal.active.load(Ordering::SeqCst) - } - - fn seat(&self) -> &str { - &self.internal.seat - } } /// Bound logind session that is driven by the `wayland_server::EventLoop`. @@ -455,7 +445,10 @@ pub fn logind_session_bind( .into_iter() .map(|watch| { let mut source = Generic::from_raw_fd(watch.fd()); - source.set_interest(Ready::readable() | Ready::writable()); + source.set_interest( + if watch.readable() { Ready::readable() } else { Ready::empty() } + | if watch.writable() { Ready::writable() } else { Ready::empty() } + ); handle.insert_source(source, { let mut notifier = notifier.clone(); move |evt, _| notifier.event(evt) diff --git a/src/backend/session/direct.rs b/src/backend/session/direct.rs index f795b44..cdb60d6 100644 --- a/src/backend/session/direct.rs +++ b/src/backend/session/direct.rs @@ -45,7 +45,7 @@ //! for notifications are the `Libinput` context, the `UdevBackend` or a `DrmDevice` (handled //! automatically by the `UdevBackend`, if not done manually). -use super::{AsErrno, AsSessionObserver, Session, SessionNotifier, SessionObserver}; +use super::{AsErrno, Session, SessionNotifier, SessionObserver}; use nix::{ fcntl::{self, open, OFlag}, libc::c_int, @@ -67,7 +67,7 @@ use std::{ Arc, }, }; -#[cfg(feature = "backend_session_udev")] +#[cfg(feature = "backend_udev")] use udev::Context; use wayland_server::calloop::{signals::Signals, LoopHandle, Source}; @@ -120,12 +120,12 @@ const TTY_MAJOR: u64 = 4; #[cfg(not(any(target_os = "linux", target_os = "android")))] const TTY_MAJOR: u64 = 0; -#[cfg(not(feature = "backend_session_udev"))] +#[cfg(not(feature = "backend_udev"))] fn is_tty_device(dev: dev_t, _path: Option<&Path>) -> bool { major(dev) == TTY_MAJOR } -#[cfg(feature = "backend_session_udev")] +#[cfg(feature = "backend_udev")] fn is_tty_device(dev: dev_t, path: Option<&Path>) -> bool { match path { Some(path) => { @@ -185,8 +185,10 @@ impl DirectSession { path, fcntl::OFlag::O_RDWR | fcntl::OFlag::O_CLOEXEC, Mode::empty(), - ).chain_err(|| ErrorKind::FailedToOpenTTY(String::from(path.to_string_lossy()))) - }).unwrap_or_else(|| { + ) + .chain_err(|| ErrorKind::FailedToOpenTTY(String::from(path.to_string_lossy()))) + }) + .unwrap_or_else(|| { dup(0 /*stdin*/).chain_err(|| ErrorKind::FailedToOpenTTY(String::from(""))) })?; @@ -350,28 +352,18 @@ pub struct Id(usize); impl SessionNotifier for DirectSessionNotifier { type Id = Id; - fn register>( - &mut self, - signal: &mut A, - ) -> Self::Id { - self.signals.push(Some(Box::new(signal.observer()))); + fn register(&mut self, signal: S) -> Self::Id { + self.signals.push(Some(Box::new(signal))); Id(self.signals.len() - 1) } fn unregister(&mut self, signal: Id) { self.signals[signal.0] = None; } - - fn is_active(&self) -> bool { - self.active.load(Ordering::SeqCst) - } - fn seat(&self) -> &str { - "seat0" - } } impl DirectSessionNotifier { fn signal_received(&mut self) { - if self.is_active() { + if self.active.load(Ordering::SeqCst) { info!(self.logger, "Session shall become inactive."); for signal in &mut self.signals { if let Some(ref mut signal) = *signal { @@ -439,7 +431,8 @@ pub fn direct_session_bind( .insert_source(source, { let notifier = notifier.clone(); move |_, _| notifier.borrow_mut().signal_received() - }).map_err(move |e| { + }) + .map_err(move |e| { // the backend in the closure should already have been dropped let notifier = Rc::try_unwrap(fail_notifier) .unwrap_or_else(|_| unreachable!()) diff --git a/src/backend/session/mod.rs b/src/backend/session/mod.rs index d6c07cb..8967233 100644 --- a/src/backend/session/mod.rs +++ b/src/backend/session/mod.rs @@ -10,7 +10,7 @@ //! The following mechanisms are currently provided: //! - direct - legacy tty / virtual terminal kernel API //! -use nix::fcntl::OFlag; +pub use nix::fcntl::OFlag; use std::{ cell::RefCell, os::unix::io::RawFd, @@ -54,15 +54,9 @@ pub trait SessionNotifier { /// Registers a given `SessionObserver`. /// /// Returns an id of the inserted observer, can be used to remove it again. - fn register>(&mut self, signal: &mut A) - -> Self::Id; + fn register(&mut self, signal: S) -> Self::Id; /// Removes an observer by its given id from `SessionNotifier::register`. fn unregister(&mut self, signal: Self::Id); - - /// Check if this session is currently active - fn is_active(&self) -> bool; - /// Which seat this session is on - fn seat(&self) -> &str; } /// Trait describing the ability to return a `SessionObserver` related to Self. @@ -186,3 +180,5 @@ pub mod auto; mod dbus; pub mod direct; pub use self::dbus::*; +mod multi; +pub use self::multi::*; diff --git a/src/backend/session/multi.rs b/src/backend/session/multi.rs new file mode 100644 index 0000000..4cd3fb7 --- /dev/null +++ b/src/backend/session/multi.rs @@ -0,0 +1,69 @@ +use std::{ + collections::HashMap, + os::unix::io::RawFd, + sync::{ + atomic::{AtomicUsize, Ordering}, + Arc, Mutex, + }, +}; + +use super::{SessionNotifier, SessionObserver}; + +static ID_COUNTER: AtomicUsize = AtomicUsize::new(0); + +/// Ids of registered `SessionObserver`s of the `DirectSessionNotifier` +#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)] +pub struct Id(usize); + +struct MultiObserver { + observer: Arc>>>, +} + +impl SessionObserver for MultiObserver { + fn pause(&mut self, device: Option<(u32, u32)>) { + let mut lock = self.observer.lock().unwrap(); + for mut observer in lock.values_mut() { + observer.pause(device) + } + } + fn activate(&mut self, device: Option<(u32, u32, Option)>) { + let mut lock = self.observer.lock().unwrap(); + for mut observer in lock.values_mut() { + observer.activate(device) + } + } +} + +struct MultiNotifier { + observer: Arc>>>, +} + +impl SessionNotifier for MultiNotifier { + type Id = Id; + + fn register(&mut self, signal: S) -> Self::Id { + let id = Id(ID_COUNTER.fetch_add(1, Ordering::SeqCst)); + self.observer.lock().unwrap().insert(id, Box::new(signal)); + id + } + + fn unregister(&mut self, signal: Self::Id) { + self.observer.lock().unwrap().remove(&signal); + } +} + +/// Create a pair of a linked [`SessionObserver`](../trait.SessionObserver.html) and a +/// [`SessionNotifier`](../trait.SessionNotifier.html). +/// +/// Observers added to the returned notifier are notified, +/// when the returned observer is notified. +pub fn notify_multiplexer() -> (impl SessionObserver, impl SessionNotifier) { + let observer = Arc::new(Mutex::new(HashMap::new())); + + ( + MultiObserver { + observer: observer.clone(), + }, + MultiNotifier { observer }, + ) +} diff --git a/src/backend/udev.rs b/src/backend/udev.rs index 4055697..cc2f901 100644 --- a/src/backend/udev.rs +++ b/src/backend/udev.rs @@ -9,258 +9,90 @@ //! See also `examples/udev.rs` for pure hardware backed example of a compositor utilizing this //! backend. -use backend::{ - drm::{drm_device_bind, DrmDevice, DrmHandler}, - session::{AsSessionObserver, Session, SessionObserver}, -}; -use drm::{control::Device as ControlDevice, Device as BasicDevice}; -use nix::{fcntl, sys::stat::dev_t}; +use nix::sys::stat::{dev_t, stat}; use std::{ - cell::RefCell, - collections::HashMap, + collections::HashSet, ffi::OsString, - io::Error as IoError, - mem::drop, os::unix::io::{AsRawFd, RawFd}, path::{Path, PathBuf}, - rc::{Rc, Weak}, }; -use udev::{Context, Enumerator, Event, EventType, MonitorBuilder, MonitorSocket, Result as UdevResult}; +use udev::{Context, Enumerator, EventType, MonitorBuilder, MonitorSocket, Result as UdevResult}; use wayland_server::calloop::{ - generic::{EventedRawFd, Generic}, + generic::{EventedFd, Generic}, mio::Ready, - LoopHandle, Source, + InsertError, LoopHandle, Source, }; -/// Udev's `DrmDevice` type based on the underlying session -pub struct SessionFdDrmDevice(RawFd); - -impl AsRawFd for SessionFdDrmDevice { - fn as_raw_fd(&self) -> RawFd { - self.0 - } -} -impl BasicDevice for SessionFdDrmDevice {} -impl ControlDevice for SessionFdDrmDevice {} - -/// Graphical backend that monitors available DRM devices. +/// Backend to monitor available drm devices. /// -/// Provides a way to automatically initialize a `DrmDevice` for available GPUs and notifies the -/// given handler of any changes. Can be used to provide hot-plug functionality for GPUs and +/// Provides a way to automatically scan for available gpus and notifies the +/// given handler of any changes. Can be used to provide hot-plug functionality for gpus and /// attached monitors. -pub struct UdevBackend< - H: DrmHandler + 'static, - S: Session + 'static, - T: UdevHandler + 'static, - Data: 'static, -> { - _handler: ::std::marker::PhantomData, - devices: Rc< - RefCell< - HashMap< - dev_t, - ( - Source>, - Rc>>, - ), - >, - >, - >, +pub struct UdevBackend { + devices: HashSet, monitor: MonitorSocket, - session: S, handler: T, logger: ::slog::Logger, - handle: LoopHandle, } -impl< - H: DrmHandler + 'static, - S: Session + 'static, - T: UdevHandler + 'static, - Data: 'static, - > UdevBackend -{ +impl AsRawFd for UdevBackend { + fn as_raw_fd(&self) -> RawFd { + self.monitor.as_raw_fd() + } +} + +impl UdevBackend { /// Creates a new `UdevBackend` and adds it to the given `EventLoop`'s state. /// /// ## Arguments - /// `evlh` - An event loop to use for binding `DrmDevices` /// `context` - An initialized udev context - /// `session` - A session used to open and close devices as they become available /// `handler` - User-provided handler to respond to any detected changes + /// `seat` - /// `logger` - slog Logger to be used by the backend and its `DrmDevices`. - pub fn new( - handle: LoopHandle, + pub fn new>( context: &Context, - mut session: S, mut handler: T, + seat: S, logger: L, - ) -> Result> + ) -> UdevResult> where L: Into>, { - let logger = ::slog_or_stdlog(logger).new(o!("smithay_module" => "backend_udev")); - let seat = session.seat(); - let devices = all_gpus(context, seat) - .chain_err(|| ErrorKind::FailedToScan)? + let log = ::slog_or_stdlog(logger).new(o!("smithay_module" => "backend_udev")); + + let devices = all_gpus(context, seat)? .into_iter() // Create devices - .flat_map(|path| { - match DrmDevice::new( - { - match session.open( - &path, - fcntl::OFlag::O_RDWR - | fcntl::OFlag::O_CLOEXEC - | fcntl::OFlag::O_NOCTTY - | fcntl::OFlag::O_NONBLOCK, - ) { - Ok(fd) => SessionFdDrmDevice(fd), - Err(err) => { - warn!( - logger, - "Unable to open drm device {:?}, Error: {:?}. Skipping", path, err - ); - return None; - } - } - }, - logger.clone(), - ) { - // Call the handler, which might add it to the runloop - Ok(mut device) => { - let devnum = device.device_id(); - let fd = device.as_raw_fd(); - match handler.device_added(&mut device) { - Some(drm_handler) => match drm_device_bind(&handle, device, drm_handler) { - Ok((event_source, device)) => Some((devnum, (event_source, device))), - Err((err, mut device)) => { - warn!(logger, "Failed to bind device. Error: {:?}.", err); - handler.device_removed(&mut device); - drop(device); - if let Err(err) = session.close(fd) { - warn!( - logger, - "Failed to close dropped device. Error: {:?}. Ignoring", err - ); - }; - None - } - }, - None => { - drop(device); //drops master - if let Err(err) = session.close(fd) { - warn!(logger, "Failed to close device. Error: {:?}. Ignoring", err); - } - None - } - } - } - Err(err) => { - warn!( - logger, - "Failed to initialize device {:?}. Error: {:?}. Skipping", path, err - ); - None - } + .flat_map(|path| match stat(&path) { + Ok(stat) => { + handler.device_added(stat.st_rdev, path); + Some(stat.st_rdev) } - }).collect::>(); + Err(err) => { + warn!(log, "Unable to get id of {:?}, Error: {:?}. Skipping", path, err); + None + } + }) + .collect(); - let mut builder = MonitorBuilder::new(context).chain_err(|| ErrorKind::FailedToInitMonitor)?; - builder - .match_subsystem("drm") - .chain_err(|| ErrorKind::FailedToInitMonitor)?; - let monitor = builder.listen().chain_err(|| ErrorKind::FailedToInitMonitor)?; + let mut builder = MonitorBuilder::new(context)?; + builder.match_subsystem("drm")?; + let monitor = builder.listen()?; Ok(UdevBackend { - _handler: ::std::marker::PhantomData, - devices: Rc::new(RefCell::new(devices)), + devices, monitor, - session, handler, - logger, - handle, + logger: log, }) } - - /// Closes the udev backend and frees all remaining open devices. - pub fn close(&mut self) { - let mut devices = self.devices.borrow_mut(); - for (_, (event_source, device)) in devices.drain() { - event_source.remove(); - let mut device = Rc::try_unwrap(device) - .unwrap_or_else(|_| unreachable!()) - .into_inner(); - self.handler.device_removed(&mut device); - let fd = device.as_raw_fd(); - drop(device); - if let Err(err) = self.session.close(fd) { - warn!(self.logger, "Failed to close device. Error: {:?}. Ignoring", err); - }; - } - info!(self.logger, "All devices closed"); - } } -impl< - H: DrmHandler + 'static, - S: Session + 'static, - T: UdevHandler + 'static, - Data: 'static, - > Drop for UdevBackend -{ +impl Drop for UdevBackend { fn drop(&mut self) { - self.close(); - } -} - -/// `SessionObserver` linked to the `UdevBackend` it was created from. -pub struct UdevBackendObserver { - devices: Weak< - RefCell< - HashMap< - dev_t, - ( - Source>, - Rc>>, - ), - >, - >, - >, - logger: ::slog::Logger, -} - -impl< - H: DrmHandler + 'static, - S: Session + 'static, - T: UdevHandler + 'static, - Data: 'static, - > AsSessionObserver for UdevBackend -{ - fn observer(&mut self) -> UdevBackendObserver { - UdevBackendObserver { - devices: Rc::downgrade(&self.devices), - logger: self.logger.clone(), - } - } -} - -impl SessionObserver for UdevBackendObserver { - fn pause(&mut self, devnum: Option<(u32, u32)>) { - if let Some(devices) = self.devices.upgrade() { - for &mut (_, ref device) in devices.borrow_mut().values_mut() { - info!(self.logger, "changed successful"); - device.borrow_mut().observer().pause(devnum); - } - } - } - - fn activate(&mut self, devnum: Option<(u32, u32, Option)>) { - if let Some(devices) = self.devices.upgrade() { - for &mut (_, ref device) in devices.borrow_mut().values_mut() { - info!(self.logger, "changed successful"); - device.borrow_mut().observer().activate(devnum); - } + for device in &self.devices { + self.handler.device_removed(*device); } } } @@ -269,122 +101,38 @@ impl SessionObserver for UdevBackendObserver { /// /// Allows the backend to receive kernel events and thus to drive the `UdevHandler`. /// No runtime functionality can be provided without using this function. -pub fn udev_backend_bind( - mut udev: UdevBackend, -) -> ::std::result::Result>, IoError> -where - H: DrmHandler + 'static, - T: UdevHandler + 'static, - S: Session + 'static, -{ - let fd = udev.monitor.as_raw_fd(); - let handle = udev.handle.clone(); - let mut source = Generic::from_raw_fd(fd); +pub fn udev_backend_bind( + udev: UdevBackend, + handle: &LoopHandle, +) -> Result>>>, InsertError>>>> { + let mut source = Generic::from_fd_source(udev); source.set_interest(Ready::readable()); - handle - .insert_source(source, move |_, _| { - udev.process_events(); - }).map_err(Into::into) + + handle.insert_source(source, |evt, _| { + evt.source.borrow_mut().0.process_events(); + }) } -impl UdevBackend -where - H: DrmHandler + 'static, - T: UdevHandler + 'static, - S: Session + 'static, - Data: 'static, -{ +impl UdevBackend { fn process_events(&mut self) { - let events = self.monitor.clone().collect::>(); - for event in events { + let monitor = self.monitor.clone(); + for event in monitor { match event.event_type() { // New device EventType::Add => { info!(self.logger, "Device Added"); if let (Some(path), Some(devnum)) = (event.devnode(), event.devnum()) { - let mut device = { - match DrmDevice::new( - { - let logger = self.logger.clone(); - match self.session.open( - path, - fcntl::OFlag::O_RDWR - | fcntl::OFlag::O_CLOEXEC - | fcntl::OFlag::O_NOCTTY - | fcntl::OFlag::O_NONBLOCK, - ) { - Ok(fd) => SessionFdDrmDevice(fd), - Err(err) => { - warn!( - logger, - "Unable to open drm device {:?}, Error: {:?}. Skipping", - path, - err - ); - continue; - } - } - }, - self.logger.clone(), - ) { - Ok(dev) => dev, - Err(err) => { - warn!( - self.logger, - "Failed to initialize device {:?}. Error: {}. Skipping", path, err - ); - continue; - } - } - }; - let fd = device.as_raw_fd(); - match self.handler.device_added(&mut device) { - Some(drm_handler) => match drm_device_bind(&self.handle, device, drm_handler) { - Ok(fd_event_source) => { - self.devices.borrow_mut().insert(devnum, fd_event_source); - } - Err((err, mut device)) => { - warn!(self.logger, "Failed to bind device. Error: {:?}.", err); - self.handler.device_removed(&mut device); - drop(device); - if let Err(err) = self.session.close(fd) { - warn!( - self.logger, - "Failed to close dropped device. Error: {:?}. Ignoring", err - ); - }; - } - }, - None => { - self.handler.device_removed(&mut device); - drop(device); - if let Err(err) = self.session.close(fd) { - warn!(self.logger, "Failed to close unused device. Error: {:?}", err); - } - } - }; + if self.devices.insert(devnum) { + self.handler.device_added(devnum, path.to_path_buf()); + } } } // Device removed EventType::Remove => { info!(self.logger, "Device Remove"); if let Some(devnum) = event.devnum() { - if let Some((fd_event_source, device)) = self.devices.borrow_mut().remove(&devnum) { - fd_event_source.remove(); - let mut device = Rc::try_unwrap(device) - .unwrap_or_else(|_| unreachable!()) - .into_inner(); - self.handler.device_removed(&mut device); - let fd = device.as_raw_fd(); - drop(device); - if let Err(err) = self.session.close(fd) { - warn!( - self.logger, - "Failed to close device {:?}. Error: {:?}. Ignoring", - event.sysname(), - err - ); - }; + if self.devices.remove(&devnum) { + self.handler.device_removed(devnum); } } } @@ -393,9 +141,8 @@ where info!(self.logger, "Device Changed"); if let Some(devnum) = event.devnum() { info!(self.logger, "Devnum: {:b}", devnum); - if let Some(&(_, ref device)) = self.devices.borrow_mut().get(&devnum) { - let handler = &mut self.handler; - handler.device_changed(&mut device.borrow_mut()); + if self.devices.contains(&devnum) { + self.handler.device_changed(devnum); } else { info!(self.logger, "changed, but device not tracked by backend"); }; @@ -409,36 +156,21 @@ where } } -/// Handler for the `UdevBackend`, allows to open, close and update DRM devices as they change during runtime. -pub trait UdevHandler + 'static> { - /// Called on initialization for every known device and when a new device is detected. - /// - /// Returning a `DrmHandler` will initialize the device, returning `None` will ignore the device. - /// - /// ## Panics - /// Panics if you try to borrow the token of the belonging `UdevBackend` using this `StateProxy`. - fn device_added(&mut self, device: &mut DrmDevice) -> Option; +/// Handler for the `UdevBackend`, allows to open, close and update drm devices as they change during runtime. +pub trait UdevHandler { + /// Called when a new device is detected. + fn device_added(&mut self, device: dev_t, path: PathBuf); /// Called when an open device is changed. /// /// This usually indicates that some connectors did become available or were unplugged. The handler /// should scan again for connected monitors and mode switch accordingly. - /// - /// ## Panics - /// Panics if you try to borrow the token of the belonging `UdevBackend` using this `StateProxy`. - fn device_changed(&mut self, device: &mut DrmDevice); + fn device_changed(&mut self, device: dev_t); /// Called when a device was removed. /// - /// The device will not accept any operations anymore and its file descriptor will be closed once - /// this function returns, any open references/tokens to this device need to be released. - /// - /// ## Panics - /// Panics if you try to borrow the token of the belonging `UdevBackend` using this `StateProxy`. - fn device_removed(&mut self, device: &mut DrmDevice); - /// Called when the udev context has encountered and error. - /// - /// ## Panics - /// Panics if you try to borrow the token of the belonging `UdevBackend` using this `StateProxy`. - fn error(&mut self, error: IoError); + /// The corresponding `UdevRawFd` will never return a valid `RawFd` anymore + /// and its file descriptor will be closed once this function returns, + /// any open references/tokens to this device need to be released. + fn device_removed(&mut self, device: dev_t); } /// Returns the path of the primary GPU device if any @@ -486,25 +218,7 @@ pub fn all_gpus>(context: &Context, seat: S) -> UdevResult &WinitWindow { + fn window(&self) -> Ref { match *self { - Window::Wayland { ref context, .. } => &**context, - Window::X11 { ref context, .. } => &**context, + Window::Wayland { ref context, .. } => context.borrow(), + Window::X11 { ref context, .. } => context.borrow(), } } } @@ -156,12 +155,12 @@ where let reqs = Default::default(); let window = Rc::new( if native::NativeDisplay::::is_backend(&winit_window) { - let context = + let mut context = EGLContext::::new(winit_window, attributes, reqs, log.clone())?; let surface = context.create_surface(())?; Window::Wayland { context, surface } } else if native::NativeDisplay::::is_backend(&winit_window) { - let context = + let mut context = EGLContext::::new(winit_window, attributes, reqs, log.clone())?; let surface = context.create_surface(())?; Window::X11 { context, surface } @@ -221,13 +220,13 @@ pub trait WinitEventsHandler { impl WinitGraphicsBackend { /// Get a reference to the internally used `winit::Window` - pub fn winit_window(&self) -> &WinitWindow { + pub fn winit_window(&self) -> Ref { self.window.window() } } -impl GraphicsBackend for WinitGraphicsBackend { - type CursorFormat = MouseCursor; +impl<'a> CursorBackend<'a> for WinitGraphicsBackend { + type CursorFormat = &'a MouseCursor; type Error = (); fn set_cursor_position(&self, x: u32, y: u32) -> ::std::result::Result<(), ()> { @@ -240,11 +239,14 @@ impl GraphicsBackend for WinitGraphicsBackend { }) } - fn set_cursor_representation( - &self, - cursor: &Self::CursorFormat, + fn set_cursor_representation<'b>( + &'b self, + cursor: Self::CursorFormat, _hotspot: (u32, u32), - ) -> ::std::result::Result<(), ()> { + ) -> ::std::result::Result<(), ()> + where + 'a: 'b, + { // Cannot log this one, as `CursorFormat` is not `Debug` and should not be debug!(self.logger, "Changing cursor representation"); self.window.window().set_cursor(*cursor); @@ -252,7 +254,7 @@ impl GraphicsBackend for WinitGraphicsBackend { } } -impl EGLGraphicsBackend for WinitGraphicsBackend { +impl GLGraphicsBackend for WinitGraphicsBackend { fn swap_buffers(&self) -> ::std::result::Result<(), SwapBuffersError> { trace!(self.logger, "Swapping buffers"); match *self.window { @@ -303,7 +305,7 @@ impl EGLGraphicsBackend for WinitGraphicsBackend { } } -impl EGLWaylandExtensions for WinitGraphicsBackend { +impl EGLGraphicsBackend for WinitGraphicsBackend { fn bind_wl_display(&self, display: &Display) -> EGLResult { match *self.window { Window::Wayland { ref context, .. } => context.bind_wl_display(display), diff --git a/src/lib.rs b/src/lib.rs index 24e5b49..9e11b51 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,6 +14,7 @@ extern crate tempfile; pub extern crate wayland_commons; pub extern crate wayland_protocols; pub extern crate wayland_server; +#[cfg(feature = "native_lib")] extern crate wayland_sys; extern crate xkbcommon; @@ -21,13 +22,13 @@ extern crate xkbcommon; pub extern crate dbus; #[cfg(feature = "backend_drm")] pub extern crate drm; -#[cfg(feature = "backend_drm")] +#[cfg(feature = "backend_drm_gbm")] pub extern crate gbm; #[cfg(feature = "backend_libinput")] pub extern crate input; #[cfg(feature = "backend_session_logind")] pub extern crate systemd; -#[cfg(feature = "udev")] +#[cfg(feature = "backend_udev")] pub extern crate udev; #[cfg(feature = "backend_winit")] extern crate wayland_client; diff --git a/src/wayland/data_device/dnd_grab.rs b/src/wayland/data_device/dnd_grab.rs index 4876f14..132219f 100644 --- a/src/wayland/data_device/dnd_grab.rs +++ b/src/wayland/data_device/dnd_grab.rs @@ -108,7 +108,8 @@ impl PointerGrab for DnDGrab { offer_data.clone(), action_choice, ) - }).unwrap(); + }) + .unwrap(); // advertize the offer to the client device.send(wl_data_device::Event::DataOffer { id: offer.clone() }); with_source_metadata(source, |meta| { @@ -118,7 +119,8 @@ impl PointerGrab for DnDGrab { offer.send(wl_data_offer::Event::SourceActions { source_actions: meta.dnd_action.to_raw(), }); - }).unwrap(); + }) + .unwrap(); device.send(wl_data_device::Event::Enter { serial, x: x - sx, diff --git a/src/wayland/data_device/mod.rs b/src/wayland/data_device/mod.rs index 0c2b84b..e1d410e 100644 --- a/src/wayland/data_device/mod.rs +++ b/src/wayland/data_device/mod.rs @@ -150,7 +150,8 @@ impl SeatData { // check if the source and associated mime type is still valid let valid = with_source_metadata(&source, |meta| { meta.mime_types.contains(&mime_type) - }).unwrap_or(false) + }) + .unwrap_or(false) && source.is_alive(); if !valid { // deny the receive @@ -171,7 +172,8 @@ impl SeatData { for mime_type in meta.mime_types.iter().cloned() { offer.send(wl_data_offer::Event::Offer { mime_type }) } - }).unwrap(); + }) + .unwrap(); dd.send(wl_data_device::Event::Selection { id: Some(offer) }); } } diff --git a/src/wayland/data_device/server_dnd_grab.rs b/src/wayland/data_device/server_dnd_grab.rs index ec8e474..9de8b69 100644 --- a/src/wayland/data_device/server_dnd_grab.rs +++ b/src/wayland/data_device/server_dnd_grab.rs @@ -131,7 +131,8 @@ where self.callback.clone(), action_choice, ) - }).unwrap(); + }) + .unwrap(); // advertize the offer to the client device.send(wl_data_device::Event::DataOffer { id: offer.clone() }); for mime_type in self.metadata.mime_types.iter().cloned() { diff --git a/src/wayland/seat/keyboard.rs b/src/wayland/seat/keyboard.rs index 55c2622..782fdb6 100644 --- a/src/wayland/seat/keyboard.rs +++ b/src/wayland/seat/keyboard.rs @@ -141,7 +141,8 @@ impl KbdInternal { &xkb_config.variant, xkb_config.options, xkb::KEYMAP_COMPILE_NO_FLAGS, - ).ok_or(())?; + ) + .ok_or(())?; let state = xkb::State::new(&keymap); Ok(KbdInternal { known_kbds: Vec::new(), diff --git a/src/wayland/shell/legacy/wl_handlers.rs b/src/wayland/shell/legacy/wl_handlers.rs index 7793a30..b72c570 100644 --- a/src/wayland/shell/legacy/wl_handlers.rs +++ b/src/wayland/shell/legacy/wl_handlers.rs @@ -119,7 +119,8 @@ where } else { false } - }).expect("wl_shell_surface exists but surface has not the right role?"); + }) + .expect("wl_shell_surface exists but surface has not the right role?"); if valid { (&mut *user_impl)(ShellRequest::Pong { surface: make_handle(&shell_surface, ctoken), diff --git a/src/wayland/shell/xdg/mod.rs b/src/wayland/shell/xdg/mod.rs index 2ad1492..c4f3150 100644 --- a/src/wayland/shell/xdg/mod.rs +++ b/src/wayland/shell/xdg/mod.rs @@ -620,7 +620,8 @@ where .with_role_data::(&self.wl_surface, |data| match data.pending_state { XdgSurfacePendingState::Toplevel(ref state) => Some(state.clone()), _ => None, - }).ok() + }) + .ok() .and_then(|x| x) } } @@ -782,7 +783,8 @@ where .with_role_data::(&self.wl_surface, |data| match data.pending_state { XdgSurfacePendingState::Popup(ref state) => Some(state.clone()), _ => None, - }).ok() + }) + .ok() .and_then(|x| x) } } diff --git a/src/wayland/shell/xdg/xdg_handlers.rs b/src/wayland/shell/xdg/xdg_handlers.rs index d5dc724..dd722cb 100644 --- a/src/wayland/shell/xdg/xdg_handlers.rs +++ b/src/wayland/shell/xdg/xdg_handlers.rs @@ -217,7 +217,8 @@ where "xdg_surface was destroyed before its role object".into(), ); } - }).expect("xdg_surface exists but surface has not shell_surface role?!"); + }) + .expect("xdg_surface exists but surface has not shell_surface role?!"); } fn xdg_surface_implementation( @@ -244,7 +245,8 @@ fn xdg_surface_implementation( min_size: (0, 0), max_size: (0, 0), }); - }).expect("xdg_surface exists but surface has not shell_surface role?!"); + }) + .expect("xdg_surface exists but surface has not shell_surface role?!"); let toplevel = id.implement_nonsend( toplevel_implementation::, Some(destroy_toplevel::), @@ -286,7 +288,8 @@ fn xdg_surface_implementation( parent: parent_surface, positioner: positioner_data.borrow().clone(), }); - }).expect("xdg_surface exists but surface has not shell_surface role?!"); + }) + .expect("xdg_surface exists but surface has not shell_surface role?!"); let popup = id.implement_nonsend( xg_popup_implementation::, Some(destroy_popup::), @@ -315,7 +318,8 @@ fn xdg_surface_implementation( .compositor_token .with_role_data::(&data.wl_surface, |data| { data.window_geometry = Some(Rectangle { x, y, width, height }); - }).expect("xdg_surface exists but surface has not shell_surface role?!"); + }) + .expect("xdg_surface exists but surface has not shell_surface role?!"); } xdg_surface::Request::AckConfigure { serial } => { data.shell_data @@ -336,7 +340,8 @@ fn xdg_surface_implementation( ); } role_data.configured = true; - }).expect("xdg_surface exists but surface has not shell_surface role?!"); + }) + .expect("xdg_surface exists but surface has not shell_surface role?!"); } } } @@ -369,7 +374,8 @@ fn with_surface_toplevel_data( .with_role_data::(&toplevel_data.wl_surface, |data| match data.pending_state { XdgSurfacePendingState::Toplevel(ref mut toplevel_data) => f(toplevel_data), _ => unreachable!(), - }).expect("xdg_toplevel exists but surface has not shell_surface role?!"); + }) + .expect("xdg_toplevel exists but surface has not shell_surface role?!"); } pub fn send_toplevel_configure( @@ -539,7 +545,8 @@ where .with_role_data::(&data.wl_surface, |data| { data.pending_state = XdgSurfacePendingState::None; data.configured = false; - }).expect("xdg_toplevel exists but surface has not shell_surface role?!"); + }) + .expect("xdg_toplevel exists but surface has not shell_surface role?!"); } // remove this surface from the known ones (as well as any leftover dead surface) data.shell_data @@ -627,7 +634,8 @@ where .with_role_data::(&data.wl_surface, |data| { data.pending_state = XdgSurfacePendingState::None; data.configured = false; - }).expect("xdg_popup exists but surface has not shell_surface role?!"); + }) + .expect("xdg_popup exists but surface has not shell_surface role?!"); } // remove this surface from the known ones (as well as any leftover dead surface) data.shell_data diff --git a/src/wayland/shell/xdg/zxdgv6_handlers.rs b/src/wayland/shell/xdg/zxdgv6_handlers.rs index 1621146..eb8cc17 100644 --- a/src/wayland/shell/xdg/zxdgv6_handlers.rs +++ b/src/wayland/shell/xdg/zxdgv6_handlers.rs @@ -236,7 +236,8 @@ where "xdg_surface was destroyed before its role object".into(), ); } - }).expect("xdg_surface exists but surface has not shell_surface role?!"); + }) + .expect("xdg_surface exists but surface has not shell_surface role?!"); } fn xdg_surface_implementation( @@ -263,7 +264,8 @@ fn xdg_surface_implementation( min_size: (0, 0), max_size: (0, 0), }); - }).expect("xdg_surface exists but surface has not shell_surface role?!"); + }) + .expect("xdg_surface exists but surface has not shell_surface role?!"); let toplevel = id.implement_nonsend( toplevel_implementation::, Some(destroy_toplevel::), @@ -302,7 +304,8 @@ fn xdg_surface_implementation( parent: Some(parent_data.wl_surface.clone()), positioner: positioner_data.borrow().clone(), }); - }).expect("xdg_surface exists but surface has not shell_surface role?!"); + }) + .expect("xdg_surface exists but surface has not shell_surface role?!"); let popup = id.implement_nonsend( popup_implementation::, Some(destroy_popup::), @@ -331,7 +334,8 @@ fn xdg_surface_implementation( .compositor_token .with_role_data::(&data.wl_surface, |data| { data.window_geometry = Some(Rectangle { x, y, width, height }); - }).expect("xdg_surface exists but surface has not shell_surface role?!"); + }) + .expect("xdg_surface exists but surface has not shell_surface role?!"); } zxdg_surface_v6::Request::AckConfigure { serial } => { data.shell_data @@ -352,7 +356,8 @@ fn xdg_surface_implementation( ); } role_data.configured = true; - }).expect("xdg_surface exists but surface has not shell_surface role?!"); + }) + .expect("xdg_surface exists but surface has not shell_surface role?!"); } } } @@ -382,7 +387,8 @@ where .with_role_data::(&data.wl_surface, |data| match data.pending_state { XdgSurfacePendingState::Toplevel(ref mut toplevel_data) => f(toplevel_data), _ => unreachable!(), - }).expect("xdg_toplevel exists but surface has not shell_surface role?!"); + }) + .expect("xdg_toplevel exists but surface has not shell_surface role?!"); } pub fn send_toplevel_configure( @@ -553,7 +559,8 @@ where .with_role_data::(&data.wl_surface, |data| { data.pending_state = XdgSurfacePendingState::None; data.configured = false; - }).expect("xdg_toplevel exists but surface has not shell_surface role?!"); + }) + .expect("xdg_toplevel exists but surface has not shell_surface role?!"); } // remove this surface from the known ones (as well as any leftover dead surface) data.shell_data @@ -644,7 +651,8 @@ where .with_role_data::(&data.wl_surface, |data| { data.pending_state = XdgSurfacePendingState::None; data.configured = false; - }).expect("xdg_popup exists but surface has not shell_surface role?!"); + }) + .expect("xdg_popup exists but surface has not shell_surface role?!"); } // remove this surface from the known ones (as well as any leftover dead surface) data.shell_data diff --git a/src/xwayland/xserver.rs b/src/xwayland/xserver.rs index 868129c..32eb4c2 100644 --- a/src/xwayland/xserver.rs +++ b/src/xwayland/xserver.rs @@ -97,7 +97,8 @@ impl XWayland { debug_assert!(evt.signal() == Signal::SIGUSR1); xwayland_ready(&inner); }, - ).map_err(|_| ()) + ) + .map_err(|_| ()) }), wayland_display: display, instance: None,