From 0698775153c13c7d37061b35f29d1e57d64591af Mon Sep 17 00:00:00 2001 From: Drakulix Date: Wed, 13 Sep 2017 22:51:35 +0200 Subject: [PATCH 01/18] drm: Introduce the Drm Backend - new backend rendering via egl via gbm directly on a drm device - refine EGLContext and EGLSurface dependencies through lifetimes - fixup the old winit backend to work with these changes - add new example using the drm backend instead - change GliumDrawer to be static for the drm example --- Cargo.toml | 18 +- examples/drm.rs | 349 +++++++++++++++++++++++++ examples/helpers/glium.rs | 30 ++- examples/{simple.rs => winit.rs} | 12 +- src/backend/drm/backend.rs | 419 +++++++++++++++++++++++++++++++ src/backend/drm/error.rs | 131 ++++++++++ src/backend/drm/mod.rs | 273 ++++++++++++++++++++ src/backend/graphics/egl.rs | 323 +++++++++++++++++------- src/backend/graphics/glium.rs | 8 +- src/backend/graphics/mod.rs | 7 +- src/backend/mod.rs | 2 + src/backend/winit.rs | 183 +++++++------- src/lib.rs | 5 + 13 files changed, 1563 insertions(+), 197 deletions(-) create mode 100644 examples/drm.rs rename examples/{simple.rs => winit.rs} (91%) create mode 100644 src/backend/drm/backend.rs create mode 100644 src/backend/drm/error.rs create mode 100644 src/backend/drm/mod.rs diff --git a/Cargo.toml b/Cargo.toml index 67db4f2..699d88d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,12 +13,15 @@ slog = { version = "2.0.0" } slog-stdlog = "2.0.0-0.2" libloading = "0.4.0" wayland-client = { version = "0.9.9", optional = true } -winit = { version = "0.7.0", optional = true } -glium = { version = "0.16.0", optional = true, default-features = false } +winit = { version = "0.7.5", optional = true } +drm = { version = "0.2.1", optional = true } +gbm = { version = "0.2.1", optional = true } +glium = { version = "0.17.1", optional = true, default-features = false } input = { version = "0.2.0", optional = true } clippy = { version = "*", optional = true } rental = "0.4.11" wayland-protocols = { version = "0.10.1", features = ["unstable_protocols", "server"] } +image = "0.15.0" [build-dependencies] gl_generator = "0.5" @@ -29,7 +32,14 @@ slog-async = "2.0" rand = "0.3" [features] -default = ["backend_winit", "backend_libinput", "renderer_glium"] +default = ["backend_winit", "backend_drm", "backend_libinput", "renderer_glium"] backend_winit = ["winit", "wayland-server/dlopen", "wayland-client/dlopen"] -renderer_glium = ["glium"] +backend_drm = ["drm", "gbm"] backend_libinput = ["input"] +renderer_glium = ["glium"] + +[replace] +"wayland-server:0.9.9" = { git = "https://github.com/Drakulix/wayland-rs", branch = "raw_handler_access"} +"wayland-protocols:0.9.9" = { git = "https://github.com/Drakulix/wayland-rs", branch = "raw_handler_access"} +"wayland-client:0.9.9" = { git = "https://github.com/Drakulix/wayland-rs", branch = "raw_handler_access"} +"drm:0.2.1" = { git = "https://github.com/Drakulix/drm-rs", branch = "fix/userdata" } diff --git a/examples/drm.rs b/examples/drm.rs new file mode 100644 index 0000000..bc65307 --- /dev/null +++ b/examples/drm.rs @@ -0,0 +1,349 @@ + +extern crate drm; +#[macro_use] +extern crate glium; +extern crate rand; +#[macro_use(define_roles)] +extern crate smithay; +extern crate wayland_protocols; +extern crate wayland_server; + +#[macro_use] +extern crate slog; +extern crate slog_async; +extern crate slog_term; + +mod helpers; + +use drm::control::{Device as ControlDevice, ResourceInfo}; +use drm::control::connector::{Info as ConnectorInfo, State as ConnectorState}; + +use glium::Surface; + +use helpers::GliumDrawer; +use slog::*; + +use smithay::backend::drm::{DrmBackend, DrmDevice, DrmHandler, Id}; +use smithay::backend::graphics::egl::EGLGraphicsBackend; +use smithay::backend::graphics::glium::{GliumGraphicsBackend, IntoGlium}; +use smithay::compositor::{self, CompositorHandler, CompositorToken, SubsurfaceRole, TraversalAction}; +use smithay::compositor::roles::Role; +use smithay::shell::{self, PopupConfigure, PopupSurface, ShellClient, ShellHandler, ShellSurfaceRole, + ToplevelConfigure, ToplevelSurface}; +use smithay::shm::{ShmGlobal, ShmToken}; + +use std::fs::OpenOptions; +use std::io::Error as IoError; +use std::time::Duration; +use std::os::unix::io::AsRawFd; + +use wayland_protocols::unstable::xdg_shell::server::{zxdg_shell_v6, zxdg_toplevel_v6}; + +use wayland_server::{Client, EventLoopHandle}; +use wayland_server::sources::READ; +use wayland_server::protocol::{wl_callback, wl_compositor, wl_output, wl_seat, wl_shell, wl_shm, + wl_subcompositor, wl_surface}; + +define_roles!(Roles => [ ShellSurface, ShellSurfaceRole ] ); + +struct SurfaceHandler { + shm_token: ShmToken, +} + +#[derive(Default)] +struct SurfaceData { + buffer: Option<(Vec, (u32, u32))>, + location: Option<(i32, i32)>, +} + +impl compositor::Handler for SurfaceHandler { + fn commit(&mut self, _evlh: &mut EventLoopHandle, _client: &Client, surface: &wl_surface::WlSurface, + token: CompositorToken) { + // we retrieve the contents of the associated buffer and copy it + token.with_surface_data(surface, |attributes| { + match attributes.buffer.take() { + Some(Some((buffer, (_x, _y)))) => { + // we ignore hotspot coordinates in this simple example + self.shm_token + .with_buffer_contents(&buffer, |slice, data| { + let offset = data.offset as usize; + let stride = data.stride as usize; + let width = data.width as usize; + let height = data.height as usize; + let mut new_vec = Vec::with_capacity(width * height * 4); + for i in 0..height { + new_vec.extend( + &slice[(offset + i * stride)..(offset + i * stride + width * 4)], + ); + } + attributes.user_data.buffer = + Some((new_vec, (data.width as u32, data.height as u32))); + }) + .unwrap(); + buffer.release(); + } + Some(None) => { + // erase the contents + attributes.user_data.buffer = None; + } + None => {} + } + }); + } + + fn frame(&mut self, _evlh: &mut EventLoopHandle, _client: &Client, _surface: &wl_surface::WlSurface, + callback: wl_callback::WlCallback, + _token: CompositorToken) { + callback.done(0); + } +} + +struct ShellSurfaceHandler { + token: CompositorToken, +} + +impl ShellSurfaceHandler { + fn new(token: CompositorToken) -> ShellSurfaceHandler { + ShellSurfaceHandler { token } + } +} + +impl shell::Handler for ShellSurfaceHandler { + fn new_client(&mut self, _evlh: &mut EventLoopHandle, _client: ShellClient<()>) {} + fn client_pong(&mut self, _evlh: &mut EventLoopHandle, _client: ShellClient<()>) {} + fn new_toplevel(&mut self, _evlh: &mut EventLoopHandle, + surface: ToplevelSurface) + -> ToplevelConfigure { + let wl_surface = surface.get_surface().unwrap(); + self.token.with_surface_data(wl_surface, |data| { + // place the window at a random location in the [0;300]x[0;300] square + use rand::distributions::{IndependentSample, Range}; + let range = Range::new(0, 300); + let mut rng = rand::thread_rng(); + let x = range.ind_sample(&mut rng); + let y = range.ind_sample(&mut rng); + data.user_data.location = Some((x, y)) + }); + ToplevelConfigure { + size: None, + states: vec![], + serial: 42, + } + } + fn new_popup(&mut self, _evlh: &mut EventLoopHandle, + _surface: PopupSurface) + -> PopupConfigure { + PopupConfigure { + size: (10, 10), + position: (10, 10), + serial: 42, + } + } + fn move_(&mut self, _evlh: &mut EventLoopHandle, + _surface: ToplevelSurface, _seat: &wl_seat::WlSeat, + _serial: u32) { + } + fn resize(&mut self, _evlh: &mut EventLoopHandle, + _surface: ToplevelSurface, _seat: &wl_seat::WlSeat, + _serial: u32, _edges: zxdg_toplevel_v6::ResizeEdge) { + } + fn grab(&mut self, _evlh: &mut EventLoopHandle, + _surface: PopupSurface, _seat: &wl_seat::WlSeat, + _serial: u32) { + } + fn change_display_state(&mut self, _evlh: &mut EventLoopHandle, + _surface: ToplevelSurface, + _maximized: Option, _minimized: Option, _fullscreen: Option, + _output: Option<&wl_output::WlOutput>) + -> ToplevelConfigure { + ToplevelConfigure { + size: None, + states: vec![], + serial: 42, + } + } + fn show_window_menu(&mut self, _evlh: &mut EventLoopHandle, + _surface: ToplevelSurface, + _seat: &wl_seat::WlSeat, _serial: u32, _x: i32, _y: i32) { + } +} + + +type MyCompositorHandler = CompositorHandler; +type MyShellHandler = ShellHandler; + +fn main() { + // A logger facility, here we use the terminal for this example + let log = Logger::root( + slog_async::Async::default(slog_term::term_full().fuse()).fuse(), + o!(), + ); + + // Initialize the wayland server + let (mut display, mut event_loop) = wayland_server::create_display(); + + // "Find" a suitable drm device + let mut options = OpenOptions::new(); + options.read(true); + options.write(true); + let mut device = + DrmDevice::new_from_file(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 crtc (should be successful in most cases) + let crtc = res_handles.crtcs()[0]; + + // 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 backends + let renderer = device + .create_backend(crtc, mode, vec![connector_info.handle()]) + .unwrap(); + + /* + * Initialize wl_shm global + */ + // Insert the ShmGlobal as a handler to your event loop + // Here, we specify tha the standard Argb8888 and Xrgb8888 is the only supported. + let shm_handler_id = event_loop.add_handler_with_init(ShmGlobal::new(vec![], log.clone())); + // Register this handler to advertise a wl_shm global of version 1 + event_loop.register_global::(shm_handler_id, 1); + // retreive the token + let shm_token = { + let state = event_loop.state(); + state.get_handler::(shm_handler_id).get_token() + }; + + + /* + * Initialize the compositor global + */ + let compositor_handler_id = event_loop.add_handler_with_init(MyCompositorHandler::new( + SurfaceHandler { + shm_token: shm_token.clone(), + }, + log.clone(), + )); + // register it to handle wl_compositor and wl_subcompositor + event_loop.register_global::(compositor_handler_id, 4); + event_loop + .register_global::(compositor_handler_id, 1); + // retrieve the tokens + let compositor_token = { + let state = event_loop.state(); + state + .get_handler::(compositor_handler_id) + .get_token() + }; + + /* + * Initialize the shell global + */ + let shell_handler_id = event_loop.add_handler_with_init(MyShellHandler::new( + ShellSurfaceHandler::new(compositor_token), + compositor_token, + log.clone(), + )); + event_loop.register_global::(shell_handler_id, 1); + event_loop.register_global::(shell_handler_id, 1); + + + /* + * Initialize glium + */ + let drawer = GliumDrawer::new(renderer.into_glium()); + let mut frame = drawer.draw(); + frame.clear_color(0.8, 0.8, 0.9, 1.0); + frame.finish().unwrap(); + + /* + * Add a listening socket: + */ + let name = display.add_socket_auto().unwrap().into_string().unwrap(); + println!("Listening on socket: {}", name); + + device.set_handler(DrmHandlerImpl { + drawer: drawer, + shell_handler_id, + compositor_token, + logger: log, + }); + + let fd = device.as_raw_fd(); + let drm_device_id = event_loop.add_handler(device); + let _drm_event_source = event_loop.add_fd_event_source::>(fd, drm_device_id, READ); + + event_loop.run().unwrap(); +} + +pub struct DrmHandlerImpl { + drawer: GliumDrawer>, + shell_handler_id: usize, + compositor_token: CompositorToken, + logger: ::slog::Logger, +} + +impl DrmHandler for DrmHandlerImpl { + fn ready(&mut self, evlh: &mut EventLoopHandle, _id: Id, _frame: u32, _duration: Duration) { + let mut frame = self.drawer.draw(); + frame.clear_color(0.8, 0.8, 0.9, 1.0); + // redraw the frame, in a simple but inneficient way + { + let screen_dimensions = self.drawer.get_framebuffer_dimensions(); + for toplevel_surface in unsafe { + evlh.get_handler_unchecked::(self.shell_handler_id) + .toplevel_surfaces() + } { + if let Some(wl_surface) = toplevel_surface.get_surface() { + // this surface is a root of a subsurface tree that needs to be drawn + let initial_place = self.compositor_token + .with_surface_data(wl_surface, |data| data.user_data.location.unwrap_or((0, 0))); + self.compositor_token + .with_surface_tree( + wl_surface, + initial_place, + |_surface, attributes, role, &(mut x, mut y)| { + if let Some((ref contents, (w, h))) = attributes.user_data.buffer { + // there is actually something to draw ! + if let Ok(subdata) = Role::::data(role) { + x += subdata.x; + y += subdata.y; + } + self.drawer.render( + &mut frame, + contents, + (w, h), + (x, y), + screen_dimensions, + ); + TraversalAction::DoChildren((x, y)) + } else { + // we are not display, so our children are neither + TraversalAction::SkipChildren + } + }, + ) + .unwrap(); + } + } + } + frame.finish().unwrap(); + } + + fn error(&mut self, _evlh: &mut EventLoopHandle, error: IoError) { + error!(self.logger, "{:?}", error); + } +} diff --git a/examples/helpers/glium.rs b/examples/helpers/glium.rs index d407b46..936d183 100644 --- a/examples/helpers/glium.rs +++ b/examples/helpers/glium.rs @@ -2,6 +2,8 @@ use glium; use glium::Surface; use glium::index::PrimitiveType; +use std::ops::Deref; + #[derive(Copy, Clone)] struct Vertex { position: [f32; 2], @@ -10,18 +12,26 @@ struct Vertex { implement_vertex!(Vertex, position, tex_coords); -pub struct GliumDrawer<'a, F: 'a> { - display: &'a F, +pub struct GliumDrawer { + display: F, vertex_buffer: glium::VertexBuffer, index_buffer: glium::IndexBuffer, program: glium::Program, } -impl<'a, F: glium::backend::Facade + 'a> GliumDrawer<'a, F> { - pub fn new(display: &'a F) -> GliumDrawer<'a, F> { +impl Deref for GliumDrawer { + type Target = F; + + fn deref(&self) -> &F { + &self.display + } +} + +impl GliumDrawer { + pub fn new(display: F) -> GliumDrawer { // building the vertex buffer, which contains all the vertices that we will draw let vertex_buffer = glium::VertexBuffer::new( - display, + &display, &[ Vertex { position: [0.0, 0.0], @@ -44,10 +54,10 @@ impl<'a, F: glium::backend::Facade + 'a> GliumDrawer<'a, F> { // building the index buffer let index_buffer = - glium::IndexBuffer::new(display, PrimitiveType::TriangleStrip, &[1 as u16, 2, 0, 3]).unwrap(); + glium::IndexBuffer::new(&display, PrimitiveType::TriangleStrip, &[1 as u16, 2, 0, 3]).unwrap(); // compiling shaders and linking them together - let program = program!(display, + let program = program!(&display, 100 => { vertex: " #version 100 @@ -84,15 +94,15 @@ impl<'a, F: glium::backend::Facade + 'a> GliumDrawer<'a, F> { } } - pub fn draw(&self, target: &mut glium::Frame, contents: &[u8], surface_dimensions: (u32, u32), - surface_location: (i32, i32), screen_size: (u32, u32)) { + pub fn render(&self, target: &mut glium::Frame, contents: &[u8], surface_dimensions: (u32, u32), + surface_location: (i32, i32), screen_size: (u32, u32)) { let image = glium::texture::RawImage2d { data: contents.into(), width: surface_dimensions.0, height: surface_dimensions.1, format: glium::texture::ClientFormat::U8U8U8U8, }; - let opengl_texture = glium::texture::Texture2d::new(self.display, image).unwrap(); + let opengl_texture = glium::texture::Texture2d::new(&self.display, image).unwrap(); let xscale = 2.0 * (surface_dimensions.0 as f32) / (screen_size.0 as f32); let yscale = -2.0 * (surface_dimensions.1 as f32) / (screen_size.1 as f32); diff --git a/examples/simple.rs b/examples/winit.rs similarity index 91% rename from examples/simple.rs rename to examples/winit.rs index 38ae92a..c1b63e9 100644 --- a/examples/simple.rs +++ b/examples/winit.rs @@ -15,6 +15,8 @@ mod helpers; use glium::Surface; use helpers::{shell_implementation, surface_implementation, GliumDrawer}; use slog::{Drain, Logger}; +use smithay::backend::graphics::egl::EGLGraphicsBackend; + use smithay::backend::graphics::glium::IntoGlium; use smithay::backend::input::InputBackend; use smithay::backend::winit; @@ -55,9 +57,7 @@ fn main() { /* * Initialize glium */ - let context = renderer.into_glium(); - - let drawer = GliumDrawer::new(&context); + let drawer = GliumDrawer::new(renderer.into_glium()); /* * Add a listening socket: @@ -68,11 +68,11 @@ fn main() { loop { input.dispatch_new_events().unwrap(); - let mut frame = context.draw(); + let mut frame = drawer.draw(); frame.clear(None, Some((0.8, 0.8, 0.9, 1.0)), false, None, None); // redraw the frame, in a simple but inneficient way { - let screen_dimensions = context.get_framebuffer_dimensions(); + let screen_dimensions = drawer.get_framebuffer_dimensions(); let state = event_loop.state(); for toplevel_surface in state.get(&shell_state_token).toplevel_surfaces() { if let Some(wl_surface) = toplevel_surface.get_surface() { @@ -90,7 +90,7 @@ fn main() { x += subdata.x; y += subdata.y; } - drawer.draw(&mut frame, contents, (w, h), (x, y), screen_dimensions); + drawer.render(&mut frame, contents, (w, h), (x, y), screen_dimensions); TraversalAction::DoChildren((x, y)) } else { // we are not display, so our children are neither diff --git a/src/backend/drm/backend.rs b/src/backend/drm/backend.rs new file mode 100644 index 0000000..b821a70 --- /dev/null +++ b/src/backend/drm/backend.rs @@ -0,0 +1,419 @@ + +use super::{DrmError, ModeError}; + +use super::devices; + +use backend::graphics::GraphicsBackend; +use backend::graphics::egl::{EGLGraphicsBackend, EGLSurface, PixelFormat, SwapBuffersError}; +use drm::buffer::Buffer; +use drm::control::{connector, crtc, framebuffer, Mode}; +use drm::control::ResourceInfo; + +use gbm::{BufferObject, BufferObjectFlags, Format as GbmFormat, Surface as GbmSurface, SurfaceBufferHandle}; + +use image::{ImageBuffer, Rgba}; + +use nix::c_void; + +use std::cell::{Cell, RefCell}; +use std::rc::Rc; + +pub struct DrmBackend(Rc>); + +impl DrmBackend { + pub(crate) fn new(drm: Rc>) -> DrmBackend { + DrmBackend(drm) + } +} + +/* + Dependency graph + - drm + - gbm + - context + - gbm_surface + - egl_surface + - gbm_buffers + - cursor +*/ + +pub(crate) struct GbmTypes<'dev, 'context> { + cursor: BufferObject<'dev, ()>, + surface: Surface<'context>, +} + +pub(crate) struct EGL<'gbm, 'context> { + surface: EGLSurface<'context, 'gbm, GbmSurface<'context, framebuffer::Info>>, + buffers: GbmBuffers<'gbm>, +} + +pub(crate) struct GbmBuffers<'gbm> { + front_buffer: Cell>, + next_buffer: Cell>>, +} + +rental! { + mod graphics { + use drm::control::framebuffer; + use gbm::Surface as GbmSurface; + use std::rc::Rc; + use super::devices::{Context, Context_Borrow}; + use super::GbmTypes; + + #[rental] + pub(crate) struct Surface<'context> { + gbm: Box>, + egl: super::EGL<'gbm, 'context>, + } + + #[rental] + pub(crate) struct Graphics { + #[subrental(arity = 3)] + context: Rc, + gbm: GbmTypes<'context_1, 'context_2>, + } + } +} +use self::graphics::{Graphics, Surface}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct Id(usize); + +impl Id { + pub(crate) fn raw(&self) -> usize { + self.0 + } +} + +pub(crate) struct DrmBackendInternal { + graphics: Graphics, + crtc: crtc::Handle, + mode: Mode, + connectors: Vec, + own_id: Id, + logger: ::slog::Logger, +} + +impl DrmBackendInternal { + pub(crate) fn new(context: Rc, crtc: crtc::Handle, mode: Mode, connectors: I, + own_id: usize, logger: L) + -> Result + where + I: Into>, + L: Into>, + { + let log = ::slog_or_stdlog(logger).new(o!("smithay_module" => "backend_drm")); + info!(log, "Initializing a drm backend"); + + let connectors = connectors.into(); + + for connector in connectors.iter() { + if !connector::Info::load_from_device(context.head().head(), *connector)? + .modes() + .contains(&mode) + { + return Err(DrmError::Mode(ModeError::ModeNotSuitable)); + } + } + + let (w, h) = mode.size(); + + info!(log, "Drm Backend initialized"); + + Ok(DrmBackendInternal { + graphics: Graphics::try_new(context, |context| { + debug!(log, "GBM EGLContext initialized"); + + Ok(GbmTypes { + cursor: { + // Create an unused cursor buffer (we don't want an Option here) + context.devices.gbm.create_buffer_object( + 1, + 1, + GbmFormat::ARGB8888, + &[BufferObjectFlags::Cursor, BufferObjectFlags::Write], + )? + }, + surface: Surface::try_new( + Box::new(context.devices.gbm.create_surface( + w as u32, + h as u32, + GbmFormat::XRGB8888, + &[BufferObjectFlags::Scanout, BufferObjectFlags::Rendering], + )?), + |surface| { + let egl_surface = context.egl.create_surface(&surface)?; + unsafe { egl_surface.make_current()? }; + egl_surface.swap_buffers()?; + + let mut front_bo = surface.lock_front_buffer()?; + debug!(log, "FrontBuffer color format: {:?}", front_bo.format()); + let fb = framebuffer::create(context.devices.drm, &*front_bo)?; + crtc::set( + context.devices.drm, + crtc, + fb.handle(), + &connectors, + (0, 0), + Some(mode), + )?; + front_bo.set_userdata(fb); + + Ok(EGL { + surface: egl_surface, + buffers: GbmBuffers { + front_buffer: Cell::new(front_bo), + next_buffer: Cell::new(None), + }, + }) + }, + ).map_err(DrmError::from)?, + }) + })?, + crtc, + mode, + connectors, + own_id: Id(own_id), + logger: log.clone(), + }) + } + + pub(crate) fn unlock_buffer(&self) { + self.graphics.rent(|gbm| { + gbm.surface.rent(|egl| { + let next_bo = egl.buffers.next_buffer.replace(None); + + if let Some(next_buffer) = next_bo { + egl.buffers.front_buffer.set(next_buffer); + // drop and release the old buffer + } else { + unreachable!(); + } + }) + }); + } +} + +impl DrmBackend { + pub fn add_connector(&mut self, connector: connector::Handle) -> Result<(), ModeError> { + let info = + connector::Info::load_from_device(self.0.borrow().graphics.head().head().head(), connector) + .map_err(|err| ModeError::FailedToLoad(err))?; + let mut internal = self.0.borrow_mut(); + if info.modes().contains(&internal.mode) { + internal.connectors.push(connector); + Ok(()) + } else { + Err(ModeError::ModeNotSuitable) + } + } + + pub fn used_connectors(&self) -> Vec { + self.0.borrow().connectors.clone() + } + + pub fn remove_connector(&mut self, connector: connector::Handle) { + self.0.borrow_mut().connectors.retain(|x| *x != connector); + } + + pub fn use_mode(&mut self, mode: Mode) -> Result<(), DrmError> { + for connector in self.0.borrow().connectors.iter() { + if !connector::Info::load_from_device(self.0.borrow().graphics.head().head().head(), *connector)? + .modes() + .contains(&mode) + { + return Err(DrmError::Mode(ModeError::ModeNotSuitable)); + } + } + + let crtc = self.0.borrow().crtc; + let mut internal = self.0.borrow_mut(); + let connectors = internal.connectors.clone(); + let logger = internal.logger.clone(); + + let (w, h) = mode.size(); + + internal + .graphics + .rent_all_mut(|graphics| -> Result<(), DrmError> { + graphics.gbm.surface = Surface::try_new( + Box::new(graphics.context.devices.gbm.create_surface( + w as u32, + h as u32, + GbmFormat::XRGB8888, + &[BufferObjectFlags::Scanout, BufferObjectFlags::Rendering], + )?), + |surface| { + let egl_surface = graphics.context.egl.create_surface(&surface)?; + unsafe { egl_surface.make_current()? }; + egl_surface.swap_buffers()?; + + let mut front_bo = surface.lock_front_buffer()?; + debug!(logger, "FrontBuffer color format: {:?}", front_bo.format()); + let fb = framebuffer::create(graphics.context.devices.drm, &*front_bo)?; + crtc::set( + graphics.context.devices.drm, + crtc, + fb.handle(), + &connectors, + (0, 0), + Some(mode), + )?; + front_bo.set_userdata(fb); + + Ok(EGL { + surface: egl_surface, + buffers: GbmBuffers { + front_buffer: Cell::new(front_bo), + next_buffer: Cell::new(None), + }, + }) + }, + )?; + + Ok(()) + })?; + + internal.mode = mode; + Ok(()) + } + + pub fn is(&self, id: Id) -> bool { + self.0.borrow().own_id == id + } +} + +impl GraphicsBackend for DrmBackend { + type CursorFormat = ImageBuffer, Vec>; + type Error = DrmError; + + fn set_cursor_position(&self, x: u32, y: u32) -> Result<(), DrmError> { + crtc::move_cursor( + self.0.borrow().graphics.head().head().head(), + self.0.borrow().crtc, + (x as i32, y as i32), + ).map_err(DrmError::from) + } + + fn set_cursor_representation(&self, buffer: ImageBuffer, Vec>, hotspot: (u32, u32)) + -> Result<(), DrmError> { + let (w, h) = buffer.dimensions(); + self.0 + .borrow_mut() + .graphics + .rent_all_mut(|graphics| -> Result<(), DrmError> { + graphics.gbm.cursor = { + let mut cursor = graphics.context.devices.gbm.create_buffer_object( + w, + h, + GbmFormat::ARGB8888, + &[BufferObjectFlags::Cursor, BufferObjectFlags::Write], + )?; + cursor.write(&*buffer.into_raw())?; + cursor + }; + Ok(()) + })?; + + if crtc::set_cursor2( + self.0.borrow().graphics.head().head().head(), + self.0.borrow().crtc, + self.0 + .borrow() + .graphics + .rent(|gbm| Buffer::handle(&gbm.cursor)), + (w, h), + (hotspot.0 as i32, hotspot.1 as i32), + ).is_err() + { + crtc::set_cursor( + self.0.borrow().graphics.head().head().head(), + self.0.borrow().crtc, + self.0 + .borrow() + .graphics + .rent(|gbm| Buffer::handle(&gbm.cursor)), + (w, h), + ).map_err(DrmError::from) + } else { + Ok(()) + } + } +} + +impl EGLGraphicsBackend for DrmBackend { + fn swap_buffers(&self) -> Result<(), SwapBuffersError> { + self.0.borrow().graphics.rent_all(|graphics| { + // We cannot call lock_front_buffer anymore without releasing the previous buffer, which will happen when the page flip is done + if graphics.gbm.surface.rent(|egl| { + let next = egl.buffers.next_buffer.take(); + let res = next.is_some(); + egl.buffers.next_buffer.set(next); + res + }) { + return Err(SwapBuffersError::AlreadySwapped); + } + + graphics.gbm.surface.rent(|egl| egl.surface.swap_buffers())?; + + graphics.gbm.surface.rent_all(|surface| { + // supporting this 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. + let mut next_bo = surface.gbm.lock_front_buffer().expect("Surface only has one front buffer. Not supported by smithay"); + + let maybe_fb = next_bo.userdata().cloned(); + let fb = if let Some(info) = maybe_fb { + info + } else { + let fb = framebuffer::create(graphics.context.devices.drm, &*next_bo).map_err(|_| SwapBuffersError::ContextLost)?; + next_bo.set_userdata(fb); + fb + }; + surface.egl.buffers.next_buffer.set(Some(next_bo)); + + trace!(self.0.borrow().logger, "Page flip queued"); + + let id: Id = self.0.borrow().own_id; + + crtc::page_flip(graphics.context.devices.drm, self.0.borrow().crtc, fb.handle(), &[crtc::PageFlipFlags::PageFlipEvent], id).map_err(|_| SwapBuffersError::ContextLost) + }) + }) + } + + unsafe fn get_proc_address(&self, symbol: &str) -> *const c_void { + self.0 + .borrow() + .graphics + .head() + .rent(|context| context.get_proc_address(symbol)) + } + + fn get_framebuffer_dimensions(&self) -> (u32, u32) { + let (w, h) = self.0.borrow().mode.size(); + (w as u32, h as u32) + } + + fn is_current(&self) -> bool { + self.0 + .borrow() + .graphics + .head() + .rent(|context| context.is_current()) + } + + unsafe fn make_current(&self) -> Result<(), SwapBuffersError> { + self.0 + .borrow() + .graphics + .rent(|gbm| gbm.surface.rent(|egl| egl.surface.make_current())) + } + + fn get_pixel_format(&self) -> PixelFormat { + self.0 + .borrow() + .graphics + .head() + .rent(|context| context.get_pixel_format()) + } +} diff --git a/src/backend/drm/error.rs b/src/backend/drm/error.rs new file mode 100644 index 0000000..9110671 --- /dev/null +++ b/src/backend/drm/error.rs @@ -0,0 +1,131 @@ + + +use backend::graphics::egl::{CreationError, SwapBuffersError}; +use drm::result::Error as DrmError; +use gbm::FrontBufferError; +use rental::TryNewError; + +use std::error::{self, Error as ErrorTrait}; +use std::fmt; +use std::io::Error as IoError; + +#[derive(Debug)] +pub enum Error { + Drm(DrmError), + EGLCreation(CreationError), + EGLSwap(SwapBuffersError), + Gbm(FrontBufferError), + Io(IoError), + Mode(ModeError), +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "DrmBackend error: {}", + match self { + &Error::Drm(ref x) => x as &error::Error, + &Error::EGLCreation(ref x) => x as &error::Error, + &Error::EGLSwap(ref x) => x as &error::Error, + &Error::Gbm(ref x) => x as &error::Error, + &Error::Io(ref x) => x as &error::Error, + &Error::Mode(ref x) => x as &error::Error, + } + ) + } +} + +impl error::Error for Error { + fn description(&self) -> &str { + "DrmBackend error" + } + + fn cause(&self) -> Option<&error::Error> { + match self { + &Error::Drm(ref x) => Some(x as &error::Error), + &Error::EGLCreation(ref x) => Some(x as &error::Error), + &Error::EGLSwap(ref x) => Some(x as &error::Error), + &Error::Gbm(ref x) => Some(x as &error::Error), + &Error::Io(ref x) => Some(x as &error::Error), + &Error::Mode(ref x) => Some(x as &error::Error), + } + } +} + +impl From for Error { + fn from(err: DrmError) -> Error { + Error::Drm(err) + } +} + +impl From for Error { + fn from(err: CreationError) -> Error { + Error::EGLCreation(err) + } +} + +impl From for Error { + fn from(err: SwapBuffersError) -> Error { + Error::EGLSwap(err) + } +} + +impl From for Error { + fn from(err: FrontBufferError) -> Error { + Error::Gbm(err) + } +} + +impl From for Error { + fn from(err: IoError) -> Error { + Error::Io(err) + } +} + +impl From for Error { + fn from(err: ModeError) -> Error { + match err { + err @ ModeError::ModeNotSuitable => Error::Mode(err), + ModeError::FailedToLoad(err) => Error::Drm(err), + } + } +} + +impl From> for Error { + fn from(err: TryNewError) -> Error { + err.0 + } +} + +#[derive(Debug)] +pub enum ModeError { + ModeNotSuitable, + FailedToLoad(DrmError), +} + +impl fmt::Display for ModeError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.description())?; + if let Some(cause) = self.cause() { + write!(f, "\tCause: {}", cause)?; + } + Ok(()) + } +} + +impl error::Error for ModeError { + fn description(&self) -> &str { + match self { + &ModeError::ModeNotSuitable => "Mode does not match all attached connectors", + &ModeError::FailedToLoad(_) => "Failed to load mode information from device", + } + } + + fn cause(&self) -> Option<&error::Error> { + match self { + &ModeError::FailedToLoad(ref err) => Some(err as &error::Error), + _ => None, + } + } +} diff --git a/src/backend/drm/mod.rs b/src/backend/drm/mod.rs new file mode 100644 index 0000000..68de54b --- /dev/null +++ b/src/backend/drm/mod.rs @@ -0,0 +1,273 @@ + + +use backend::graphics::egl::{EGLContext, GlAttributes, PixelFormatRequirements}; +use drm::Device as BasicDevice; +use drm::control::{connector, crtc, Mode}; +use drm::control::Device as ControlDevice; + +use gbm::Device as GbmDevice; + +use nix; + +use std::cell::RefCell; +use std::fs::File; +use std::io::Error as IoError; +use std::os::unix::io::{AsRawFd, RawFd}; +use std::rc::{Rc, Weak}; +use std::time::Duration; + +use wayland_server::{EventLoopHandle}; +use wayland_server::sources::{FdEventSourceHandler, FdInterest}; + +mod backend; +mod error; + +pub use self::backend::{DrmBackend, Id}; +use self::backend::DrmBackendInternal; +pub use self::error::{Error as DrmError, ModeError}; + +#[derive(Debug)] +pub(crate) struct DrmDev(File); + +impl AsRawFd for DrmDev { + fn as_raw_fd(&self) -> RawFd { + self.0.as_raw_fd() + } +} +impl BasicDevice for DrmDev {} +impl ControlDevice for DrmDev {} + +impl DrmDev { + unsafe fn new_from_fd(fd: RawFd) -> Self { + use std::os::unix::io::FromRawFd; + DrmDev(File::from_raw_fd(fd)) + } + + fn new_from_file(file: File) -> Self { + DrmDev(file) + } +} + +rental! { + mod devices { + use drm::control::framebuffer; + use gbm::{Device as GbmDevice, Surface as GbmSurface}; + + use ::backend::graphics::egl::EGLContext; + use super::DrmDev; + + #[rental] + pub(crate) struct Context { + #[subrental(arity = 2)] + devices: Box, + egl: EGLContext<'devices_1, GbmSurface<'devices_1, framebuffer::Info>>, + } + + #[rental] + pub(crate) struct Devices { + drm: Box, + gbm: GbmDevice<'drm>, + } + } +} +use self::devices::{Context, Devices}; + + +// odd naming, but makes sense for the user +pub struct DrmDevice { + context: Rc, + backends: Vec>>, + handler: Option, + logger: ::slog::Logger, +} + +impl DrmDevice { + pub unsafe fn new_from_fd(fd: RawFd, logger: L) -> Result + where + L: Into>, + { + DrmDevice::new( + DrmDev::new_from_fd(fd), + GlAttributes { + version: None, + profile: None, + debug: cfg!(debug_assertions), + vsync: true, + }, + logger, + ) + } + + pub unsafe fn new_from_fd_with_gl_attr(fd: RawFd, attributes: GlAttributes, logger: L) + -> Result + where + L: Into>, + { + DrmDevice::new(DrmDev::new_from_fd(fd), attributes, logger) + } + + pub fn new_from_file(file: File, logger: L) -> Result + where + L: Into>, + { + DrmDevice::new( + DrmDev::new_from_file(file), + GlAttributes { + version: None, + profile: None, + debug: cfg!(debug_assertions), + vsync: true, + }, + logger, + ) + } + + pub fn new_from_file_with_gl_attr(file: File, attributes: GlAttributes, logger: L) + -> Result + where + L: Into>, + { + DrmDevice::new(DrmDev::new_from_file(file), attributes, logger) + } + + fn new(drm: DrmDev, attributes: GlAttributes, logger: L) -> Result + where + L: Into>, + { + let log = ::slog_or_stdlog(logger).new(o!("smithay_module" => "backend_drm", "drm" => "device")); + + /* 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. + */ + unsafe { + nix::libc::dlopen( + "libglapi.so.0".as_ptr() as *const _, + nix::libc::RTLD_LAZY | nix::libc::RTLD_GLOBAL, + ); + } + + Ok(DrmDevice { + context: Rc::new(Context::try_new( + Box::new(Devices::try_new(Box::new(drm), |drm| { + GbmDevice::new_from_drm::>(drm).map_err(DrmError::from) + })?), + |devices| { + EGLContext::new_from_gbm( + devices.gbm, + attributes, + PixelFormatRequirements { + hardware_accelerated: Some(true), + color_bits: Some(24), + alpha_bits: Some(8), + ..Default::default() + }, + log.clone(), + ).map_err(DrmError::from) + }, + )?), + backends: Vec::new(), + handler: None, + logger: log, + }) + } + + pub fn create_backend(&mut self, crtc: crtc::Handle, mode: Mode, connectors: I) + -> Result + where + I: Into>, + { + let logger = self.logger + .new(o!("drm" => "backend", "crtc" => format!("{:?}", crtc))); + let own_id = self.backends.len(); + + let backend = Rc::new(RefCell::new(DrmBackendInternal::new( + self.context.clone(), + crtc, + mode, + connectors, + own_id, + logger, + )?)); + + self.backends.push(Rc::downgrade(&backend)); + + Ok(DrmBackend::new(backend)) + } + + pub fn set_handler(&mut self, handler: H) -> Option { + let res = self.handler.take(); + self.handler = Some(handler); + res + } + + pub fn clear_handler(&mut self) -> Option { + self.handler.take() + } +} + +// for users convinience +impl AsRawFd for DrmDevice { + fn as_raw_fd(&self) -> RawFd { + self.context.head().head().as_raw_fd() + } +} +impl BasicDevice for DrmDevice {} +impl ControlDevice for DrmDevice {} + +pub trait DrmHandler { + fn ready(&mut self, evlh: &mut EventLoopHandle, id: Id, frame: u32, duration: Duration); + fn error(&mut self, evlh: &mut EventLoopHandle, error: IoError); +} + +impl FdEventSourceHandler for DrmDevice { + fn ready(&mut self, evlh: &mut EventLoopHandle, fd: RawFd, _mask: FdInterest) { + use std::any::Any; + + struct DrmDeviceRef(RawFd); + impl AsRawFd for DrmDeviceRef { + fn as_raw_fd(&self) -> RawFd { + self.0 + } + } + impl BasicDevice for DrmDeviceRef {} + impl ControlDevice for DrmDeviceRef {} + + struct PageFlipHandler<'a, 'b, H: DrmHandler + 'static>( + &'a mut DrmDevice, + &'b mut EventLoopHandle, + ); + + impl<'a, 'b, H: DrmHandler + 'static> crtc::PageFlipHandler for PageFlipHandler<'a, 'b, H> { + fn handle_event(&mut self, _device: &DrmDeviceRef, frame: u32, duration: Duration, + userdata: Box) { + let id: Id = *userdata.downcast().unwrap(); + if let Some(backend) = self.0.backends[id.raw()].upgrade() { + backend.borrow().unlock_buffer(); + if let Some(handler) = self.0.handler.as_mut() { + handler.ready(self.1, id, frame, duration); + } + } + } + } + + crtc::handle_event( + &DrmDeviceRef(fd), + 2, + None::<&mut ()>, + Some(&mut PageFlipHandler( + self, + evlh, + )), + None::<&mut ()>, + ).unwrap(); + } + + fn error(&mut self, evlh: &mut EventLoopHandle, _fd: RawFd, error: IoError) { + if let Some(handler) = self.handler.as_mut() { + handler.error(evlh, error) + } + } +} diff --git a/src/backend/graphics/egl.rs b/src/backend/graphics/egl.rs index b6c5e51..fc91fe9 100644 --- a/src/backend/graphics/egl.rs +++ b/src/backend/graphics/egl.rs @@ -7,19 +7,29 @@ /// (see https://github.com/tomaka/glutin/tree/044e651edf67a2029eecc650dd42546af1501414/LICENSE) use super::GraphicsBackend; +#[cfg(feature = "backend_drm")] +use gbm::{AsRaw, Device as GbmDevice, Surface as GbmSurface}; use libloading::Library; use nix::{c_int, c_void}; use slog; -use std::error::{self, Error}; +use std::error::{self, Error}; use std::ffi::{CStr, CString}; use std::fmt; use std::io; +use std::marker::PhantomData; use std::mem; -use std::ops::Deref; +use std::ops::{Deref, DerefMut}; use std::ptr; +#[cfg(feature = "backend_winit")] +use wayland_client::egl as wegl; +#[cfg(feature = "backend_winit")] +use winit::Window as WinitWindow; +#[cfg(feature = "backend_winit")] +use winit::os::unix::WindowExt; + #[allow(non_camel_case_types, dead_code)] mod ffi { use nix::c_void; @@ -46,7 +56,7 @@ mod ffi { /// Native types to create an `EGLContext` from. /// Currently supported providers are X11, Wayland and GBM. #[derive(Clone, Copy)] -pub enum NativeDisplay { +enum NativeDisplayPtr { /// X11 Display to create an `EGLContext` upon. X11(ffi::NativeDisplayType), /// Wayland Display to create an `EGLContext` upon. @@ -58,7 +68,7 @@ pub enum NativeDisplay { /// Native types to create an `EGLSurface` from. /// Currently supported providers are X11, Wayland and GBM. #[derive(Clone, Copy)] -pub enum NativeSurface { +pub enum NativeSurfacePtr { /// X11 Window to create an `EGLSurface` upon. X11(ffi::NativeWindowType), /// Wayland Surface to create an `EGLSurface` upon. @@ -68,7 +78,7 @@ pub enum NativeSurface { } #[derive(Clone, Copy, PartialEq)] -enum NativeType { +pub enum NativeType { X11, Wayland, Gbm, @@ -135,8 +145,61 @@ impl error::Error for CreationError { } } +pub unsafe trait NativeSurface { + type Keep: 'static; + + fn surface(&self, backend: NativeType) -> Result<(NativeSurfacePtr, Self::Keep), CreationError>; +} + +#[cfg(feature = "backend_winit")] +unsafe impl NativeSurface for WinitWindow { + type Keep = Option; + + fn surface(&self, backend_type: NativeType) + -> Result<(NativeSurfacePtr, Option), CreationError> { + match backend_type { + NativeType::X11 => if let Some(window) = self.get_xlib_window() { + Ok((NativeSurfacePtr::X11(window), None)) + } else { + return Err(CreationError::NonMatchingSurfaceType); + }, + NativeType::Wayland => if let Some(surface) = self.get_wayland_surface() { + let (w, h) = self.get_inner_size().unwrap(); + let egl_surface = + unsafe { wegl::WlEglSurface::new_from_raw(surface as *mut _, w as i32, h as i32) }; + Ok(( + NativeSurfacePtr::Wayland(egl_surface.ptr() as *const _), + Some(egl_surface), + )) + } else { + return Err(CreationError::NonMatchingSurfaceType); + }, + _ => Err(CreationError::NonMatchingSurfaceType), + } + } +} + +#[cfg(feature = "backend_drm")] +unsafe impl<'a, T: 'static> NativeSurface for GbmSurface<'a, T> { + type Keep = (); + + fn surface(&self, backend: NativeType) -> Result<(NativeSurfacePtr, Self::Keep), CreationError> { + match backend { + NativeType::Gbm => Ok((NativeSurfacePtr::Gbm(self.as_raw() as *const _), ())), + _ => Err(CreationError::NonMatchingSurfaceType), + } + } +} + +unsafe impl NativeSurface for () { + type Keep = (); + fn surface(&self, _backend: NativeType) -> Result<(NativeSurfacePtr, ()), CreationError> { + Err(CreationError::NotSupported) + } +} + /// EGL context for rendering -pub struct EGLContext { +pub struct EGLContext<'a, T: NativeSurface> { _lib: Library, context: ffi::egl::types::EGLContext, display: ffi::egl::types::EGLDisplay, @@ -146,38 +209,67 @@ pub struct EGLContext { pixel_format: PixelFormat, backend_type: NativeType, logger: slog::Logger, + _lifetime: PhantomData<&'a ()>, + _type: PhantomData, } -/// EGL surface of a given egl context for rendering -pub struct EGLSurface<'a> { - context: &'a EGLContext, - surface: ffi::egl::types::EGLSurface, -} - -impl<'a> Deref for EGLSurface<'a> { - type Target = EGLContext; - fn deref(&self) -> &Self::Target { - self.context - } -} - -impl EGLContext { - /// Create a new EGL context - /// - /// # Unsafety - /// - /// This method is marked unsafe, because the contents of `NativeDisplay` cannot be verified and may - /// contain dangling pointers or similar unsafe content - pub unsafe fn new(native: NativeDisplay, mut attributes: GlAttributes, reqs: PixelFormatRequirements, - logger: L) - -> Result +impl<'a> EGLContext<'a, ()> { + #[cfg(feature = "backend_winit")] + pub fn new_from_winit(window: &'a WinitWindow, attributes: GlAttributes, + reqs: PixelFormatRequirements, logger: L) + -> Result, CreationError> where L: Into>, { - let logger = logger.into(); - let log = ::slog_or_stdlog(logger.clone()).new(o!("smithay_module" => "renderer_egl")); - trace!(log, "Loading libEGL"); + let log = ::slog_or_stdlog(logger.into()).new(o!("smithay_module" => "renderer_egl")); + info!(log, "Initializing from winit window"); + unsafe { + EGLContext::new( + if let Some(display) = window.get_xlib_display() { + debug!(log, "Window is backed by X11"); + NativeDisplayPtr::X11(display) + } else if let Some(display) = window.get_wayland_display() { + debug!(log, "Window is backed by Wayland"); + NativeDisplayPtr::Wayland(display) + } else { + error!(log, "Window is backed by an unsupported graphics framework"); + return Err(CreationError::NotSupported); + }, + attributes, + reqs, + log, + ) + } + } + + #[cfg(feature = "backend_drm")] + pub fn new_from_gbm(gbm: &'a GbmDevice<'a>, attributes: GlAttributes, + reqs: PixelFormatRequirements, logger: L) + -> Result>, CreationError> + where + L: Into>, + { + let log = ::slog_or_stdlog(logger.into()).new(o!("smithay_module" => "renderer_egl")); + info!(log, "Initializing from gbm device"); + unsafe { + EGLContext::new( + NativeDisplayPtr::Gbm(gbm.as_raw() as *const _), + attributes, + reqs, + log, + ) + } + } +} + +impl<'a, T: NativeSurface> EGLContext<'a, T> { + unsafe fn new(native: NativeDisplayPtr, mut attributes: GlAttributes, reqs: PixelFormatRequirements, + log: ::slog::Logger) + -> Result, CreationError> + where + T: NativeSurface, + { // If no version is given, try OpenGLES 3.0, if available, // fallback to 2.0 otherwise let version = match attributes.version { @@ -186,13 +278,13 @@ impl EGLContext { None => { debug!(log, "Trying to initialize EGL with OpenGLES 3.0"); attributes.version = Some((3, 0)); - match EGLContext::new(native, attributes, reqs, logger.clone()) { + match EGLContext::new(native, attributes, reqs, log.clone()) { Ok(x) => return Ok(x), Err(err) => { warn!(log, "EGL OpenGLES 3.0 Initialization failed with {}", err); debug!(log, "Trying to initialize EGL with OpenGLES 2.0"); attributes.version = Some((2, 0)); - return EGLContext::new(native, attributes, reqs, logger); + return EGLContext::new(native, attributes, reqs, log); } } } @@ -213,6 +305,7 @@ impl EGLContext { } }; + trace!(log, "Loading libEGL"); let lib = Library::new("libEGL.so.1")?; let egl = ffi::egl::Egl::load_with(|sym| { let name = CString::new(sym).unwrap(); @@ -243,35 +336,42 @@ impl EGLContext { let has_dp_extension = |e: &str| dp_extensions.iter().any(|s| s == e); let display = match native { - NativeDisplay::X11(display) + NativeDisplayPtr::X11(display) if has_dp_extension("EGL_KHR_platform_x11") && egl.GetPlatformDisplay.is_loaded() => { trace!(log, "EGL Display Initialization via EGL_KHR_platform_x11"); egl.GetPlatformDisplay(ffi::egl::PLATFORM_X11_KHR, display as *mut _, ptr::null()) } - NativeDisplay::X11(display) + NativeDisplayPtr::X11(display) if has_dp_extension("EGL_EXT_platform_x11") && egl.GetPlatformDisplayEXT.is_loaded() => { trace!(log, "EGL Display Initialization via EGL_EXT_platform_x11"); egl.GetPlatformDisplayEXT(ffi::egl::PLATFORM_X11_EXT, display as *mut _, ptr::null()) } - NativeDisplay::Gbm(display) + NativeDisplayPtr::Gbm(display) if has_dp_extension("EGL_KHR_platform_gbm") && egl.GetPlatformDisplay.is_loaded() => { trace!(log, "EGL Display Initialization via EGL_KHR_platform_gbm"); egl.GetPlatformDisplay(ffi::egl::PLATFORM_GBM_KHR, display as *mut _, ptr::null()) } - NativeDisplay::Gbm(display) + NativeDisplayPtr::Gbm(display) if has_dp_extension("EGL_MESA_platform_gbm") && egl.GetPlatformDisplayEXT.is_loaded() => { trace!(log, "EGL Display Initialization via EGL_MESA_platform_gbm"); - egl.GetPlatformDisplayEXT(ffi::egl::PLATFORM_GBM_KHR, display as *mut _, ptr::null()) + egl.GetPlatformDisplayEXT(ffi::egl::PLATFORM_GBM_MESA, display as *mut _, ptr::null()) } - NativeDisplay::Wayland(display) + NativeDisplayPtr::Gbm(display) + if has_dp_extension("EGL_MESA_platform_gbm") && egl.GetPlatformDisplay.is_loaded() => + { + trace!(log, "EGL Display Initialization via EGL_MESA_platform_gbm"); + egl.GetPlatformDisplay(ffi::egl::PLATFORM_GBM_MESA, display as *mut _, ptr::null()) + } + + NativeDisplayPtr::Wayland(display) if has_dp_extension("EGL_KHR_platform_wayland") && egl.GetPlatformDisplay.is_loaded() => { trace!( @@ -285,7 +385,7 @@ impl EGLContext { ) } - NativeDisplay::Wayland(display) + NativeDisplayPtr::Wayland(display) if has_dp_extension("EGL_EXT_platform_wayland") && egl.GetPlatformDisplayEXT.is_loaded() => { trace!( @@ -299,12 +399,18 @@ impl EGLContext { ) } - NativeDisplay::X11(display) | NativeDisplay::Gbm(display) | NativeDisplay::Wayland(display) => { + NativeDisplayPtr::X11(display) | + NativeDisplayPtr::Gbm(display) | + NativeDisplayPtr::Wayland(display) => { trace!(log, "Default EGL Display Initialization via GetDisplay"); egl.GetDisplay(display as *mut _) } }; + if display == ffi::egl::NO_DISPLAY { + error!(log, "EGL Display is not valid"); + } + let egl_version = { let mut major: ffi::egl::types::EGLint = mem::uninitialized(); let mut minor: ffi::egl::types::EGLint = mem::uninitialized(); @@ -583,11 +689,13 @@ impl EGLContext { surface_attributes: surface_attributes, pixel_format: desc, backend_type: match native { - NativeDisplay::X11(_) => NativeType::X11, - NativeDisplay::Wayland(_) => NativeType::Wayland, - NativeDisplay::Gbm(_) => NativeType::Gbm, + NativeDisplayPtr::X11(_) => NativeType::X11, + NativeDisplayPtr::Wayland(_) => NativeType::Wayland, + NativeDisplayPtr::Gbm(_) => NativeType::Gbm, }, logger: log, + _lifetime: PhantomData, + _type: PhantomData, }) } @@ -597,37 +705,37 @@ impl EGLContext { /// /// This method is marked unsafe, because the contents of `NativeSurface` cannot be verified and may /// contain dangling pointers or similar unsafe content - pub unsafe fn create_surface<'a>(&'a self, native: NativeSurface) - -> Result, CreationError> { + pub fn create_surface<'b>(&'a self, native: &'b T) -> Result, CreationError> { trace!(self.logger, "Creating EGL window surface..."); - let surface = { - let surface = match (native, self.backend_type) { - (NativeSurface::X11(window), NativeType::X11) | - (NativeSurface::Wayland(window), NativeType::Wayland) | - (NativeSurface::Gbm(window), NativeType::Gbm) => self.egl.CreateWindowSurface( - self.display, - self.config_id, - window, - self.surface_attributes.as_ptr(), - ), - _ => return Err(CreationError::NonMatchingSurfaceType), - }; + let (surface, keep) = native.surface(self.backend_type)?; - if surface.is_null() { - return Err(CreationError::OsError( - String::from("eglCreateWindowSurface failed"), - )); - } - - surface + let egl_surface = unsafe { + self.egl.CreateWindowSurface( + self.display, + self.config_id, + match surface { + NativeSurfacePtr::X11(ptr) => ptr, + NativeSurfacePtr::Wayland(ptr) => ptr, + NativeSurfacePtr::Gbm(ptr) => ptr, + }, + self.surface_attributes.as_ptr(), + ) }; - debug!(self.logger, "EGL window surface successfully created"); + if egl_surface.is_null() { + return Err(CreationError::OsError( + String::from("eglCreateWindowSurface failed"), + )); + } + + debug!(self.logger, "EGL surface successfully created"); Ok(EGLSurface { context: &self, - surface: surface, + surface: egl_surface, + keep, + _lifetime_surface: PhantomData, }) } @@ -651,7 +759,43 @@ impl EGLContext { } } -impl<'a> EGLSurface<'a> { +unsafe impl<'a, T: NativeSurface> Send for EGLContext<'a, T> {} +unsafe impl<'a, T: NativeSurface> Sync for EGLContext<'a, T> {} + +impl<'a, T: NativeSurface> Drop for EGLContext<'a, T> { + fn drop(&mut self) { + unsafe { + // we don't call MakeCurrent(0, 0) because we are not sure that the context + // is still the current one + self.egl + .DestroyContext(self.display as *const _, self.context as *const _); + self.egl.Terminate(self.display as *const _); + } + } +} + +/// EGL surface of a given egl context for rendering +pub struct EGLSurface<'context, 'surface, T: NativeSurface + 'context> { + context: &'context EGLContext<'context, T>, + surface: ffi::egl::types::EGLSurface, + keep: T::Keep, + _lifetime_surface: PhantomData<&'surface ()>, +} + +impl<'a, 'b, T: NativeSurface> Deref for EGLSurface<'a, 'b, T> { + type Target = T::Keep; + fn deref(&self) -> &Self::Target { + &self.keep + } +} + +impl<'a, 'b, T: NativeSurface> DerefMut for EGLSurface<'a, 'b, T> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.keep + } +} + +impl<'context, 'surface, T: NativeSurface> EGLSurface<'context, 'surface, T> { /// Swaps buffers at the end of a frame. pub fn swap_buffers(&self) -> Result<(), SwapBuffersError> { let ret = unsafe { @@ -695,24 +839,10 @@ impl<'a> EGLSurface<'a> { } } -unsafe impl Send for EGLContext {} -unsafe impl Sync for EGLContext {} -unsafe impl<'a> Send for EGLSurface<'a> {} -unsafe impl<'a> Sync for EGLSurface<'a> {} +unsafe impl<'a, 'b, T: NativeSurface> Send for EGLSurface<'a, 'b, T> {} +unsafe impl<'a, 'b, T: NativeSurface> Sync for EGLSurface<'a, 'b, T> {} -impl Drop for EGLContext { - fn drop(&mut self) { - unsafe { - // we don't call MakeCurrent(0, 0) because we are not sure that the context - // is still the current one - self.egl - .DestroyContext(self.display as *const _, self.context as *const _); - self.egl.Terminate(self.display as *const _); - } - } -} - -impl<'a> Drop for EGLSurface<'a> { +impl<'a, 'b, T: NativeSurface> Drop for EGLSurface<'a, 'b, T> { fn drop(&mut self) { unsafe { self.context @@ -744,6 +874,27 @@ pub enum SwapBuffersError { AlreadySwapped, } +impl fmt::Display for SwapBuffersError { + fn fmt(&self, formatter: &mut fmt::Formatter) -> Result<(), fmt::Error> { + write!(formatter, "{}", self.description()) + } +} + +impl 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" + } + } + } + + fn cause(&self) -> Option<&error::Error> { + None + } +} + /// Attributes to use when creating an OpenGL context. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct GlAttributes { diff --git a/src/backend/graphics/glium.rs b/src/backend/graphics/glium.rs index dacfb51..3a820d3 100644 --- a/src/backend/graphics/glium.rs +++ b/src/backend/graphics/glium.rs @@ -34,7 +34,7 @@ impl GliumGraphicsBackend { GliumGraphicsBackend { // cannot fail context: unsafe { - Context::new::<_, ()>(internal.clone(), false, DebugCallbackBehavior::default()).unwrap() + Context::new(internal.clone(), true, DebugCallbackBehavior::default()).unwrap() }, backend: internal, } @@ -56,10 +56,10 @@ impl GliumGraphicsBackend { } impl Deref for GliumGraphicsBackend { - type Target = Context; + type Target = T; - fn deref(&self) -> &Context { - &self.context + fn deref(&self) -> &T { + &self.backend.0 } } diff --git a/src/backend/graphics/mod.rs b/src/backend/graphics/mod.rs index cb04c21..5aa3238 100644 --- a/src/backend/graphics/mod.rs +++ b/src/backend/graphics/mod.rs @@ -8,6 +8,8 @@ pub trait GraphicsBackend { /// Format representing the image drawn for the cursor. type CursorFormat; + type Error; + /// Sets the cursor position and therefor updates the drawn cursors position. /// Useful as well for e.g. pointer wrapping. /// @@ -19,14 +21,15 @@ pub trait GraphicsBackend { /// 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(&mut self, x: u32, y: u32) -> Result<(), ()>; + fn set_cursor_position(&self, x: u32, y: u32) -> Result<(), Self::Error>; /// 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(&mut self, cursor: Self::CursorFormat); + fn set_cursor_representation(&self, cursor: Self::CursorFormat, hotspot: (u32, u32)) + -> Result<(), Self::Error>; } pub mod software; diff --git a/src/backend/mod.rs b/src/backend/mod.rs index 1102635..6835c02 100644 --- a/src/backend/mod.rs +++ b/src/backend/mod.rs @@ -18,6 +18,8 @@ pub mod graphics; #[cfg(feature = "backend_winit")] pub mod winit; +#[cfg(feature = "backend_drm")] +pub mod drm; #[cfg(feature = "backend_libinput")] pub mod libinput; diff --git a/src/backend/winit.rs b/src/backend/winit.rs index 3cf23d2..f1828be 100644 --- a/src/backend/winit.rs +++ b/src/backend/winit.rs @@ -2,8 +2,8 @@ use backend::{SeatInternal, TouchSlotInternal}; use backend::graphics::GraphicsBackend; -use backend::graphics::egl::{CreationError, EGLContext, EGLGraphicsBackend, GlAttributes, NativeDisplay, - NativeSurface, PixelFormat, PixelFormatRequirements, SwapBuffersError}; +use backend::graphics::egl::{CreationError, EGLContext, EGLGraphicsBackend, GlAttributes, PixelFormat, + PixelFormatRequirements, SwapBuffersError}; use backend::input::{Axis, AxisSource, Event as BackendEvent, InputBackend, InputHandler, KeyState, KeyboardKeyEvent, MouseButton, MouseButtonState, PointerAxisEvent, PointerButtonEvent, PointerMotionAbsoluteEvent, Seat, SeatCapabilities, TouchCancelEvent, TouchDownEvent, @@ -14,31 +14,36 @@ use std::cmp; use std::error::Error; use std::fmt; use std::rc::Rc; -use wayland_client::egl as wegl; use winit::{CreationError as WinitCreationError, ElementState, Event, EventsLoop, KeyboardInput, - MouseButton as WinitMouseButton, MouseCursor, MouseScrollDelta, Touch, TouchPhase, Window, + MouseButton as WinitMouseButton, MouseCursor, MouseScrollDelta, Touch, TouchPhase, WindowBuilder, WindowEvent}; -use winit::os::unix::{WindowExt, get_x11_xconnection}; rental! { - mod egl { + mod rental { use std::boxed::Box; + use ::winit::{Window as WinitWindow}; use ::backend::graphics::egl::{EGLContext, EGLSurface}; + #[rental] + pub struct Window { + window: Box, + egl: EGL<'window>, + } - #[rental(deref_suffix)] - pub struct RentEGL { - context: Box, - surface: EGLSurface<'context>, + #[rental] + pub struct EGL<'a> { + context: Box>, + surface: EGLSurface<'context, 'a, WinitWindow>, } } } +use self::rental::{Window, EGL}; + /// Window with an active EGL Context created by `winit`. Implements the /// `EGLGraphicsBackend` graphics backend trait pub struct WinitGraphicsBackend { window: Rc, - context: egl::RentEGL, logger: ::slog::Logger, } @@ -48,7 +53,6 @@ pub struct WinitGraphicsBackend { pub struct WinitInputBackend { events_loop: EventsLoop, window: Rc, - surface: Option, time_counter: u32, key_counter: u32, seat: Seat, @@ -107,69 +111,46 @@ where info!(log, "Initializing a winit backend"); let events_loop = EventsLoop::new(); - let window = Rc::new(builder.build(&events_loop)?); + let winit_window = builder.build(&events_loop)?; debug!(log, "Window created"); - let (native_display, native_surface, surface) = if let (Some(conn), Some(window)) = - (get_x11_xconnection(), window.get_xlib_window()) - { - debug!(log, "Window is backed by X11"); - ( - NativeDisplay::X11(conn.display as *const _), - NativeSurface::X11(window), - None, - ) - } else if let (Some(display), Some(surface)) = - (window.get_wayland_display(), window.get_wayland_surface()) - { - debug!(log, "Window is backed by Wayland"); - let (w, h) = window.get_inner_size().unwrap(); - let egl_surface = unsafe { wegl::WlEglSurface::new_from_raw(surface as *mut _, w as i32, h as i32) }; - ( - NativeDisplay::Wayland(display), - NativeSurface::Wayland(egl_surface.ptr() as *const _), - Some(egl_surface), - ) - } else { - error!(log, "Window is backed by an unsupported graphics framework"); - return Err(CreationError::NotSupported); - }; - - let context = unsafe { - match EGLContext::new( - native_display, - attributes, - PixelFormatRequirements { - hardware_accelerated: Some(true), - color_bits: Some(24), - alpha_bits: Some(8), - ..Default::default() - }, - log.clone(), + let window = match Window::try_new(Box::new(winit_window), |window| { + match EGL::try_new( + Box::new(match EGLContext::new_from_winit( + &*window, + attributes, + PixelFormatRequirements { + hardware_accelerated: Some(true), + color_bits: Some(24), + alpha_bits: Some(8), + ..Default::default() + }, + log.clone(), + ) { + Ok(context) => context, + Err(err) => { + error!(log, "EGLContext creation failed:\n {}", err); + return Err(err); + } + }), + |context| context.create_surface(window), ) { - Ok(context) => context, - Err(err) => { - error!(log, "EGLContext creation failed:\n {}", err); - return Err(err); - } + Ok(x) => Ok(x), + Err(::rental::TryNewError(err, _)) => return Err(err), } + }) { + Ok(x) => Rc::new(x), + Err(::rental::TryNewError(err, _)) => return Err(err), }; Ok(( WinitGraphicsBackend { window: window.clone(), - context: match egl::RentEGL::try_new(Box::new(context), move |context| unsafe { - context.create_surface(native_surface) - }) { - Ok(x) => x, - Err(::rental::TryNewError(err, _)) => return Err(err), - }, logger: log.new(o!("smithay_winit_component" => "graphics")), }, WinitInputBackend { - events_loop: events_loop, - window: window, - surface: surface, + events_loop, + window, time_counter: 0, key_counter: 0, seat: Seat::new( @@ -189,47 +170,52 @@ where impl GraphicsBackend for WinitGraphicsBackend { type CursorFormat = MouseCursor; + type Error = (); - fn set_cursor_position(&mut self, x: u32, y: u32) -> Result<(), ()> { + fn set_cursor_position(&self, x: u32, y: u32) -> Result<(), ()> { debug!(self.logger, "Setting cursor position to {:?}", (x, y)); - self.window.set_cursor_position(x as i32, y as i32) + self.window.head().set_cursor_position(x as i32, y as i32) } - fn set_cursor_representation(&mut self, cursor: Self::CursorFormat) { + fn set_cursor_representation(&self, cursor: Self::CursorFormat, _hotspot: (u32, u32)) -> Result<(), ()> { // Cannot log this one, as `CursorFormat` is not `Debug` and should not be debug!(self.logger, "Changing cursor representation"); - self.window.set_cursor(cursor) + self.window.head().set_cursor(cursor); + Ok(()) } } impl EGLGraphicsBackend for WinitGraphicsBackend { fn swap_buffers(&self) -> Result<(), SwapBuffersError> { trace!(self.logger, "Swapping buffers"); - self.context.rent(|surface| surface.swap_buffers()) + self.window + .rent(|egl| egl.rent(|surface| surface.swap_buffers())) } unsafe fn get_proc_address(&self, symbol: &str) -> *const c_void { trace!(self.logger, "Getting symbol for {:?}", symbol); - self.context.get_proc_address(symbol) + self.window.rent(|egl| egl.head().get_proc_address(symbol)) } fn get_framebuffer_dimensions(&self) -> (u32, u32) { self.window + .head() .get_inner_size_pixels() .expect("Window does not exist anymore") } fn is_current(&self) -> bool { - self.context.is_current() + self.window.rent(|egl| egl.head().is_current()) } unsafe fn make_current(&self) -> Result<(), SwapBuffersError> { debug!(self.logger, "Setting EGL context to be the current context"); - self.context.rent(|surface| surface.make_current()) + self.window + .rent(|egl| egl.rent(|surface| surface.make_current())) } fn get_pixel_format(&self) -> PixelFormat { - self.context.get_pixel_format() + self.window.rent(|egl| egl.head().get_pixel_format()) } } @@ -310,16 +296,24 @@ impl PointerMotionAbsoluteEvent for WinitMouseMovedEvent { fn x_transformed(&self, width: u32) -> u32 { cmp::min( - (self.x * width as f64 / self.window.get_inner_size_points().unwrap_or((width, 0)).0 as f64) as - u32, + (self.x * width as f64 / + self.window + .head() + .get_inner_size_points() + .unwrap_or((width, 0)) + .0 as f64) as u32, 0, ) } fn y_transformed(&self, height: u32) -> u32 { cmp::min( - (self.y * height as f64 / self.window.get_inner_size_points().unwrap_or((0, height)).1 as f64) as - u32, + (self.y * height as f64 / + self.window + .head() + .get_inner_size_points() + .unwrap_or((0, height)) + .1 as f64) as u32, 0, ) } @@ -416,7 +410,11 @@ impl TouchDownEvent for WinitTouchStartedEvent { fn x_transformed(&self, width: u32) -> u32 { cmp::min( self.location.0 as i32 * width as i32 / - self.window.get_inner_size_points().unwrap_or((width, 0)).0 as i32, + self.window + .head() + .get_inner_size_points() + .unwrap_or((width, 0)) + .0 as i32, 0, ) as u32 } @@ -424,7 +422,11 @@ impl TouchDownEvent for WinitTouchStartedEvent { fn y_transformed(&self, height: u32) -> u32 { cmp::min( self.location.1 as i32 * height as i32 / - self.window.get_inner_size_points().unwrap_or((0, height)).1 as i32, + self.window + .head() + .get_inner_size_points() + .unwrap_or((0, height)) + .1 as i32, 0, ) as u32 } @@ -459,11 +461,21 @@ impl TouchMotionEvent for WinitTouchMovedEvent { } fn x_transformed(&self, width: u32) -> u32 { - self.location.0 as u32 * width / self.window.get_inner_size_points().unwrap_or((width, 0)).0 + self.location.0 as u32 * width / + self.window + .head() + .get_inner_size_points() + .unwrap_or((width, 0)) + .0 } fn y_transformed(&self, height: u32) -> u32 { - self.location.1 as u32 * height / self.window.get_inner_size_points().unwrap_or((0, height)).1 + self.location.1 as u32 * height / + self.window + .head() + .get_inner_size_points() + .unwrap_or((0, height)) + .1 } } @@ -578,7 +590,6 @@ impl InputBackend for WinitInputBackend { let mut time_counter = &mut self.time_counter; let seat = &self.seat; let window = &self.window; - let surface = &self.surface; let mut handler = self.handler.as_mut(); let logger = &self.logger; @@ -587,10 +598,12 @@ impl InputBackend for WinitInputBackend { match (event, handler.as_mut()) { (WindowEvent::Resized(x, y), _) => { trace!(logger, "Resizing window to {:?}", (x, y)); - window.set_inner_size(x, y); - if let Some(wl_egl_surface) = surface.as_ref() { - wl_egl_surface.resize(x as i32, y as i32, 0, 0); - } + window.head().set_inner_size(x, y); + window.rent(|egl| { + egl.rent(|surface| if let Some(wegl_surface) = (**surface).as_ref() { + wegl_surface.resize(x as i32, y as i32, 0, 0) + }) + }); } ( WindowEvent::KeyboardInput { diff --git a/src/lib.rs b/src/lib.rs index 98ef926..76050ec 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,6 +7,7 @@ #![cfg_attr(feature = "clippy", feature(plugin))] #![cfg_attr(feature = "clippy", plugin(clippy))] +extern crate image; extern crate nix; #[macro_use] extern crate rental; @@ -15,6 +16,10 @@ extern crate wayland_protocols; extern crate wayland_server; extern crate xkbcommon; +#[cfg(feature = "backend_drm")] +extern crate drm; +#[cfg(feature = "backend_drm")] +extern crate gbm; #[cfg(feature = "backend_libinput")] extern crate input; #[cfg(feature = "backend_winit")] From f2bff6172b08920f57cf8c6fbb7636fcdcdb4cfb Mon Sep 17 00:00:00 2001 From: Drakulix Date: Thu, 14 Sep 2017 14:00:11 +0200 Subject: [PATCH 02/18] drm: Documentation --- src/backend/drm/backend.rs | 22 ++++ src/backend/drm/error.rs | 13 ++- src/backend/drm/mod.rs | 196 +++++++++++++++++++++++++++++++++++- src/backend/graphics/egl.rs | 22 ++-- src/backend/graphics/mod.rs | 1 + src/backend/mod.rs | 1 + 6 files changed, 240 insertions(+), 15 deletions(-) diff --git a/src/backend/drm/backend.rs b/src/backend/drm/backend.rs index b821a70..ef16315 100644 --- a/src/backend/drm/backend.rs +++ b/src/backend/drm/backend.rs @@ -18,6 +18,7 @@ use nix::c_void; use std::cell::{Cell, RefCell}; use std::rc::Rc; +/// Backend based on a `DrmDevice` and a given crtc pub struct DrmBackend(Rc>); impl DrmBackend { @@ -76,6 +77,9 @@ rental! { } use self::graphics::{Graphics, Surface}; +/// Id of a `DrmBackend` related to its `DrmDevice`. +/// +/// Used to track which `DrmBackend` finished page-flipping #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct Id(usize); @@ -195,6 +199,11 @@ impl DrmBackendInternal { } impl DrmBackend { + /// 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<(), ModeError> { let info = connector::Info::load_from_device(self.0.borrow().graphics.head().head().head(), connector) @@ -208,14 +217,23 @@ impl DrmBackend { } } + /// Returns a copy of the currently set connectors pub fn used_connectors(&self) -> Vec { self.0.borrow().connectors.clone() } + /// Removes a currently set connector pub fn remove_connector(&mut self, connector: connector::Handle) { self.0.borrow_mut().connectors.retain(|x| *x != connector); } + /// 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<(), DrmError> { for connector in self.0.borrow().connectors.iter() { if !connector::Info::load_from_device(self.0.borrow().graphics.head().head().head(), *connector)? @@ -278,6 +296,10 @@ impl DrmBackend { Ok(()) } + /// Checks of the `DrmBackend` is of the given `Id` + /// + /// Only produces valid results, if the `Id` is from the `DrmDevice`, + /// that created this backend. pub fn is(&self, id: Id) -> bool { self.0.borrow().own_id == id } diff --git a/src/backend/drm/error.rs b/src/backend/drm/error.rs index 9110671..c689ee2 100644 --- a/src/backend/drm/error.rs +++ b/src/backend/drm/error.rs @@ -1,5 +1,3 @@ - - use backend::graphics::egl::{CreationError, SwapBuffersError}; use drm::result::Error as DrmError; use gbm::FrontBufferError; @@ -9,13 +7,21 @@ use std::error::{self, Error as ErrorTrait}; use std::fmt; use std::io::Error as IoError; +/// Error summing up error types related to all underlying libraries +/// involved in creating the a `DrmDevice`/`DrmBackend` #[derive(Debug)] pub enum Error { + /// The `DrmDevice` has encountered an error on an ioctl Drm(DrmError), + /// The `EGLContext` could not be created EGLCreation(CreationError), + /// Swapping Buffers via EGL was not possible EGLSwap(SwapBuffersError), + /// Locking the front buffer of the underlying `GbmSurface` failed Gbm(FrontBufferError), + /// A generic IO-Error happened accessing the underlying devices Io(IoError), + /// Selected an invalid Mode Mode(ModeError), } @@ -98,9 +104,12 @@ impl From> for Error { } } +/// Error when trying to select an invalid mode #[derive(Debug)] pub enum ModeError { + /// `Mode` is not compatible with all given connectors ModeNotSuitable, + /// Failed to load `Mode` information FailedToLoad(DrmError), } diff --git a/src/backend/drm/mod.rs b/src/backend/drm/mod.rs index 68de54b..e11bc65 100644 --- a/src/backend/drm/mod.rs +++ b/src/backend/drm/mod.rs @@ -1,4 +1,144 @@ - +//! Drm/Kms types and backend implementations +//! +//! This module provide a `DrmDevice` which acts as a reprensentation for any drm +//! device and can be used to create the second provided structure a `DrmBackend`. +//! +//! The latter represents a crtc of the graphics card you can render to. +//! +//! +//! ## How to use it +//! +//! ### 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. +//! +//! 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,ignore +//! extern crate drm; +//! extern crate smithay; +//! # extern crate wayland_server; +//! +//! use drm::control::{Device as ControlDevice, ResourceInfo}; +//! use drm::control::connector::{Info as ConnectorInfo, State as ConnectorState}; +//! use std::fs::OpenOptions; +//! use smithay::backend::drm::DrmDevice; +//! +//! # fn main() { +//! // Open the drm device +//! let mut options = OpenOptions::new(); +//! options.read(true); +//! options.write(true); +//! let mut device = DrmDevice::new_from_file( +//! options.open("/dev/dri/card0").unwrap(), // try to detect it properly +//! None /*put a logger here*/ +//! ).unwrap(); +//! +//! // Get a set of all modesetting resource handles +//! let res_handles = device.resource_handles().unwrap(); +//! +//! // 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 first crtc (should be successful in most cases) +//! let crtc = res_handles.crtcs()[0]; +//! +//! // 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 happend, so you can render the next frame immediately +//! and get a tear-free reprensentation 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::control::{Device as ControlDevice, ResourceInfo}; +//! # use drm::control::connector::{Info as ConnectorInfo, State as ConnectorState}; +//! use std::io::Error as IoError; +//! use std::os::unix::io::AsRawFd; +//! # use std::fs::OpenOptions; +//! # use std::time::Duration; +//! use smithay::backend::drm::{DrmDevice, DrmBackend, DrmHandler, Id}; +//! use smithay::backend::graphics::egl::EGLGraphicsBackend; +//! use wayland_server::sources::READ; +//! # use wayland_server::EventLoopHandle; +//! # +//! # fn main() { +//! # +//! # let (_display, mut event_loop) = wayland_server::create_display(); +//! # +//! # let mut options = OpenOptions::new(); +//! # options.read(true); +//! # options.write(true); +//! # let mut device = DrmDevice::new_from_file( +//! # 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, _: &mut EventLoopHandle, id: Id, _frame: u32, _duration: Duration) { +//! if self.0.is(id) { // check id in case you got multiple backends +//! // ... render surfaces ... +//! self.0.swap_buffers().unwrap(); // trigger the swap +//! } +//! } +//! fn error(&mut self, _: &mut EventLoopHandle, error: IoError) { +//! panic!("DrmDevice errored: {}", error); +//! } +//! } +//! +//! // render something (like clear_color) +//! backend.swap_buffers().unwrap(); +//! +//! device.set_handler(MyDrmHandler(backend)); +//! let fd = device.as_raw_fd(); +//! let drm_device_id = event_loop.add_handler(device); +//! let _drm_event_source = event_loop.add_fd_event_source::>(fd, drm_device_id, READ); +//! +//! event_loop.run().unwrap(); +//! # } +//! ``` use backend::graphics::egl::{EGLContext, GlAttributes, PixelFormatRequirements}; use drm::Device as BasicDevice; @@ -26,6 +166,7 @@ pub use self::backend::{DrmBackend, Id}; use self::backend::DrmBackendInternal; pub use self::error::{Error as DrmError, ModeError}; +/// Internal struct as required by the drm crate #[derive(Debug)] pub(crate) struct DrmDev(File); @@ -72,8 +213,7 @@ rental! { } use self::devices::{Context, Devices}; - -// odd naming, but makes sense for the user +/// Representation of an open drm device node to create rendering backends pub struct DrmDevice { context: Rc, backends: Vec>>, @@ -82,6 +222,14 @@ pub struct DrmDevice { } impl DrmDevice { + /// Create a new `DrmDevice` from a raw file descriptor + /// + /// Returns an error of opening the device failed or context creation was not + /// successful. + /// + /// # Safety + /// The file descriptor might not be valid and needs to be owned by smithay, + /// make sure not to share it. Otherwise undefined behavior might occur. pub unsafe fn new_from_fd(fd: RawFd, logger: L) -> Result where L: Into>, @@ -98,6 +246,14 @@ impl DrmDevice { ) } + /// Create a new `DrmDevice` from a raw file descriptor and given `GlAttributes` + /// + /// Returns an error of opening the device failed or context creation was not + /// successful. + /// + /// # Safety + /// The file descriptor might not be valid and needs to be owned by smithay, + /// make sure not to share it. Otherwise undefined behavior might occur. pub unsafe fn new_from_fd_with_gl_attr(fd: RawFd, attributes: GlAttributes, logger: L) -> Result where @@ -106,6 +262,10 @@ impl DrmDevice { DrmDevice::new(DrmDev::new_from_fd(fd), attributes, logger) } + /// Create a new `DrmDevice` from a `File` of an open drm node + /// + /// Returns an error if the file is no valid drm node or context creation was not + /// successful. pub fn new_from_file(file: File, logger: L) -> Result where L: Into>, @@ -122,6 +282,10 @@ impl DrmDevice { ) } + /// Create a new `DrmDevice` from a `File` of an open drm node and given `GlAttributes` + /// + /// Returns an error if the file is no valid drm node or context creation was not + /// successful. pub fn new_from_file_with_gl_attr(file: File, attributes: GlAttributes, logger: L) -> Result where @@ -149,6 +313,7 @@ impl DrmDevice { ); } + // Open the gbm device from the drm device and create a context based on that Ok(DrmDevice { context: Rc::new(Context::try_new( Box::new(Devices::try_new(Box::new(drm), |drm| { @@ -174,6 +339,11 @@ impl DrmDevice { }) } + /// Create a new backend on a given crtc with a given `Mode` for a given amount + /// of `connectors` (mirroring). + /// + /// Errors if initialization fails or the mode is not available on all given + /// connectors. pub fn create_backend(&mut self, crtc: crtc::Handle, mode: Mode, connectors: I) -> Result where @@ -183,6 +353,8 @@ impl DrmDevice { .new(o!("drm" => "backend", "crtc" => format!("{:?}", crtc))); let own_id = self.backends.len(); + // TODO: Make sure we do not initialize the same crtc multiple times + // (check weak pointers and return an error otherwise) let backend = Rc::new(RefCell::new(DrmBackendInternal::new( self.context.clone(), crtc, @@ -197,18 +369,20 @@ impl DrmDevice { Ok(DrmBackend::new(backend)) } + /// Set a handler for handling finished rendering pub fn set_handler(&mut self, handler: H) -> Option { let res = self.handler.take(); self.handler = Some(handler); res } + /// Clear the currently set handler pub fn clear_handler(&mut self) -> Option { self.handler.take() } } -// for users convinience +// for users convinience and FdEventSource registering impl AsRawFd for DrmDevice { fn as_raw_fd(&self) -> RawFd { self.context.head().head().as_raw_fd() @@ -217,8 +391,20 @@ impl AsRawFd for DrmDevice { impl BasicDevice for DrmDevice {} impl ControlDevice for DrmDevice {} +/// Handler for drm node events +/// +/// See module-level documentation for its use pub trait DrmHandler { + /// A `DrmBackend` has finished swapping buffers and new frame can now + /// (and should be immediately) be rendered. + /// + /// The `id` argument is the `Id` of the `DrmBackend` that finished rendering, + /// check using `DrmBackend::is`. fn ready(&mut self, evlh: &mut EventLoopHandle, id: Id, 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, evlh: &mut EventLoopHandle, error: IoError); } @@ -245,8 +431,10 @@ impl FdEventSourceHandler for DrmDevice { userdata: Box) { let id: Id = *userdata.downcast().unwrap(); if let Some(backend) = self.0.backends[id.raw()].upgrade() { + // we can now unlock the buffer backend.borrow().unlock_buffer(); if let Some(handler) = self.0.handler.as_mut() { + // and then call the user to render the next frame handler.ready(self.1, id, frame, duration); } } diff --git a/src/backend/graphics/egl.rs b/src/backend/graphics/egl.rs index fc91fe9..d08c8e4 100644 --- a/src/backend/graphics/egl.rs +++ b/src/backend/graphics/egl.rs @@ -5,15 +5,12 @@ /// /// It therefore falls under glutin's Apache 2.0 license /// (see https://github.com/tomaka/glutin/tree/044e651edf67a2029eecc650dd42546af1501414/LICENSE) - use super::GraphicsBackend; #[cfg(feature = "backend_drm")] use gbm::{AsRaw, Device as GbmDevice, Surface as GbmSurface}; - use libloading::Library; use nix::{c_int, c_void}; use slog; - use std::error::{self, Error}; use std::ffi::{CStr, CString}; use std::fmt; @@ -22,7 +19,6 @@ use std::marker::PhantomData; use std::mem; use std::ops::{Deref, DerefMut}; use std::ptr; - #[cfg(feature = "backend_winit")] use wayland_client::egl as wegl; #[cfg(feature = "backend_winit")] @@ -77,10 +73,14 @@ pub enum NativeSurfacePtr { Gbm(ffi::NativeWindowType), } +/// Enumerates all supported backends #[derive(Clone, Copy, PartialEq)] pub enum NativeType { + /// X11 window & surface X11, + /// Wayland surface Wayland, + /// Gbm surface Gbm, } @@ -145,9 +145,16 @@ impl error::Error for CreationError { } } +/// Trait for supported types returning valid surface pointers for initializing egl +/// +/// # Safety +/// The returned `NativeSurfacePtr` must be valid for egl +/// and there is no way to test that. pub unsafe trait NativeSurface { + /// Type to keep the surface valid, if needed type Keep: 'static; + /// Return a surface for the given type if possible fn surface(&self, backend: NativeType) -> Result<(NativeSurfacePtr, Self::Keep), CreationError>; } @@ -214,6 +221,7 @@ pub struct EGLContext<'a, T: NativeSurface> { } impl<'a> EGLContext<'a, ()> { + /// Create a new context from a given `winit`-`Window` #[cfg(feature = "backend_winit")] pub fn new_from_winit(window: &'a WinitWindow, attributes: GlAttributes, reqs: PixelFormatRequirements, logger: L) @@ -243,6 +251,7 @@ impl<'a> EGLContext<'a, ()> { } } + /// Create a new context from a given `gbm::Device` #[cfg(feature = "backend_drm")] pub fn new_from_gbm(gbm: &'a GbmDevice<'a>, attributes: GlAttributes, reqs: PixelFormatRequirements, logger: L) @@ -700,11 +709,6 @@ impl<'a, T: NativeSurface> EGLContext<'a, T> { } /// Creates a surface bound to the given egl context for rendering - /// - /// # Unsafety - /// - /// This method is marked unsafe, because the contents of `NativeSurface` cannot be verified and may - /// contain dangling pointers or similar unsafe content pub fn create_surface<'b>(&'a self, native: &'b T) -> Result, CreationError> { trace!(self.logger, "Creating EGL window surface..."); diff --git a/src/backend/graphics/mod.rs b/src/backend/graphics/mod.rs index 5aa3238..0a082d7 100644 --- a/src/backend/graphics/mod.rs +++ b/src/backend/graphics/mod.rs @@ -8,6 +8,7 @@ pub trait GraphicsBackend { /// Format representing the image drawn for the cursor. type CursorFormat; + /// Error the underlying backend throws if operations fail type Error; /// Sets the cursor position and therefor updates the drawn cursors position. diff --git a/src/backend/mod.rs b/src/backend/mod.rs index 6835c02..09bb18f 100644 --- a/src/backend/mod.rs +++ b/src/backend/mod.rs @@ -7,6 +7,7 @@ //! Supported graphics backends: //! //! - winit +//! - drm //! //! Supported input backends: //! From 7bf41050505f214d144679e50de6bfbcd0734e67 Mon Sep 17 00:00:00 2001 From: Drakulix Date: Thu, 14 Sep 2017 18:47:19 +0200 Subject: [PATCH 03/18] drm: Add error for multi-use of a single crtc --- src/backend/drm/backend.rs | 4 ++++ src/backend/drm/error.rs | 41 ++++++++++++++++++++++++++++++++++++++ src/backend/drm/mod.rs | 12 ++++++++--- 3 files changed, 54 insertions(+), 3 deletions(-) diff --git a/src/backend/drm/backend.rs b/src/backend/drm/backend.rs index ef16315..57cc563 100644 --- a/src/backend/drm/backend.rs +++ b/src/backend/drm/backend.rs @@ -196,6 +196,10 @@ impl DrmBackendInternal { }) }); } + + pub(crate) fn is_crtc(&self, crtc: crtc::Handle) -> bool { + crtc == self.crtc + } } impl DrmBackend { diff --git a/src/backend/drm/error.rs b/src/backend/drm/error.rs index c689ee2..5e05e5c 100644 --- a/src/backend/drm/error.rs +++ b/src/backend/drm/error.rs @@ -23,6 +23,8 @@ pub enum Error { Io(IoError), /// Selected an invalid Mode Mode(ModeError), + /// Error related to the selected crtc + Crtc(CrtcError), } impl fmt::Display for Error { @@ -37,6 +39,7 @@ impl fmt::Display for Error { &Error::Gbm(ref x) => x as &error::Error, &Error::Io(ref x) => x as &error::Error, &Error::Mode(ref x) => x as &error::Error, + &Error::Crtc(ref x) => x as &error::Error, } ) } @@ -55,6 +58,7 @@ impl error::Error for Error { &Error::Gbm(ref x) => Some(x as &error::Error), &Error::Io(ref x) => Some(x as &error::Error), &Error::Mode(ref x) => Some(x as &error::Error), + &Error::Crtc(ref x) => Some(x as &error::Error), } } } @@ -98,6 +102,12 @@ impl From for Error { } } +impl From for Error { + fn from(err: CrtcError) -> Error { + Error::Crtc(err) + } +} + impl From> for Error { fn from(err: TryNewError) -> Error { err.0 @@ -138,3 +148,34 @@ impl error::Error for ModeError { } } } + +/// Errors related to the selected crtc +#[derive(Debug)] +pub enum CrtcError { + /// Selected crtc is already in use by another `DrmBackend` + AlreadyInUse +} + +impl fmt::Display for CrtcError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.description())?; + if let Some(cause) = self.cause() { + write!(f, "\tCause: {}", cause)?; + } + Ok(()) + } +} + +impl error::Error for CrtcError { + fn description(&self) -> &str { + match self { + &CrtcError::AlreadyInUse => "Crtc is already in use by another DrmBackend", + } + } + + fn cause(&self) -> Option<&error::Error> { + match self { + _ => None, + } + } +} diff --git a/src/backend/drm/mod.rs b/src/backend/drm/mod.rs index e11bc65..19bfd09 100644 --- a/src/backend/drm/mod.rs +++ b/src/backend/drm/mod.rs @@ -164,7 +164,7 @@ mod error; pub use self::backend::{DrmBackend, Id}; use self::backend::DrmBackendInternal; -pub use self::error::{Error as DrmError, ModeError}; +pub use self::error::{Error as DrmError, ModeError, CrtcError}; /// Internal struct as required by the drm crate #[derive(Debug)] @@ -349,12 +349,18 @@ impl DrmDevice { where I: Into>, { + for backend in self.backends.iter() { + if let Some(backend) = backend.upgrade() { + if backend.borrow().is_crtc(crtc) { + return Err(DrmError::Crtc(CrtcError::AlreadyInUse)); + } + } + } + let logger = self.logger .new(o!("drm" => "backend", "crtc" => format!("{:?}", crtc))); let own_id = self.backends.len(); - // TODO: Make sure we do not initialize the same crtc multiple times - // (check weak pointers and return an error otherwise) let backend = Rc::new(RefCell::new(DrmBackendInternal::new( self.context.clone(), crtc, From 9c71793b0ec1a0999f896c41f6f1bb50849ea6a1 Mon Sep 17 00:00:00 2001 From: Drakulix Date: Thu, 14 Sep 2017 22:27:35 +0200 Subject: [PATCH 04/18] drm: Add more inline comments explaining the drm code --- src/backend/drm/backend.rs | 34 ++++++++++++++++++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/src/backend/drm/backend.rs b/src/backend/drm/backend.rs index 57cc563..a8c3f1e 100644 --- a/src/backend/drm/backend.rs +++ b/src/backend/drm/backend.rs @@ -111,6 +111,7 @@ impl DrmBackendInternal { let connectors = connectors.into(); + // check the connectors, if they suite the mode for connector in connectors.iter() { if !connector::Info::load_from_device(context.head().head(), *connector)? .modes() @@ -122,7 +123,7 @@ impl DrmBackendInternal { let (w, h) = mode.size(); - info!(log, "Drm Backend initialized"); + info!(log, "Drm Backend initializing"); Ok(DrmBackendInternal { graphics: Graphics::try_new(context, |context| { @@ -139,6 +140,7 @@ impl DrmBackendInternal { )? }, surface: Surface::try_new( + // create a gbm surface Box::new(context.devices.gbm.create_surface( w as u32, h as u32, @@ -146,12 +148,18 @@ impl DrmBackendInternal { &[BufferObjectFlags::Scanout, BufferObjectFlags::Rendering], )?), |surface| { + // create an egl surface from the gbm one let egl_surface = context.egl.create_surface(&surface)?; + + // set it to be able to use `crtc::set` once unsafe { egl_surface.make_current()? }; egl_surface.swap_buffers()?; + // init the first screen + // (must be done before calling page_flip for the first time) let mut front_bo = surface.lock_front_buffer()?; debug!(log, "FrontBuffer color format: {:?}", front_bo.format()); + // we need a framebuffer per front buffer let fb = framebuffer::create(context.devices.drm, &*front_bo)?; crtc::set( context.devices.drm, @@ -183,6 +191,8 @@ 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 self.graphics.rent(|gbm| { gbm.surface.rent(|egl| { let next_bo = egl.buffers.next_buffer.replace(None); @@ -212,6 +222,8 @@ impl DrmBackend { let info = connector::Info::load_from_device(self.0.borrow().graphics.head().head().head(), connector) .map_err(|err| ModeError::FailedToLoad(err))?; + + // check if the connector can handle the current mode let mut internal = self.0.borrow_mut(); if info.modes().contains(&internal.mode) { internal.connectors.push(connector); @@ -223,6 +235,7 @@ impl DrmBackend { /// Returns a copy of the currently set connectors pub fn used_connectors(&self) -> Vec { + // thanks to the RefCell we can sadly not return a `&[connector::Handle]` self.0.borrow().connectors.clone() } @@ -239,6 +252,7 @@ impl DrmBackend { /// 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<(), DrmError> { + // check the connectors for connector in self.0.borrow().connectors.iter() { if !connector::Info::load_from_device(self.0.borrow().graphics.head().head().head(), *connector)? .modes() @@ -248,6 +262,8 @@ impl DrmBackend { } } + // borrow & clone stuff because rust cannot figure out the upcoming + // closure otherwise. let crtc = self.0.borrow().crtc; let mut internal = self.0.borrow_mut(); let connectors = internal.connectors.clone(); @@ -258,6 +274,8 @@ impl DrmBackend { internal .graphics .rent_all_mut(|graphics| -> Result<(), DrmError> { + // Recreate the surface and the related resources to match the new + // resolution. graphics.gbm.surface = Surface::try_new( Box::new(graphics.context.devices.gbm.create_surface( w as u32, @@ -267,12 +285,17 @@ impl DrmBackend { )?), |surface| { let egl_surface = graphics.context.egl.create_surface(&surface)?; + + // make it active for the first crtc::set + // (which is needed before the first page_flip) unsafe { egl_surface.make_current()? }; egl_surface.swap_buffers()?; let mut front_bo = surface.lock_front_buffer()?; debug!(logger, "FrontBuffer color format: {:?}", front_bo.format()); + // we need a framebuffer per front_buffer let fb = framebuffer::create(graphics.context.devices.drm, &*front_bo)?; + // init the first screen crtc::set( graphics.context.devices.drm, crtc, @@ -324,6 +347,7 @@ impl GraphicsBackend for DrmBackend { fn set_cursor_representation(&self, buffer: ImageBuffer, Vec>, hotspot: (u32, u32)) -> Result<(), DrmError> { let (w, h) = buffer.dimensions(); + /// import the cursor into a buffer we can render self.0 .borrow_mut() .graphics @@ -341,6 +365,7 @@ impl GraphicsBackend for DrmBackend { Ok(()) })?; + // and set it if crtc::set_cursor2( self.0.borrow().graphics.head().head().head(), self.0.borrow().crtc, @@ -380,14 +405,18 @@ impl EGLGraphicsBackend for DrmBackend { return Err(SwapBuffersError::AlreadySwapped); } + /// flip normally graphics.gbm.surface.rent(|egl| egl.surface.swap_buffers())?; graphics.gbm.surface.rent_all(|surface| { - // supporting this would cause a lot of inconvinience and + // supporting this error 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 = surface.gbm.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().cloned(); let fb = if let Some(info) = maybe_fb { info @@ -402,6 +431,7 @@ impl EGLGraphicsBackend for DrmBackend { let id: Id = self.0.borrow().own_id; + // and flip crtc::page_flip(graphics.context.devices.drm, self.0.borrow().crtc, fb.handle(), &[crtc::PageFlipFlags::PageFlipEvent], id).map_err(|_| SwapBuffersError::ContextLost) }) }) From 73d262e292a4bc2e738b5635fce91ce45c74cf5c Mon Sep 17 00:00:00 2001 From: Drakulix Date: Thu, 14 Sep 2017 22:32:53 +0200 Subject: [PATCH 05/18] drm: reformat --- examples/drm.rs | 8 ++++---- src/backend/drm/backend.rs | 1 - src/backend/drm/error.rs | 2 +- src/backend/drm/mod.rs | 16 +++++----------- 4 files changed, 10 insertions(+), 17 deletions(-) diff --git a/examples/drm.rs b/examples/drm.rs index bc65307..964cb67 100644 --- a/examples/drm.rs +++ b/examples/drm.rs @@ -1,4 +1,3 @@ - extern crate drm; #[macro_use] extern crate glium; @@ -34,15 +33,15 @@ use smithay::shm::{ShmGlobal, ShmToken}; use std::fs::OpenOptions; use std::io::Error as IoError; -use std::time::Duration; use std::os::unix::io::AsRawFd; +use std::time::Duration; use wayland_protocols::unstable::xdg_shell::server::{zxdg_shell_v6, zxdg_toplevel_v6}; use wayland_server::{Client, EventLoopHandle}; -use wayland_server::sources::READ; use wayland_server::protocol::{wl_callback, wl_compositor, wl_output, wl_seat, wl_shell, wl_shm, wl_subcompositor, wl_surface}; +use wayland_server::sources::READ; define_roles!(Roles => [ ShellSurface, ShellSurfaceRole ] ); @@ -284,7 +283,8 @@ fn main() { let fd = device.as_raw_fd(); let drm_device_id = event_loop.add_handler(device); - let _drm_event_source = event_loop.add_fd_event_source::>(fd, drm_device_id, READ); + let _drm_event_source = + event_loop.add_fd_event_source::>(fd, drm_device_id, READ); event_loop.run().unwrap(); } diff --git a/src/backend/drm/backend.rs b/src/backend/drm/backend.rs index a8c3f1e..31bc3e6 100644 --- a/src/backend/drm/backend.rs +++ b/src/backend/drm/backend.rs @@ -1,4 +1,3 @@ - use super::{DrmError, ModeError}; use super::devices; diff --git a/src/backend/drm/error.rs b/src/backend/drm/error.rs index 5e05e5c..01f7d2c 100644 --- a/src/backend/drm/error.rs +++ b/src/backend/drm/error.rs @@ -153,7 +153,7 @@ impl error::Error for ModeError { #[derive(Debug)] pub enum CrtcError { /// Selected crtc is already in use by another `DrmBackend` - AlreadyInUse + AlreadyInUse, } impl fmt::Display for CrtcError { diff --git a/src/backend/drm/mod.rs b/src/backend/drm/mod.rs index 19bfd09..81f1a9c 100644 --- a/src/backend/drm/mod.rs +++ b/src/backend/drm/mod.rs @@ -156,7 +156,7 @@ use std::os::unix::io::{AsRawFd, RawFd}; use std::rc::{Rc, Weak}; use std::time::Duration; -use wayland_server::{EventLoopHandle}; +use wayland_server::EventLoopHandle; use wayland_server::sources::{FdEventSourceHandler, FdInterest}; mod backend; @@ -164,7 +164,7 @@ mod error; pub use self::backend::{DrmBackend, Id}; use self::backend::DrmBackendInternal; -pub use self::error::{Error as DrmError, ModeError, CrtcError}; +pub use self::error::{CrtcError, Error as DrmError, ModeError}; /// Internal struct as required by the drm crate #[derive(Debug)] @@ -255,7 +255,7 @@ impl DrmDevice { /// The file descriptor might not be valid and needs to be owned by smithay, /// make sure not to share it. Otherwise undefined behavior might occur. pub unsafe fn new_from_fd_with_gl_attr(fd: RawFd, attributes: GlAttributes, logger: L) - -> Result + -> Result where L: Into>, { @@ -427,10 +427,7 @@ impl FdEventSourceHandler for DrmDevice { impl BasicDevice for DrmDeviceRef {} impl ControlDevice for DrmDeviceRef {} - struct PageFlipHandler<'a, 'b, H: DrmHandler + 'static>( - &'a mut DrmDevice, - &'b mut EventLoopHandle, - ); + struct PageFlipHandler<'a, 'b, H: DrmHandler + 'static>(&'a mut DrmDevice, &'b mut EventLoopHandle); impl<'a, 'b, H: DrmHandler + 'static> crtc::PageFlipHandler for PageFlipHandler<'a, 'b, H> { fn handle_event(&mut self, _device: &DrmDeviceRef, frame: u32, duration: Duration, @@ -451,10 +448,7 @@ impl FdEventSourceHandler for DrmDevice { &DrmDeviceRef(fd), 2, None::<&mut ()>, - Some(&mut PageFlipHandler( - self, - evlh, - )), + Some(&mut PageFlipHandler(self, evlh)), None::<&mut ()>, ).unwrap(); } From 10fd94f96391dc2a162e575657a761af88e05539 Mon Sep 17 00:00:00 2001 From: Drakulix Date: Thu, 14 Sep 2017 23:12:16 +0200 Subject: [PATCH 06/18] drm: add more logging --- src/backend/drm/backend.rs | 73 +++++++++++++++++++++++++------------- src/backend/drm/mod.rs | 12 +++++-- 2 files changed, 57 insertions(+), 28 deletions(-) diff --git a/src/backend/drm/backend.rs b/src/backend/drm/backend.rs index 31bc3e6..ab38033 100644 --- a/src/backend/drm/backend.rs +++ b/src/backend/drm/backend.rs @@ -105,8 +105,9 @@ impl DrmBackendInternal { I: Into>, L: Into>, { - let log = ::slog_or_stdlog(logger).new(o!("smithay_module" => "backend_drm")); - info!(log, "Initializing a drm backend"); + // logger already initialized by the DrmDevice + let log = ::slog_or_stdlog(logger); + info!(log, "Initializing DrmBackend"); let connectors = connectors.into(); @@ -122,12 +123,8 @@ impl DrmBackendInternal { let (w, h) = mode.size(); - info!(log, "Drm Backend initializing"); - Ok(DrmBackendInternal { graphics: Graphics::try_new(context, |context| { - debug!(log, "GBM EGLContext initialized"); - Ok(GbmTypes { cursor: { // Create an unused cursor buffer (we don't want an Option here) @@ -138,19 +135,23 @@ impl DrmBackendInternal { &[BufferObjectFlags::Cursor, BufferObjectFlags::Write], )? }, - surface: Surface::try_new( - // create a gbm surface - Box::new(context.devices.gbm.create_surface( - w as u32, - h as u32, - GbmFormat::XRGB8888, - &[BufferObjectFlags::Scanout, BufferObjectFlags::Rendering], - )?), + surface: Surface::try_new({ + debug!(log, "Creating GbmSurface"); + // create a gbm surface + Box::new(context.devices.gbm.create_surface( + w as u32, + h as u32, + GbmFormat::XRGB8888, + &[BufferObjectFlags::Scanout, BufferObjectFlags::Rendering], + )?) + }, |surface| { // create an egl surface from the gbm one + debug!(log, "Creating EGLSurface"); let egl_surface = context.egl.create_surface(&surface)?; - // set it to be able to use `crtc::set` once + // make it active for the first `crtc::set` + // (which is needed before the first page_flip) unsafe { egl_surface.make_current()? }; egl_surface.swap_buffers()?; @@ -160,6 +161,8 @@ impl DrmBackendInternal { debug!(log, "FrontBuffer color format: {:?}", front_bo.format()); // we need a framebuffer per front buffer let fb = framebuffer::create(context.devices.drm, &*front_bo)?; + + debug!(log, "Initialize screen"); crtc::set( context.devices.drm, crtc, @@ -197,6 +200,7 @@ impl DrmBackendInternal { let next_bo = egl.buffers.next_buffer.replace(None); if let Some(next_buffer) = next_bo { + trace!(self.logger, "Releasing all front buffer"); egl.buffers.front_buffer.set(next_buffer); // drop and release the old buffer } else { @@ -225,6 +229,7 @@ impl DrmBackend { // check if the connector can handle the current mode let mut internal = self.0.borrow_mut(); if info.modes().contains(&internal.mode) { + info!(self.0.borrow().logger, "Adding new connector: {:?}", info.connector_type()); internal.connectors.push(connector); Ok(()) } else { @@ -240,6 +245,12 @@ impl DrmBackend { /// Removes a currently set connector pub fn remove_connector(&mut self, connector: connector::Handle) { + if let Ok(info) = connector::Info::load_from_device(self.0.borrow().graphics.head().head().head(), connector) { + info!(self.0.borrow().logger, "Removing connector: {:?}", info.connector_type()); + } else { + info!(self.0.borrow().logger, "Removeing unknown connector"); + } + self.0.borrow_mut().connectors.retain(|x| *x != connector); } @@ -275,17 +286,23 @@ impl DrmBackend { .rent_all_mut(|graphics| -> Result<(), DrmError> { // Recreate the surface and the related resources to match the new // resolution. - graphics.gbm.surface = Surface::try_new( - Box::new(graphics.context.devices.gbm.create_surface( - w as u32, - h as u32, - GbmFormat::XRGB8888, - &[BufferObjectFlags::Scanout, BufferObjectFlags::Rendering], - )?), + debug!(logger, "Reinitializing surface for new mode: {}:{}", w, h); + graphics.gbm.surface = Surface::try_new({ + // create a new gbm surface + debug!(logger, "Creating GbmSurface"); + Box::new(graphics.context.devices.gbm.create_surface( + w as u32, + h as u32, + GbmFormat::XRGB8888, + &[BufferObjectFlags::Scanout, BufferObjectFlags::Rendering], + )?) + }, |surface| { + // create an egl surface from the gbm one + debug!(logger, "Creating EGLSurface"); let egl_surface = graphics.context.egl.create_surface(&surface)?; - // make it active for the first crtc::set + // make it active for the first `crtc::set` // (which is needed before the first page_flip) unsafe { egl_surface.make_current()? }; egl_surface.swap_buffers()?; @@ -294,7 +311,8 @@ impl DrmBackend { debug!(logger, "FrontBuffer color format: {:?}", front_bo.format()); // we need a framebuffer per front_buffer let fb = framebuffer::create(graphics.context.devices.drm, &*front_bo)?; - // init the first screen + + debug!(logger, "Initialize screen"); crtc::set( graphics.context.devices.drm, crtc, @@ -318,6 +336,7 @@ impl DrmBackend { Ok(()) })?; + info!(logger, "Setting new mode: {:?}", mode.name()); internal.mode = mode; Ok(()) } @@ -336,6 +355,7 @@ impl GraphicsBackend for DrmBackend { type Error = DrmError; fn set_cursor_position(&self, x: u32, y: u32) -> Result<(), DrmError> { + trace!(self.0.borrow().logger, "Move the cursor to {},{}", x, y); crtc::move_cursor( self.0.borrow().graphics.head().head().head(), self.0.borrow().crtc, @@ -346,6 +366,7 @@ impl GraphicsBackend for DrmBackend { fn set_cursor_representation(&self, buffer: ImageBuffer, Vec>, hotspot: (u32, u32)) -> Result<(), DrmError> { let (w, h) = buffer.dimensions(); + debug!(self.0.borrow().logger, "Importing cursor"); /// import the cursor into a buffer we can render self.0 .borrow_mut() @@ -364,6 +385,7 @@ impl GraphicsBackend for DrmBackend { Ok(()) })?; + trace!(self.0.borrow().logger, "Set the new imported cursor"); // and set it if crtc::set_cursor2( self.0.borrow().graphics.head().head().head(), @@ -401,6 +423,7 @@ impl EGLGraphicsBackend for DrmBackend { egl.buffers.next_buffer.set(next); res }) { + warn!(self.0.borrow().logger, "Tried to swap a DrmBackend with a queued flip"); return Err(SwapBuffersError::AlreadySwapped); } @@ -426,7 +449,7 @@ impl EGLGraphicsBackend for DrmBackend { }; surface.egl.buffers.next_buffer.set(Some(next_bo)); - trace!(self.0.borrow().logger, "Page flip queued"); + trace!(self.0.borrow().logger, "Queueing Page flip"); let id: Id = self.0.borrow().own_id; diff --git a/src/backend/drm/mod.rs b/src/backend/drm/mod.rs index 81f1a9c..f2a3a82 100644 --- a/src/backend/drm/mod.rs +++ b/src/backend/drm/mod.rs @@ -298,7 +298,7 @@ impl DrmDevice { where L: Into>, { - let log = ::slog_or_stdlog(logger).new(o!("smithay_module" => "backend_drm", "drm" => "device")); + 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 @@ -313,13 +313,17 @@ impl DrmDevice { ); } + info!(log, "DrmDevice initializing"); + // Open the gbm device from the drm device and create a context based on that Ok(DrmDevice { context: Rc::new(Context::try_new( Box::new(Devices::try_new(Box::new(drm), |drm| { + debug!(log, "Creating gbm device"); GbmDevice::new_from_drm::>(drm).map_err(DrmError::from) })?), |devices| { + debug!(log, "Creating egl context from gbm device"); EGLContext::new_from_gbm( devices.gbm, attributes, @@ -357,9 +361,9 @@ impl DrmDevice { } } - let logger = self.logger - .new(o!("drm" => "backend", "crtc" => format!("{:?}", crtc))); let own_id = self.backends.len(); + let logger = self.logger + .new(o!("id" => format!("{}", own_id), "crtc" => format!("{:?}", crtc))); let backend = Rc::new(RefCell::new(DrmBackendInternal::new( self.context.clone(), @@ -435,6 +439,7 @@ impl FdEventSourceHandler for DrmDevice { let id: Id = *userdata.downcast().unwrap(); if let Some(backend) = self.0.backends[id.raw()].upgrade() { // we can now unlock the buffer + trace!(self.0.logger, "Handling event for backend {:?}", id.raw()); backend.borrow().unlock_buffer(); if let Some(handler) = self.0.handler.as_mut() { // and then call the user to render the next frame @@ -455,6 +460,7 @@ impl FdEventSourceHandler for DrmDevice { fn error(&mut self, evlh: &mut EventLoopHandle, _fd: RawFd, error: IoError) { if let Some(handler) = self.handler.as_mut() { + warn!(self.logger, "DrmDevice errored: {}", error); handler.error(evlh, error) } } From 660080c45dfe60eef98296e5f1ca3d8b987f0d1e Mon Sep 17 00:00:00 2001 From: Drakulix Date: Thu, 14 Sep 2017 23:20:48 +0200 Subject: [PATCH 07/18] drm: Refine drm example commentary --- examples/drm.rs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/examples/drm.rs b/examples/drm.rs index 964cb67..892f5d1 100644 --- a/examples/drm.rs +++ b/examples/drm.rs @@ -181,6 +181,9 @@ fn main() { // Initialize the wayland server let (mut display, mut event_loop) = wayland_server::create_display(); + /* + * Initialize the drm backend + */ // "Find" a suitable drm device let mut options = OpenOptions::new(); options.read(true); @@ -207,7 +210,7 @@ fn main() { // 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 backends + // Initialize the hardware backend let renderer = device .create_backend(crtc, mode, vec![connector_info.handle()]) .unwrap(); @@ -274,6 +277,7 @@ fn main() { let name = display.add_socket_auto().unwrap().into_string().unwrap(); println!("Listening on socket: {}", name); + // Set the DrmHandler device.set_handler(DrmHandlerImpl { drawer: drawer, shell_handler_id, @@ -281,6 +285,9 @@ fn main() { logger: log, }); + /* + * Register the DrmDevice on the EventLoop + */ let fd = device.as_raw_fd(); let drm_device_id = event_loop.add_handler(device); let _drm_event_source = @@ -344,6 +351,6 @@ impl DrmHandler for DrmHandlerImpl { } fn error(&mut self, _evlh: &mut EventLoopHandle, error: IoError) { - error!(self.logger, "{:?}", error); + panic!("{:?}", error); } } From ab77a8772517371873fee2a1d33c0d6c238a248b Mon Sep 17 00:00:00 2001 From: Drakulix Date: Fri, 15 Sep 2017 15:52:00 +0200 Subject: [PATCH 08/18] drm: Check for valid encoder --- Cargo.toml | 2 +- src/backend/drm/error.rs | 3 +++ src/backend/drm/mod.rs | 24 +++++++++++++++++++++++- 3 files changed, 27 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 699d88d..98050f5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -42,4 +42,4 @@ renderer_glium = ["glium"] "wayland-server:0.9.9" = { git = "https://github.com/Drakulix/wayland-rs", branch = "raw_handler_access"} "wayland-protocols:0.9.9" = { git = "https://github.com/Drakulix/wayland-rs", branch = "raw_handler_access"} "wayland-client:0.9.9" = { git = "https://github.com/Drakulix/wayland-rs", branch = "raw_handler_access"} -"drm:0.2.1" = { git = "https://github.com/Drakulix/drm-rs", branch = "fix/userdata" } +"drm:0.2.1" = { git = "https://github.com/Drakulix/drm-rs", branch = "future" } diff --git a/src/backend/drm/error.rs b/src/backend/drm/error.rs index 01f7d2c..f652c5a 100644 --- a/src/backend/drm/error.rs +++ b/src/backend/drm/error.rs @@ -154,6 +154,8 @@ impl error::Error for ModeError { pub enum CrtcError { /// Selected crtc is already in use by another `DrmBackend` AlreadyInUse, + /// For the selected crtc no encoder exists that supports all connectors + NoSuitableEncoder, } impl fmt::Display for CrtcError { @@ -170,6 +172,7 @@ impl error::Error for CrtcError { fn description(&self) -> &str { match self { &CrtcError::AlreadyInUse => "Crtc is already in use by another DrmBackend", + &CrtcError::NoSuitableEncoder => "Crtc has no supported encoder that can drive all connectors", } } diff --git a/src/backend/drm/mod.rs b/src/backend/drm/mod.rs index f2a3a82..9075859 100644 --- a/src/backend/drm/mod.rs +++ b/src/backend/drm/mod.rs @@ -142,7 +142,7 @@ use backend::graphics::egl::{EGLContext, GlAttributes, PixelFormatRequirements}; use drm::Device as BasicDevice; -use drm::control::{connector, crtc, Mode}; +use drm::control::{connector, crtc, encoder, Mode, ResourceInfo}; use drm::control::Device as ControlDevice; use gbm::Device as GbmDevice; @@ -353,6 +353,8 @@ impl DrmDevice { where I: Into>, { + use std::collections::hash_set::HashSet; + for backend in self.backends.iter() { if let Some(backend) = backend.upgrade() { if backend.borrow().is_crtc(crtc) { @@ -361,6 +363,26 @@ impl DrmDevice { } } + // check if the given connectors and crtc match + let connectors = connectors.into(); + // get all encoders supported by this device + let mut set = self.context.head().head().resource_handles()?.encoders().iter().cloned().collect::>(); + for connector in connectors.iter() { + let info = connector::Info::load_from_device(self.context.head().head(), *connector)?; + // then check for every connector which encoders it does support + let conn_set = info.encoders().iter().cloned().collect::>(); + // and update the list of supported encoders for this combination + set = set.intersection(&conn_set).cloned().collect::>(); + } + + // check if there is any encoder left that can be connected to the crtc + let encoders: Vec = set.iter().map(|handle| encoder::Info::load_from_device(self.context.head().head(), *handle).map_err(DrmError::from)).collect::, DrmError>>()?; + if !encoders.iter().any(|enc| enc.supports_crtc(crtc)) { + return Err(DrmError::Crtc(CrtcError::NoSuitableEncoder)); + } + + // configuration is valid, the kernel will figure out the rest + let own_id = self.backends.len(); let logger = self.logger .new(o!("id" => format!("{}", own_id), "crtc" => format!("{:?}", crtc))); From 67b3621be326c98e0c2677be85b246a105291ce4 Mon Sep 17 00:00:00 2001 From: Drakulix Date: Fri, 15 Sep 2017 15:57:30 +0200 Subject: [PATCH 09/18] drm: reformat --- src/backend/drm/mod.rs | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/src/backend/drm/mod.rs b/src/backend/drm/mod.rs index 9075859..90e3edc 100644 --- a/src/backend/drm/mod.rs +++ b/src/backend/drm/mod.rs @@ -366,17 +366,33 @@ impl DrmDevice { // check if the given connectors and crtc match let connectors = connectors.into(); // get all encoders supported by this device - let mut set = self.context.head().head().resource_handles()?.encoders().iter().cloned().collect::>(); + let mut set = self.context + .head() + .head() + .resource_handles()? + .encoders() + .iter() + .cloned() + .collect::>(); for connector in connectors.iter() { let info = connector::Info::load_from_device(self.context.head().head(), *connector)?; // then check for every connector which encoders it does support - let conn_set = info.encoders().iter().cloned().collect::>(); + let conn_set = info.encoders() + .iter() + .cloned() + .collect::>(); // and update the list of supported encoders for this combination - set = set.intersection(&conn_set).cloned().collect::>(); + set = set.intersection(&conn_set) + .cloned() + .collect::>(); } // check if there is any encoder left that can be connected to the crtc - let encoders: Vec = set.iter().map(|handle| encoder::Info::load_from_device(self.context.head().head(), *handle).map_err(DrmError::from)).collect::, DrmError>>()?; + let encoders: Vec = set.iter() + .map(|handle| { + encoder::Info::load_from_device(self.context.head().head(), *handle).map_err(DrmError::from) + }) + .collect::, DrmError>>()?; if !encoders.iter().any(|enc| enc.supports_crtc(crtc)) { return Err(DrmError::Crtc(CrtcError::NoSuitableEncoder)); } From bdb91fc1f1df0ff6c72b7b4461b9950dfbacf1ce Mon Sep 17 00:00:00 2001 From: Drakulix Date: Sun, 17 Sep 2017 22:37:03 +0200 Subject: [PATCH 10/18] Better explaination of the drm module - Explain the types involved and their role - Link to the drm-rs documentation --- src/backend/drm/mod.rs | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/src/backend/drm/mod.rs b/src/backend/drm/mod.rs index 90e3edc..c772db3 100644 --- a/src/backend/drm/mod.rs +++ b/src/backend/drm/mod.rs @@ -3,7 +3,29 @@ //! This module provide a `DrmDevice` which acts as a reprensentation for any drm //! device and can be used to create the second provided structure a `DrmBackend`. //! -//! The latter represents a crtc of the graphics card you can render to. +//! 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 independant 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 From d3fca34475b7e22c1525c7942394a6926d523a27 Mon Sep 17 00:00:00 2001 From: Drakulix Date: Sun, 17 Sep 2017 22:37:54 +0200 Subject: [PATCH 11/18] Do a better job at finding a crtc in the example --- examples/drm.rs | 20 ++++++++++++-------- src/backend/drm/mod.rs | 29 ++++++++++++++++++++++++++--- 2 files changed, 38 insertions(+), 11 deletions(-) diff --git a/examples/drm.rs b/examples/drm.rs index 892f5d1..1da4faa 100644 --- a/examples/drm.rs +++ b/examples/drm.rs @@ -16,12 +16,10 @@ mod helpers; 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 glium::Surface; - use helpers::GliumDrawer; use slog::*; - use smithay::backend::drm::{DrmBackend, DrmDevice, DrmHandler, Id}; use smithay::backend::graphics::egl::EGLGraphicsBackend; use smithay::backend::graphics::glium::{GliumGraphicsBackend, IntoGlium}; @@ -30,14 +28,11 @@ use smithay::compositor::roles::Role; use smithay::shell::{self, PopupConfigure, PopupSurface, ShellClient, ShellHandler, ShellSurfaceRole, ToplevelConfigure, ToplevelSurface}; use smithay::shm::{ShmGlobal, ShmToken}; - use std::fs::OpenOptions; use std::io::Error as IoError; use std::os::unix::io::AsRawFd; use std::time::Duration; - use wayland_protocols::unstable::xdg_shell::server::{zxdg_shell_v6, zxdg_toplevel_v6}; - use wayland_server::{Client, EventLoopHandle}; use wayland_server::protocol::{wl_callback, wl_compositor, wl_output, wl_seat, wl_shell, wl_shm, wl_subcompositor, wl_surface}; @@ -204,8 +199,17 @@ fn main() { .find(|conn| conn.connection_state() == ConnectorState::Connected) .unwrap(); - // Use the first crtc (should be successful in most cases) - let crtc = res_handles.crtcs()[0]; + // 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.crtcs() + .iter() + .find(|crtc| encoder_info.supports_crtc(**crtc)) + .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.) diff --git a/src/backend/drm/mod.rs b/src/backend/drm/mod.rs index c772db3..f022815 100644 --- a/src/backend/drm/mod.rs +++ b/src/backend/drm/mod.rs @@ -39,15 +39,20 @@ //! You will need to use the `drm` crate to provide the required types to create //! a backend. //! -//! ```rust,ignore +//! ```rust,no_run //! extern crate drm; //! extern crate smithay; //! # extern crate wayland_server; //! //! 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::io::Error as IoError; //! use std::fs::OpenOptions; +//! # use std::time::Duration; //! use smithay::backend::drm::DrmDevice; +//! # use smithay::backend::drm::{DrmHandler, Id}; +//! # use wayland_server::EventLoopHandle; //! //! # fn main() { //! // Open the drm device @@ -68,8 +73,17 @@ //! .find(|conn| conn.connection_state() == ConnectorState::Connected) //! .unwrap(); //! -//! // Use first crtc (should be successful in most cases) -//! let crtc = res_handles.crtcs()[0]; +//! // 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.crtcs() +//! .iter() +//! .find(|crtc| encoder_info.supports_crtc(**crtc)) +//! .unwrap()); //! //! // Use first mode (usually the highest resolution) //! let mode = connector_info.modes()[0]; @@ -80,6 +94,14 @@ //! mode, //! vec![connector_info.handle()] //! ).unwrap(); +//! # struct MyDrmHandler; +//! # +//! # impl DrmHandler for MyDrmHandler { +//! # fn ready(&mut self, _: &mut EventLoopHandle, id: Id, _frame: u32, _duration: Duration) {} +//! # fn error(&mut self, _: &mut EventLoopHandle, error: IoError) {} +//! # } +//! # +//! # device.set_handler(MyDrmHandler); //! # } //! ``` //! @@ -123,6 +145,7 @@ //! # 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()) From 174e4b9d0b2400e267bf96d1ee542fad7feaedb6 Mon Sep 17 00:00:00 2001 From: Drakulix Date: Sun, 17 Sep 2017 22:54:36 +0200 Subject: [PATCH 12/18] Fix encoder detection --- src/backend/drm/mod.rs | 49 ++++++++++++++++-------------------------- 1 file changed, 18 insertions(+), 31 deletions(-) diff --git a/src/backend/drm/mod.rs b/src/backend/drm/mod.rs index f022815..a72134f 100644 --- a/src/backend/drm/mod.rs +++ b/src/backend/drm/mod.rs @@ -398,8 +398,6 @@ impl DrmDevice { where I: Into>, { - use std::collections::hash_set::HashSet; - for backend in self.backends.iter() { if let Some(backend) = backend.upgrade() { if backend.borrow().is_crtc(crtc) { @@ -410,36 +408,25 @@ impl DrmDevice { // check if the given connectors and crtc match let connectors = connectors.into(); - // get all encoders supported by this device - let mut set = self.context - .head() - .head() - .resource_handles()? - .encoders() - .iter() - .cloned() - .collect::>(); - for connector in connectors.iter() { - let info = connector::Info::load_from_device(self.context.head().head(), *connector)?; - // then check for every connector which encoders it does support - let conn_set = info.encoders() - .iter() - .cloned() - .collect::>(); - // and update the list of supported encoders for this combination - set = set.intersection(&conn_set) - .cloned() - .collect::>(); - } - // check if there is any encoder left that can be connected to the crtc - let encoders: Vec = set.iter() - .map(|handle| { - encoder::Info::load_from_device(self.context.head().head(), *handle).map_err(DrmError::from) - }) - .collect::, DrmError>>()?; - if !encoders.iter().any(|enc| enc.supports_crtc(crtc)) { - return Err(DrmError::Crtc(CrtcError::NoSuitableEncoder)); + // check if we have an encoder for every connector + for connector in connectors.iter() { + let con_info = connector::Info::load_from_device(self.context.head().head(), *connector)?; + + // then check for every connector which encoders it does support + let encoders = con_info + .encoders() + .iter() + .map(|encoder| { + encoder::Info::load_from_device(self.context.head().head(), *encoder) + .map_err(DrmError::from) + }) + .collect::, DrmError>>()?; + + // and if any encoder supports the selected crtc + if !encoders.iter().any(|encoder| encoder.supports_crtc(crtc)) { + return Err(DrmError::Crtc(CrtcError::NoSuitableEncoder)); + } } // configuration is valid, the kernel will figure out the rest From a04bfcdd11a12252775a49015f356dc9e8578991 Mon Sep 17 00:00:00 2001 From: Drakulix Date: Sun, 17 Sep 2017 22:54:51 +0200 Subject: [PATCH 13/18] Add encoder detection to runtime manipulation --- src/backend/drm/backend.rs | 53 +++++++++++++++++++++++++++----------- 1 file changed, 38 insertions(+), 15 deletions(-) diff --git a/src/backend/drm/backend.rs b/src/backend/drm/backend.rs index ab38033..50789dc 100644 --- a/src/backend/drm/backend.rs +++ b/src/backend/drm/backend.rs @@ -1,19 +1,13 @@ -use super::{DrmError, ModeError}; - +use super::{CrtcError, DrmError, ModeError}; use super::devices; - use backend::graphics::GraphicsBackend; use backend::graphics::egl::{EGLGraphicsBackend, EGLSurface, PixelFormat, SwapBuffersError}; use drm::buffer::Buffer; -use drm::control::{connector, crtc, framebuffer, Mode}; +use drm::control::{connector, crtc, encoder, framebuffer, Mode}; use drm::control::ResourceInfo; - use gbm::{BufferObject, BufferObjectFlags, Format as GbmFormat, Surface as GbmSurface, SurfaceBufferHandle}; - use image::{ImageBuffer, Rgba}; - use nix::c_void; - use std::cell::{Cell, RefCell}; use std::rc::Rc; @@ -135,7 +129,8 @@ impl DrmBackendInternal { &[BufferObjectFlags::Cursor, BufferObjectFlags::Write], )? }, - surface: Surface::try_new({ + surface: Surface::try_new( + { debug!(log, "Creating GbmSurface"); // create a gbm surface Box::new(context.devices.gbm.create_surface( @@ -221,7 +216,7 @@ impl DrmBackend { /// # Errors /// /// Errors if the new connector does not support the currently set `Mode` - pub fn add_connector(&mut self, connector: connector::Handle) -> Result<(), ModeError> { + pub fn add_connector(&mut self, connector: connector::Handle) -> Result<(), DrmError> { let info = connector::Info::load_from_device(self.0.borrow().graphics.head().head().head(), connector) .map_err(|err| ModeError::FailedToLoad(err))?; @@ -229,11 +224,32 @@ impl DrmBackend { // check if the connector can handle the current mode let mut internal = self.0.borrow_mut(); if info.modes().contains(&internal.mode) { - info!(self.0.borrow().logger, "Adding new connector: {:?}", info.connector_type()); + // check if there is a valid encoder + let encoders = info.encoders() + .iter() + .map(|encoder| { + encoder::Info::load_from_device(self.0.borrow().graphics.head().head().head(), *encoder) + .map_err(DrmError::from) + }) + .collect::, DrmError>>()?; + + // and if any encoder supports the selected crtc + if !encoders + .iter() + .any(|encoder| encoder.supports_crtc(self.0.borrow().crtc)) + { + return Err(DrmError::Crtc(CrtcError::NoSuitableEncoder)); + } + + info!( + self.0.borrow().logger, + "Adding new connector: {:?}", + info.connector_type() + ); internal.connectors.push(connector); Ok(()) } else { - Err(ModeError::ModeNotSuitable) + Err(DrmError::Mode(ModeError::ModeNotSuitable)) } } @@ -245,8 +261,14 @@ impl DrmBackend { /// Removes a currently set connector pub fn remove_connector(&mut self, connector: connector::Handle) { - if let Ok(info) = connector::Info::load_from_device(self.0.borrow().graphics.head().head().head(), connector) { - info!(self.0.borrow().logger, "Removing connector: {:?}", info.connector_type()); + if let Ok(info) = + connector::Info::load_from_device(self.0.borrow().graphics.head().head().head(), connector) + { + info!( + self.0.borrow().logger, + "Removing connector: {:?}", + info.connector_type() + ); } else { info!(self.0.borrow().logger, "Removeing unknown connector"); } @@ -287,7 +309,8 @@ impl DrmBackend { // Recreate the surface and the related resources to match the new // resolution. debug!(logger, "Reinitializing surface for new mode: {}:{}", w, h); - graphics.gbm.surface = Surface::try_new({ + graphics.gbm.surface = Surface::try_new( + { // create a new gbm surface debug!(logger, "Creating GbmSurface"); Box::new(graphics.context.devices.gbm.create_surface( From fa6742fb5ff115dd32318c050b1b5364a20c18a1 Mon Sep 17 00:00:00 2001 From: Drakulix Date: Mon, 18 Sep 2017 16:58:20 +0200 Subject: [PATCH 14/18] The glory of error-chain --- Cargo.toml | 1 + src/backend/drm/backend.rs | 252 +++++++++++++++++++++++------------- src/backend/drm/error.rs | 207 ++++++----------------------- src/backend/drm/mod.rs | 39 +++--- src/backend/graphics/egl.rs | 212 +++++++++++++++--------------- src/backend/winit.rs | 85 ++++++------ src/lib.rs | 5 + 7 files changed, 380 insertions(+), 421 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 98050f5..ce826fd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,6 +22,7 @@ clippy = { version = "*", optional = true } rental = "0.4.11" wayland-protocols = { version = "0.10.1", features = ["unstable_protocols", "server"] } image = "0.15.0" +error-chain = "0.11.0" [build-dependencies] gl_generator = "0.5" diff --git a/src/backend/drm/backend.rs b/src/backend/drm/backend.rs index 50789dc..6d0dedb 100644 --- a/src/backend/drm/backend.rs +++ b/src/backend/drm/backend.rs @@ -1,5 +1,5 @@ -use super::{CrtcError, DrmError, ModeError}; use super::devices; +use super::error::*; use backend::graphics::GraphicsBackend; use backend::graphics::egl::{EGLGraphicsBackend, EGLSurface, PixelFormat, SwapBuffersError}; use drm::buffer::Buffer; @@ -94,7 +94,7 @@ pub(crate) struct DrmBackendInternal { impl DrmBackendInternal { pub(crate) fn new(context: Rc, crtc: crtc::Handle, mode: Mode, connectors: I, own_id: usize, logger: L) - -> Result + -> Result where I: Into>, L: Into>, @@ -107,11 +107,12 @@ impl DrmBackendInternal { // check the connectors, if they suite the mode for connector in connectors.iter() { - if !connector::Info::load_from_device(context.head().head(), *connector)? + if !connector::Info::load_from_device(context.head().head(), *connector) + .chain_err(|| ErrorKind::DrmDev(format!("{:?}", context.head().head())))? .modes() .contains(&mode) { - return Err(DrmError::Mode(ModeError::ModeNotSuitable)); + bail!(ErrorKind::ModeNotSuitable(mode)) } } @@ -122,23 +123,31 @@ impl DrmBackendInternal { Ok(GbmTypes { cursor: { // Create an unused cursor buffer (we don't want an Option here) - context.devices.gbm.create_buffer_object( - 1, - 1, - GbmFormat::ARGB8888, - &[BufferObjectFlags::Cursor, BufferObjectFlags::Write], - )? + context + .devices + .gbm + .create_buffer_object( + 1, + 1, + GbmFormat::ARGB8888, + &[BufferObjectFlags::Cursor, BufferObjectFlags::Write], + ) + .chain_err(|| ErrorKind::GbmInitFailed)? }, surface: Surface::try_new( { debug!(log, "Creating GbmSurface"); // create a gbm surface - Box::new(context.devices.gbm.create_surface( - w as u32, - h as u32, - GbmFormat::XRGB8888, - &[BufferObjectFlags::Scanout, BufferObjectFlags::Rendering], - )?) + Box::new(context + .devices + .gbm + .create_surface( + w as u32, + h as u32, + GbmFormat::XRGB8888, + &[BufferObjectFlags::Scanout, BufferObjectFlags::Rendering], + ) + .chain_err(|| ErrorKind::GbmInitFailed)?) }, |surface| { // create an egl surface from the gbm one @@ -147,15 +156,24 @@ impl DrmBackendInternal { // make it active for the first `crtc::set` // (which is needed before the first page_flip) - unsafe { egl_surface.make_current()? }; - egl_surface.swap_buffers()?; + unsafe { + egl_surface + .make_current() + .chain_err(|| ErrorKind::FailedToSwap)? + }; + egl_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()?; + let mut front_bo = surface + .lock_front_buffer() + .chain_err(|| ErrorKind::FailedToSwap)?; debug!(log, "FrontBuffer color format: {:?}", front_bo.format()); // we need a framebuffer per front buffer - let fb = framebuffer::create(context.devices.drm, &*front_bo)?; + let fb = framebuffer::create(context.devices.drm, &*front_bo) + .chain_err(|| ErrorKind::DrmDev(format!("{:?}", context.devices.drm)))?; debug!(log, "Initialize screen"); crtc::set( @@ -165,6 +183,8 @@ impl DrmBackendInternal { &connectors, (0, 0), Some(mode), + ).chain_err( + || ErrorKind::DrmDev(format!("{:?}", context.devices.drm)), )?; front_bo.set_userdata(fb); @@ -176,7 +196,7 @@ impl DrmBackendInternal { }, }) }, - ).map_err(DrmError::from)?, + ).map_err(Error::from)?, }) })?, crtc, @@ -216,10 +236,15 @@ impl DrmBackend { /// # Errors /// /// Errors if the new connector does not support the currently set `Mode` - pub fn add_connector(&mut self, connector: connector::Handle) -> Result<(), DrmError> { + pub fn add_connector(&mut self, connector: connector::Handle) -> Result<()> { let info = connector::Info::load_from_device(self.0.borrow().graphics.head().head().head(), connector) - .map_err(|err| ModeError::FailedToLoad(err))?; + .chain_err(|| { + ErrorKind::DrmDev(format!( + "{:?}", + self.0.borrow().graphics.head().head().head() + )) + })?; // check if the connector can handle the current mode let mut internal = self.0.borrow_mut(); @@ -229,16 +254,21 @@ impl DrmBackend { .iter() .map(|encoder| { encoder::Info::load_from_device(self.0.borrow().graphics.head().head().head(), *encoder) - .map_err(DrmError::from) + .chain_err(|| { + ErrorKind::DrmDev(format!( + "{:?}", + self.0.borrow().graphics.head().head().head() + )) + }) }) - .collect::, DrmError>>()?; + .collect::>>()?; // and if any encoder supports the selected crtc if !encoders .iter() .any(|encoder| encoder.supports_crtc(self.0.borrow().crtc)) { - return Err(DrmError::Crtc(CrtcError::NoSuitableEncoder)); + bail!(ErrorKind::NoSuitableEncoder(info, self.0.borrow().crtc)); } info!( @@ -249,7 +279,7 @@ impl DrmBackend { internal.connectors.push(connector); Ok(()) } else { - Err(DrmError::Mode(ModeError::ModeNotSuitable)) + bail!(ErrorKind::ModeNotSuitable(self.0.borrow().mode)) } } @@ -270,7 +300,7 @@ impl DrmBackend { info.connector_type() ); } else { - info!(self.0.borrow().logger, "Removeing unknown connector"); + info!(self.0.borrow().logger, "Removing unknown connector"); } self.0.borrow_mut().connectors.retain(|x| *x != connector); @@ -283,14 +313,20 @@ impl DrmBackend { /// 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<(), DrmError> { + pub fn use_mode(&mut self, mode: Mode) -> Result<()> { // check the connectors for connector in self.0.borrow().connectors.iter() { - if !connector::Info::load_from_device(self.0.borrow().graphics.head().head().head(), *connector)? + if !connector::Info::load_from_device(self.0.borrow().graphics.head().head().head(), *connector) + .chain_err(|| { + ErrorKind::DrmDev(format!( + "{:?}", + self.0.borrow().graphics.head().head().head() + )) + })? .modes() .contains(&mode) { - return Err(DrmError::Mode(ModeError::ModeNotSuitable)); + bail!(ErrorKind::ModeNotSuitable(mode)); } } @@ -303,61 +339,76 @@ impl DrmBackend { let (w, h) = mode.size(); - internal - .graphics - .rent_all_mut(|graphics| -> Result<(), DrmError> { - // Recreate the surface and the related resources to match the new - // resolution. - debug!(logger, "Reinitializing surface for new mode: {}:{}", w, h); - graphics.gbm.surface = Surface::try_new( - { - // create a new gbm surface - debug!(logger, "Creating GbmSurface"); - Box::new(graphics.context.devices.gbm.create_surface( + internal.graphics.rent_all_mut(|graphics| -> Result<()> { + // Recreate the surface and the related resources to match the new + // resolution. + debug!(logger, "Reinitializing surface for new mode: {}:{}", w, h); + graphics.gbm.surface = Surface::try_new( + { + // create a new gbm surface + debug!(logger, "Creating GbmSurface"); + Box::new(graphics + .context + .devices + .gbm + .create_surface( w as u32, h as u32, GbmFormat::XRGB8888, &[BufferObjectFlags::Scanout, BufferObjectFlags::Rendering], - )?) - }, - |surface| { - // create an egl surface from the gbm one - debug!(logger, "Creating EGLSurface"); - let egl_surface = graphics.context.egl.create_surface(&surface)?; + ) + .chain_err(|| ErrorKind::GbmInitFailed)?) + }, + |surface| { + // create an egl surface from the gbm one + debug!(logger, "Creating EGLSurface"); + let egl_surface = graphics.context.egl.create_surface(&surface)?; - // make it active for the first `crtc::set` - // (which is needed before the first page_flip) - unsafe { egl_surface.make_current()? }; - egl_surface.swap_buffers()?; + // make it active for the first `crtc::set` + // (which is needed before the first page_flip) + unsafe { + egl_surface + .make_current() + .chain_err(|| ErrorKind::FailedToSwap)? + }; + egl_surface + .swap_buffers() + .chain_err(|| ErrorKind::FailedToSwap)?; - let mut front_bo = surface.lock_front_buffer()?; - debug!(logger, "FrontBuffer color format: {:?}", front_bo.format()); - // we need a framebuffer per front_buffer - let fb = framebuffer::create(graphics.context.devices.drm, &*front_bo)?; + let mut front_bo = surface + .lock_front_buffer() + .chain_err(|| ErrorKind::FailedToSwap)?; + debug!(logger, "FrontBuffer color format: {:?}", front_bo.format()); + // we need a framebuffer per front_buffer + let fb = framebuffer::create(graphics.context.devices.drm, &*front_bo).chain_err(|| { + ErrorKind::DrmDev(format!("{:?}", graphics.context.devices.drm)) + })?; - debug!(logger, "Initialize screen"); - crtc::set( - graphics.context.devices.drm, - crtc, - fb.handle(), - &connectors, - (0, 0), - Some(mode), - )?; - front_bo.set_userdata(fb); + debug!(logger, "Initialize screen"); + crtc::set( + graphics.context.devices.drm, + crtc, + fb.handle(), + &connectors, + (0, 0), + Some(mode), + ).chain_err(|| { + ErrorKind::DrmDev(format!("{:?}", graphics.context.devices.drm)) + })?; + front_bo.set_userdata(fb); - Ok(EGL { - surface: egl_surface, - buffers: GbmBuffers { - front_buffer: Cell::new(front_bo), - next_buffer: Cell::new(None), - }, - }) - }, - )?; + Ok(EGL { + surface: egl_surface, + buffers: GbmBuffers { + front_buffer: Cell::new(front_bo), + next_buffer: Cell::new(None), + }, + }) + }, + )?; - Ok(()) - })?; + Ok(()) + })?; info!(logger, "Setting new mode: {:?}", mode.name()); internal.mode = mode; @@ -375,34 +426,46 @@ impl DrmBackend { impl GraphicsBackend for DrmBackend { type CursorFormat = ImageBuffer, Vec>; - type Error = DrmError; + type Error = Error; - fn set_cursor_position(&self, x: u32, y: u32) -> Result<(), DrmError> { + fn set_cursor_position(&self, x: u32, y: u32) -> Result<()> { trace!(self.0.borrow().logger, "Move the cursor to {},{}", x, y); crtc::move_cursor( self.0.borrow().graphics.head().head().head(), self.0.borrow().crtc, (x as i32, y as i32), - ).map_err(DrmError::from) + ).chain_err(|| { + ErrorKind::DrmDev(format!( + "{:?}", + self.0.borrow().graphics.head().head().head() + )) + }) } fn set_cursor_representation(&self, buffer: ImageBuffer, Vec>, hotspot: (u32, u32)) - -> Result<(), DrmError> { + -> Result<()> { let (w, h) = buffer.dimensions(); debug!(self.0.borrow().logger, "Importing cursor"); /// import the cursor into a buffer we can render self.0 .borrow_mut() .graphics - .rent_all_mut(|graphics| -> Result<(), DrmError> { + .rent_all_mut(|graphics| -> Result<()> { graphics.gbm.cursor = { - let mut cursor = graphics.context.devices.gbm.create_buffer_object( - w, - h, - GbmFormat::ARGB8888, - &[BufferObjectFlags::Cursor, BufferObjectFlags::Write], - )?; - cursor.write(&*buffer.into_raw())?; + let mut cursor = graphics + .context + .devices + .gbm + .create_buffer_object( + w, + h, + GbmFormat::ARGB8888, + &[BufferObjectFlags::Cursor, BufferObjectFlags::Write], + ) + .chain_err(|| ErrorKind::GbmInitFailed)?; + cursor + .write(&*buffer.into_raw()) + .chain_err(|| ErrorKind::GbmInitFailed)?; cursor }; Ok(()) @@ -429,7 +492,12 @@ impl GraphicsBackend for DrmBackend { .graphics .rent(|gbm| Buffer::handle(&gbm.cursor)), (w, h), - ).map_err(DrmError::from) + ).chain_err(|| { + ErrorKind::DrmDev(format!( + "{:?}", + self.0.borrow().graphics.head().head().head() + )) + }) } else { Ok(()) } @@ -437,7 +505,7 @@ impl GraphicsBackend for DrmBackend { } impl EGLGraphicsBackend for DrmBackend { - fn swap_buffers(&self) -> Result<(), SwapBuffersError> { + fn swap_buffers(&self) -> ::std::result::Result<(), SwapBuffersError> { self.0.borrow().graphics.rent_all(|graphics| { // We cannot call lock_front_buffer anymore without releasing the previous buffer, which will happen when the page flip is done if graphics.gbm.surface.rent(|egl| { @@ -503,7 +571,7 @@ impl EGLGraphicsBackend for DrmBackend { .rent(|context| context.is_current()) } - unsafe fn make_current(&self) -> Result<(), SwapBuffersError> { + unsafe fn make_current(&self) -> ::std::result::Result<(), SwapBuffersError> { self.0 .borrow() .graphics diff --git a/src/backend/drm/error.rs b/src/backend/drm/error.rs index f652c5a..00cd73c 100644 --- a/src/backend/drm/error.rs +++ b/src/backend/drm/error.rs @@ -1,110 +1,52 @@ -use backend::graphics::egl::{CreationError, SwapBuffersError}; -use drm::result::Error as DrmError; -use gbm::FrontBufferError; +//! +//! Errors thrown by the `DrmDevice` and `DrmBackend` +//! + +use backend::graphics::egl; +use drm::control::{connector, crtc, Mode}; use rental::TryNewError; -use std::error::{self, Error as ErrorTrait}; -use std::fmt; -use std::io::Error as IoError; +error_chain! { + errors { + #[doc = "The `DrmDevice` encountered an access error"] + DrmDev(dev: String) { + description("The drm device encountered an access error"), + display("The drm device ({:?}) encountered an access error", dev), + } -/// Error summing up error types related to all underlying libraries -/// involved in creating the a `DrmDevice`/`DrmBackend` -#[derive(Debug)] -pub enum Error { - /// The `DrmDevice` has encountered an error on an ioctl - Drm(DrmError), - /// The `EGLContext` could not be created - EGLCreation(CreationError), - /// Swapping Buffers via EGL was not possible - EGLSwap(SwapBuffersError), - /// Locking the front buffer of the underlying `GbmSurface` failed - Gbm(FrontBufferError), - /// A generic IO-Error happened accessing the underlying devices - Io(IoError), - /// Selected an invalid Mode - Mode(ModeError), - /// Error related to the selected crtc - Crtc(CrtcError), -} + #[doc = "Creation of gbm resource failed"] + GbmInitFailed { + description("Creation of gbm resource failed"), + display("Creation of gbm resource failed"), + } -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!( - f, - "DrmBackend error: {}", - match self { - &Error::Drm(ref x) => x as &error::Error, - &Error::EGLCreation(ref x) => x as &error::Error, - &Error::EGLSwap(ref x) => x as &error::Error, - &Error::Gbm(ref x) => x as &error::Error, - &Error::Io(ref x) => x as &error::Error, - &Error::Mode(ref x) => x as &error::Error, - &Error::Crtc(ref x) => x as &error::Error, - } - ) - } -} + #[doc = "Swapping front buffers failed"] + FailedToSwap { + description("Swapping front buffers failed"), + display("Swapping front buffers failed"), + } -impl error::Error for Error { - fn description(&self) -> &str { - "DrmBackend error" - } + #[doc = "mode is not compatible with all given connectors"] + ModeNotSuitable(mode: Mode) { + description("Mode is not compatible with all given connectors"), + display("Mode ({:?}) is not compatible with all given connectors", mode), + } - fn cause(&self) -> Option<&error::Error> { - match self { - &Error::Drm(ref x) => Some(x as &error::Error), - &Error::EGLCreation(ref x) => Some(x as &error::Error), - &Error::EGLSwap(ref x) => Some(x as &error::Error), - &Error::Gbm(ref x) => Some(x as &error::Error), - &Error::Io(ref x) => Some(x as &error::Error), - &Error::Mode(ref x) => Some(x as &error::Error), - &Error::Crtc(ref x) => Some(x as &error::Error), + #[doc = "The given crtc is already in use by another backend"] + CrtcAlreadyInUse(crtc: crtc::Handle) { + description("The given crtc is already in use by another backend"), + display("The given crtc ({:?}) is already in use by another backend", crtc), + } + + #[doc = "No encoder was found for a given connector on the set crtc"] + NoSuitableEncoder(connector: connector::Info, crtc: crtc::Handle) { + description("No encoder found for given connector on set crtc"), + display("No encoder found for the given connector '{:?}' on the set crtc ({:?})", connector.connector_type(), crtc), } } -} -impl From for Error { - fn from(err: DrmError) -> Error { - Error::Drm(err) - } -} - -impl From for Error { - fn from(err: CreationError) -> Error { - Error::EGLCreation(err) - } -} - -impl From for Error { - fn from(err: SwapBuffersError) -> Error { - Error::EGLSwap(err) - } -} - -impl From for Error { - fn from(err: FrontBufferError) -> Error { - Error::Gbm(err) - } -} - -impl From for Error { - fn from(err: IoError) -> Error { - Error::Io(err) - } -} - -impl From for Error { - fn from(err: ModeError) -> Error { - match err { - err @ ModeError::ModeNotSuitable => Error::Mode(err), - ModeError::FailedToLoad(err) => Error::Drm(err), - } - } -} - -impl From for Error { - fn from(err: CrtcError) -> Error { - Error::Crtc(err) + links { + EGL(egl::Error, egl::ErrorKind) #[doc = "EGL error"]; } } @@ -113,72 +55,3 @@ impl From> for Error { err.0 } } - -/// Error when trying to select an invalid mode -#[derive(Debug)] -pub enum ModeError { - /// `Mode` is not compatible with all given connectors - ModeNotSuitable, - /// Failed to load `Mode` information - FailedToLoad(DrmError), -} - -impl fmt::Display for ModeError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", self.description())?; - if let Some(cause) = self.cause() { - write!(f, "\tCause: {}", cause)?; - } - Ok(()) - } -} - -impl error::Error for ModeError { - fn description(&self) -> &str { - match self { - &ModeError::ModeNotSuitable => "Mode does not match all attached connectors", - &ModeError::FailedToLoad(_) => "Failed to load mode information from device", - } - } - - fn cause(&self) -> Option<&error::Error> { - match self { - &ModeError::FailedToLoad(ref err) => Some(err as &error::Error), - _ => None, - } - } -} - -/// Errors related to the selected crtc -#[derive(Debug)] -pub enum CrtcError { - /// Selected crtc is already in use by another `DrmBackend` - AlreadyInUse, - /// For the selected crtc no encoder exists that supports all connectors - NoSuitableEncoder, -} - -impl fmt::Display for CrtcError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", self.description())?; - if let Some(cause) = self.cause() { - write!(f, "\tCause: {}", cause)?; - } - Ok(()) - } -} - -impl error::Error for CrtcError { - fn description(&self) -> &str { - match self { - &CrtcError::AlreadyInUse => "Crtc is already in use by another DrmBackend", - &CrtcError::NoSuitableEncoder => "Crtc has no supported encoder that can drive all connectors", - } - } - - fn cause(&self) -> Option<&error::Error> { - match self { - _ => None, - } - } -} diff --git a/src/backend/drm/mod.rs b/src/backend/drm/mod.rs index a72134f..9281b70 100644 --- a/src/backend/drm/mod.rs +++ b/src/backend/drm/mod.rs @@ -205,11 +205,11 @@ use wayland_server::EventLoopHandle; use wayland_server::sources::{FdEventSourceHandler, FdInterest}; mod backend; -mod error; +pub mod error; pub use self::backend::{DrmBackend, Id}; use self::backend::DrmBackendInternal; -pub use self::error::{CrtcError, Error as DrmError, ModeError}; +use self::error::*; /// Internal struct as required by the drm crate #[derive(Debug)] @@ -275,7 +275,7 @@ impl DrmDevice { /// # Safety /// The file descriptor might not be valid and needs to be owned by smithay, /// make sure not to share it. Otherwise undefined behavior might occur. - pub unsafe fn new_from_fd(fd: RawFd, logger: L) -> Result + pub unsafe fn new_from_fd(fd: RawFd, logger: L) -> Result where L: Into>, { @@ -299,8 +299,7 @@ impl DrmDevice { /// # Safety /// The file descriptor might not be valid and needs to be owned by smithay, /// make sure not to share it. Otherwise undefined behavior might occur. - pub unsafe fn new_from_fd_with_gl_attr(fd: RawFd, attributes: GlAttributes, logger: L) - -> Result + pub unsafe fn new_from_fd_with_gl_attr(fd: RawFd, attributes: GlAttributes, logger: L) -> Result where L: Into>, { @@ -311,7 +310,7 @@ impl DrmDevice { /// /// Returns an error if the file is no valid drm node or context creation was not /// successful. - pub fn new_from_file(file: File, logger: L) -> Result + pub fn new_from_file(file: File, logger: L) -> Result where L: Into>, { @@ -331,15 +330,14 @@ impl DrmDevice { /// /// Returns an error if the file is no valid drm node or context creation was not /// successful. - pub fn new_from_file_with_gl_attr(file: File, attributes: GlAttributes, logger: L) - -> Result + pub fn new_from_file_with_gl_attr(file: File, attributes: GlAttributes, logger: L) -> Result where L: Into>, { DrmDevice::new(DrmDev::new_from_file(file), attributes, logger) } - fn new(drm: DrmDev, attributes: GlAttributes, logger: L) -> Result + fn new(drm: DrmDev, attributes: GlAttributes, logger: L) -> Result where L: Into>, { @@ -365,7 +363,7 @@ impl DrmDevice { context: Rc::new(Context::try_new( Box::new(Devices::try_new(Box::new(drm), |drm| { debug!(log, "Creating gbm device"); - GbmDevice::new_from_drm::>(drm).map_err(DrmError::from) + GbmDevice::new_from_drm::>(drm).chain_err(|| ErrorKind::GbmInitFailed) })?), |devices| { debug!(log, "Creating egl context from gbm device"); @@ -379,7 +377,7 @@ impl DrmDevice { ..Default::default() }, log.clone(), - ).map_err(DrmError::from) + ).map_err(Error::from) }, )?), backends: Vec::new(), @@ -393,15 +391,14 @@ impl DrmDevice { /// /// Errors if initialization fails or the mode is not available on all given /// connectors. - pub fn create_backend(&mut self, crtc: crtc::Handle, mode: Mode, connectors: I) - -> Result + pub fn create_backend(&mut self, crtc: crtc::Handle, mode: Mode, connectors: I) -> Result where I: Into>, { for backend in self.backends.iter() { if let Some(backend) = backend.upgrade() { if backend.borrow().is_crtc(crtc) { - return Err(DrmError::Crtc(CrtcError::AlreadyInUse)); + bail!(ErrorKind::CrtcAlreadyInUse(crtc)) } } } @@ -411,21 +408,25 @@ impl DrmDevice { // check if we have an encoder for every connector for connector in connectors.iter() { - let con_info = connector::Info::load_from_device(self.context.head().head(), *connector)?; + let con_info = connector::Info::load_from_device(self.context.head().head(), *connector) + .chain_err(|| { + ErrorKind::DrmDev(format!("{:?}", self.context.head().head())) + })?; // then check for every connector which encoders it does support let encoders = con_info .encoders() .iter() .map(|encoder| { - encoder::Info::load_from_device(self.context.head().head(), *encoder) - .map_err(DrmError::from) + encoder::Info::load_from_device(self.context.head().head(), *encoder).chain_err(|| { + ErrorKind::DrmDev(format!("{:?}", self.context.head().head())) + }) }) - .collect::, DrmError>>()?; + .collect::>>()?; // and if any encoder supports the selected crtc if !encoders.iter().any(|encoder| encoder.supports_crtc(crtc)) { - return Err(DrmError::Crtc(CrtcError::NoSuitableEncoder)); + bail!(ErrorKind::NoSuitableEncoder(con_info, crtc)) } } diff --git a/src/backend/graphics/egl.rs b/src/backend/graphics/egl.rs index d08c8e4..7c32f43 100644 --- a/src/backend/graphics/egl.rs +++ b/src/backend/graphics/egl.rs @@ -10,11 +10,11 @@ use super::GraphicsBackend; use gbm::{AsRaw, Device as GbmDevice, Surface as GbmSurface}; use libloading::Library; use nix::{c_int, c_void}; +use rental::TryNewError; use slog; -use std::error::{self, Error}; +use std::error; use std::ffi::{CStr, CString}; use std::fmt; -use std::io; use std::marker::PhantomData; use std::mem; use std::ops::{Deref, DerefMut}; @@ -74,7 +74,7 @@ pub enum NativeSurfacePtr { } /// Enumerates all supported backends -#[derive(Clone, Copy, PartialEq)] +#[derive(Debug, Clone, Copy, PartialEq)] pub enum NativeType { /// X11 window & surface X11, @@ -82,66 +82,77 @@ pub enum NativeType { Wayland, /// Gbm surface Gbm, + /// Unknown + Unknown, } -/// Error that can happen while creating an `EGLContext` or `EGLSurface` -#[derive(Debug)] -pub enum CreationError { - /// I/O error from the underlying system - IoError(io::Error), - /// Operating System error - OsError(String), - /// The requested OpenGl version is not supported by the graphics system - OpenGlVersionNotSupported, - /// There is no pixel format available that fulfills all requirements - NoAvailablePixelFormat, - /// Surface creation from an unsupport combination - /// - /// E.g creating a surface from an X11 window on a context created from a wayland display - NonMatchingSurfaceType, - /// Context creation is not supported on this system - NotSupported, -} +error_chain! { + errors { + #[doc = "The requested OpenGL version is not supported"] + OpenGlVersionNotSupported(version: (u8, u8)) { + description("The requested OpenGL version is not supported."), + display("The requested OpenGL version {:?} is not supported.", version), + } -impl From for CreationError { - fn from(err: io::Error) -> Self { - CreationError::IoError(err) + #[doc = "The EGL implementation does not support creating OpenGL ES contexts"] + OpenGlesNotSupported { + description("The EGL implementation does not support creating OpenGL ES contexts") + } + + #[doc = "No available pixel format matched the criteria"] + NoAvailablePixelFormat { + description("No available pixel format matched the criteria.") + } + + #[doc = "Surface type does not match the context type"] + NonMatchingSurfaceType(context: NativeType, surface: NativeType) { + description("Surface type does not match the context type."), + display("Surface type '{:?}' does not match the context type '{:?}'.", surface, context), + } + + #[doc = "Context creation is not supported on the current window system"] + NotSupported { + description("Context creation is not supported on the current window system.") + } + + #[doc = "Loading libEGL failed"] + LoadingEGLFailed { + description("Loading libEGL failed"), + } + + #[doc = "EGL was unable to optain a valid EGL Display"] + DisplayNotSupported { + description("EGL was unable to optain a valid EGL Display") + } + + #[doc = "eglInitialize returned an error"] + InitFailed { + description("Failed to initialize EGL") + } + + #[doc = "Failed to configure the EGL context"] + ConfigFailed { + description("Failed to configure the EGL context") + } + + #[doc = "Context creation failed as one or more requirements could not be met. Try removing some gl attributes or pixel format requirements"] + CreationFailed { + description("Context creation failed as one or more requirements could not be met. Try removing some gl attributes or pixel format requirements") + } + + #[doc = "eglCreateWindowSurface failed"] + SurfaceCreationFailed { + description("Failed to create a new EGLSurface") + } + + #[doc = "The reason of failure could not be determined"] + Unknown(err_no: u32) } } -impl fmt::Display for CreationError { - fn fmt(&self, formatter: &mut fmt::Formatter) -> Result<(), fmt::Error> { - formatter.write_str(self.description())?; - if let Some(err) = error::Error::cause(self) { - write!(formatter, ": {}", err)?; - } - Ok(()) - } -} - -impl error::Error for CreationError { - fn description(&self) -> &str { - match *self { - CreationError::IoError(ref err) => err.description(), - CreationError::OsError(ref text) => text, - CreationError::OpenGlVersionNotSupported => { - "The requested OpenGL version is not \ - supported." - } - CreationError::NoAvailablePixelFormat => { - "Couldn't find any pixel format that matches \ - the criterias." - } - CreationError::NonMatchingSurfaceType => "Surface type does not match the context type.", - CreationError::NotSupported => "Context creation is not supported on the current window system", - } - } - - fn cause(&self) -> Option<&error::Error> { - match *self { - CreationError::IoError(ref err) => Some(err), - _ => None, - } +impl From> for Error { + fn from(err: TryNewError) -> Error { + err.0 } } @@ -155,20 +166,22 @@ pub unsafe trait NativeSurface { type Keep: 'static; /// Return a surface for the given type if possible - fn surface(&self, backend: NativeType) -> Result<(NativeSurfacePtr, Self::Keep), CreationError>; + fn surface(&self, backend: NativeType) -> Result<(NativeSurfacePtr, Self::Keep)>; } #[cfg(feature = "backend_winit")] unsafe impl NativeSurface for WinitWindow { type Keep = Option; - fn surface(&self, backend_type: NativeType) - -> Result<(NativeSurfacePtr, Option), CreationError> { + fn surface(&self, backend_type: NativeType) -> Result<(NativeSurfacePtr, Option)> { match backend_type { NativeType::X11 => if let Some(window) = self.get_xlib_window() { Ok((NativeSurfacePtr::X11(window), None)) } else { - return Err(CreationError::NonMatchingSurfaceType); + bail!(ErrorKind::NonMatchingSurfaceType( + NativeType::Wayland, + NativeType::X11 + )); }, NativeType::Wayland => if let Some(surface) = self.get_wayland_surface() { let (w, h) = self.get_inner_size().unwrap(); @@ -179,9 +192,12 @@ unsafe impl NativeSurface for WinitWindow { Some(egl_surface), )) } else { - return Err(CreationError::NonMatchingSurfaceType); + bail!(ErrorKind::NonMatchingSurfaceType( + NativeType::X11, + NativeType::Wayland + )); }, - _ => Err(CreationError::NonMatchingSurfaceType), + x => bail!(ErrorKind::NonMatchingSurfaceType(NativeType::Unknown, x)), } } } @@ -190,18 +206,18 @@ unsafe impl NativeSurface for WinitWindow { unsafe impl<'a, T: 'static> NativeSurface for GbmSurface<'a, T> { type Keep = (); - fn surface(&self, backend: NativeType) -> Result<(NativeSurfacePtr, Self::Keep), CreationError> { + fn surface(&self, backend: NativeType) -> Result<(NativeSurfacePtr, Self::Keep)> { match backend { NativeType::Gbm => Ok((NativeSurfacePtr::Gbm(self.as_raw() as *const _), ())), - _ => Err(CreationError::NonMatchingSurfaceType), + x => bail!(ErrorKind::NonMatchingSurfaceType(NativeType::Gbm, x)), } } } unsafe impl NativeSurface for () { type Keep = (); - fn surface(&self, _backend: NativeType) -> Result<(NativeSurfacePtr, ()), CreationError> { - Err(CreationError::NotSupported) + fn surface(&self, _backend: NativeType) -> Result<(NativeSurfacePtr, ())> { + bail!(ErrorKind::NotSupported) } } @@ -225,7 +241,7 @@ impl<'a> EGLContext<'a, ()> { #[cfg(feature = "backend_winit")] pub fn new_from_winit(window: &'a WinitWindow, attributes: GlAttributes, reqs: PixelFormatRequirements, logger: L) - -> Result, CreationError> + -> Result> where L: Into>, { @@ -242,7 +258,7 @@ impl<'a> EGLContext<'a, ()> { NativeDisplayPtr::Wayland(display) } else { error!(log, "Window is backed by an unsupported graphics framework"); - return Err(CreationError::NotSupported); + bail!(ErrorKind::NotSupported) }, attributes, reqs, @@ -255,7 +271,7 @@ impl<'a> EGLContext<'a, ()> { #[cfg(feature = "backend_drm")] pub fn new_from_gbm(gbm: &'a GbmDevice<'a>, attributes: GlAttributes, reqs: PixelFormatRequirements, logger: L) - -> Result>, CreationError> + -> Result>> where L: Into>, { @@ -275,7 +291,7 @@ impl<'a> EGLContext<'a, ()> { impl<'a, T: NativeSurface> EGLContext<'a, T> { unsafe fn new(native: NativeDisplayPtr, mut attributes: GlAttributes, reqs: PixelFormatRequirements, log: ::slog::Logger) - -> Result, CreationError> + -> Result> where T: NativeSurface, { @@ -297,12 +313,12 @@ impl<'a, T: NativeSurface> EGLContext<'a, T> { } } } - Some((1, _)) => { + Some((1, x)) => { error!( log, "OpenGLES 1.* is not supported by the EGL renderer backend" ); - return Err(CreationError::OpenGlVersionNotSupported); + bail!(ErrorKind::OpenGlVersionNotSupported((1, x))); } Some(version) => { error!( @@ -310,12 +326,12 @@ impl<'a, T: NativeSurface> EGLContext<'a, T> { "OpenGLES {:?} is unknown and not supported by the EGL renderer backend", version ); - return Err(CreationError::OpenGlVersionNotSupported); + bail!(ErrorKind::OpenGlVersionNotSupported(version)); } }; trace!(log, "Loading libEGL"); - let lib = Library::new("libEGL.so.1")?; + let lib = Library::new("libEGL.so.1").chain_err(|| ErrorKind::LoadingEGLFailed)?; let egl = ffi::egl::Egl::load_with(|sym| { let name = CString::new(sym).unwrap(); let symbol = lib.get::<*mut c_void>(name.as_bytes()); @@ -418,6 +434,7 @@ impl<'a, T: NativeSurface> EGLContext<'a, T> { if display == ffi::egl::NO_DISPLAY { error!(log, "EGL Display is not valid"); + bail!(ErrorKind::DisplayNotSupported); } let egl_version = { @@ -425,7 +442,7 @@ impl<'a, T: NativeSurface> EGLContext<'a, T> { let mut minor: ffi::egl::types::EGLint = mem::uninitialized(); if egl.Initialize(display, &mut major, &mut minor) == 0 { - return Err(CreationError::OsError(String::from("eglInitialize failed"))); + bail!(ErrorKind::InitFailed); } info!(log, "EGL Initialized"); @@ -451,7 +468,7 @@ impl<'a, T: NativeSurface> EGLContext<'a, T> { log, "OpenGLES not supported by the underlying EGL implementation" ); - return Err(CreationError::OpenGlVersionNotSupported); + bail!(ErrorKind::OpenGlesNotSupported); } let descriptor = { @@ -477,7 +494,7 @@ impl<'a, T: NativeSurface> EGLContext<'a, T> { log, "OpenglES 3.* is not supported on EGL Versions lower then 1.3" ); - return Err(CreationError::NoAvailablePixelFormat); + bail!(ErrorKind::NoAvailablePixelFormat); } trace!(log, "Setting RENDERABLE_TYPE to OPENGL_ES3"); out.push(ffi::egl::RENDERABLE_TYPE as c_int); @@ -492,7 +509,7 @@ impl<'a, T: NativeSurface> EGLContext<'a, T> { log, "OpenglES 2.* is not supported on EGL Versions lower then 1.3" ); - return Err(CreationError::NoAvailablePixelFormat); + bail!(ErrorKind::NoAvailablePixelFormat); } trace!(log, "Setting RENDERABLE_TYPE to OPENGL_ES2"); out.push(ffi::egl::RENDERABLE_TYPE as c_int); @@ -561,7 +578,7 @@ impl<'a, T: NativeSurface> EGLContext<'a, T> { if reqs.stereoscopy { error!(log, "Stereoscopy is currently unsupported (sorry!)"); - return Err(CreationError::NoAvailablePixelFormat); + bail!(ErrorKind::NoAvailablePixelFormat); } out.push(ffi::egl::NONE as c_int); @@ -579,13 +596,11 @@ impl<'a, T: NativeSurface> EGLContext<'a, T> { &mut num_configs, ) == 0 { - return Err(CreationError::OsError( - String::from("eglChooseConfig failed"), - )); + bail!(ErrorKind::ConfigFailed); } if num_configs == 0 { error!(log, "No matching color format found"); - return Err(CreationError::NoAvailablePixelFormat); + bail!(ErrorKind::NoAvailablePixelFormat); } // analyzing each config @@ -596,7 +611,7 @@ impl<'a, T: NativeSurface> EGLContext<'a, T> { let res = $egl.GetConfigAttrib($display, $config, $attr as ffi::egl::types::EGLint, &mut value); if res == 0 { - return Err(CreationError::OsError(String::from("eglGetConfigAttrib failed"))); + bail!(ErrorKind::ConfigFailed); } value } @@ -654,14 +669,8 @@ impl<'a, T: NativeSurface> EGLContext<'a, T> { if context.is_null() { match egl.GetError() as u32 { - ffi::egl::BAD_ATTRIBUTE => { - error!( - log, - "Context creation failed as one or more requirements could not be met. Try removing some gl attributes or pixel format requirements" - ); - return Err(CreationError::OpenGlVersionNotSupported); - } - e => panic!("eglCreateContext failed: 0x{:x}", e), + ffi::egl::BAD_ATTRIBUTE => bail!(ErrorKind::CreationFailed), + err_no => bail!(ErrorKind::Unknown(err_no)), } } debug!(log, "EGL context successfully created"); @@ -709,7 +718,7 @@ impl<'a, T: NativeSurface> EGLContext<'a, T> { } /// Creates a surface bound to the given egl context for rendering - pub fn create_surface<'b>(&'a self, native: &'b T) -> Result, CreationError> { + pub fn create_surface<'b>(&'a self, native: &'b T) -> Result> { trace!(self.logger, "Creating EGL window surface..."); let (surface, keep) = native.surface(self.backend_type)?; @@ -728,9 +737,7 @@ impl<'a, T: NativeSurface> EGLContext<'a, T> { }; if egl_surface.is_null() { - return Err(CreationError::OsError( - String::from("eglCreateWindowSurface failed"), - )); + bail!(ErrorKind::SurfaceCreationFailed); } debug!(self.logger, "EGL surface successfully created"); @@ -801,7 +808,7 @@ impl<'a, 'b, T: NativeSurface> DerefMut for EGLSurface<'a, 'b, T> { impl<'context, 'surface, T: NativeSurface> EGLSurface<'context, 'surface, T> { /// Swaps buffers at the end of a frame. - pub fn swap_buffers(&self) -> Result<(), SwapBuffersError> { + pub fn swap_buffers(&self) -> ::std::result::Result<(), SwapBuffersError> { let ret = unsafe { self.context .egl @@ -824,7 +831,7 @@ impl<'context, 'surface, T: NativeSurface> EGLSurface<'context, 'surface, T> { /// /// This function is marked unsafe, because the context cannot be made current /// on multiple threads. - pub unsafe fn make_current(&self) -> Result<(), SwapBuffersError> { + pub unsafe fn make_current(&self) -> ::std::result::Result<(), SwapBuffersError> { let ret = self.context.egl.MakeCurrent( self.context.display as *const _, self.surface as *const _, @@ -879,7 +886,8 @@ pub enum SwapBuffersError { } impl fmt::Display for SwapBuffersError { - fn fmt(&self, formatter: &mut fmt::Formatter) -> Result<(), fmt::Error> { + fn fmt(&self, formatter: &mut fmt::Formatter) -> ::std::result::Result<(), fmt::Error> { + use std::error::Error; write!(formatter, "{}", self.description()) } } @@ -983,7 +991,7 @@ pub struct PixelFormat { /// and can be used to render upon pub trait EGLGraphicsBackend: GraphicsBackend { /// Swaps buffers at the end of a frame. - fn swap_buffers(&self) -> Result<(), SwapBuffersError>; + fn swap_buffers(&self) -> ::std::result::Result<(), SwapBuffersError>; /// Returns the address of an OpenGL function. /// @@ -1006,7 +1014,7 @@ pub trait EGLGraphicsBackend: GraphicsBackend { /// /// This function is marked unsafe, because the context cannot be made current /// on multiple threads. - unsafe fn make_current(&self) -> Result<(), SwapBuffersError>; + 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/winit.rs b/src/backend/winit.rs index f1828be..04d262f 100644 --- a/src/backend/winit.rs +++ b/src/backend/winit.rs @@ -2,21 +2,20 @@ use backend::{SeatInternal, TouchSlotInternal}; use backend::graphics::GraphicsBackend; -use backend::graphics::egl::{CreationError, EGLContext, EGLGraphicsBackend, GlAttributes, PixelFormat, +use backend::graphics::egl::{self, EGLContext, EGLGraphicsBackend, GlAttributes, PixelFormat, PixelFormatRequirements, SwapBuffersError}; use backend::input::{Axis, AxisSource, Event as BackendEvent, InputBackend, InputHandler, KeyState, KeyboardKeyEvent, MouseButton, MouseButtonState, PointerAxisEvent, PointerButtonEvent, PointerMotionAbsoluteEvent, Seat, SeatCapabilities, TouchCancelEvent, TouchDownEvent, TouchMotionEvent, TouchSlot, TouchUpEvent, UnusedEvent}; use nix::c_void; - +use rental::TryNewError; use std::cmp; -use std::error::Error; +use std::error; use std::fmt; use std::rc::Rc; -use winit::{CreationError as WinitCreationError, ElementState, Event, EventsLoop, KeyboardInput, - MouseButton as WinitMouseButton, MouseCursor, MouseScrollDelta, Touch, TouchPhase, - WindowBuilder, WindowEvent}; +use winit::{ElementState, Event, EventsLoop, KeyboardInput, MouseButton as WinitMouseButton, MouseCursor, + MouseScrollDelta, Touch, TouchPhase, WindowBuilder, WindowEvent}; rental! { mod rental { @@ -40,6 +39,24 @@ rental! { use self::rental::{Window, EGL}; +error_chain! { + errors { + #[doc = "Failed to initialize a window"] + InitFailed { + description("Failed to initialize a window") + } + } + + links { + EGL(egl::Error, egl::ErrorKind) #[doc = "EGL error"]; + } +} + +impl From> for Error { + fn from(err: TryNewError) -> Error { + err.0 + } +} /// Window with an active EGL Context created by `winit`. Implements the /// `EGLGraphicsBackend` graphics backend trait pub struct WinitGraphicsBackend { @@ -64,7 +81,7 @@ pub struct WinitInputBackend { /// Create a new `WinitGraphicsBackend`, which implements the `EGLGraphicsBackend` /// graphics backend trait and a corresponding `WinitInputBackend`, which implements /// the `InputBackend` trait -pub fn init(logger: L) -> Result<(WinitGraphicsBackend, WinitInputBackend), CreationError> +pub fn init(logger: L) -> Result<(WinitGraphicsBackend, WinitInputBackend)> where L: Into>, { @@ -81,7 +98,7 @@ where /// graphics backend trait, from a given `WindowBuilder` struct and a corresponding /// `WinitInputBackend`, which implements the `InputBackend` trait pub fn init_from_builder(builder: WindowBuilder, logger: L) - -> Result<(WinitGraphicsBackend, WinitInputBackend), CreationError> + -> Result<(WinitGraphicsBackend, WinitInputBackend)> where L: Into>, { @@ -101,9 +118,8 @@ where /// graphics backend trait, from a given `WindowBuilder` struct, as well as given /// `GlAttributes` for further customization of the rendering pipeline and a /// corresponding `WinitInputBackend`, which implements the `InputBackend` trait. -pub fn init_from_builder_with_gl_attr( - builder: WindowBuilder, attributes: GlAttributes, logger: L) - -> Result<(WinitGraphicsBackend, WinitInputBackend), CreationError> +pub fn init_from_builder_with_gl_attr(builder: WindowBuilder, attributes: GlAttributes, logger: L) + -> Result<(WinitGraphicsBackend, WinitInputBackend)> where L: Into>, { @@ -111,11 +127,13 @@ where info!(log, "Initializing a winit backend"); let events_loop = EventsLoop::new(); - let winit_window = builder.build(&events_loop)?; + let winit_window = builder + .build(&events_loop) + .chain_err(|| ErrorKind::InitFailed)?; debug!(log, "Window created"); - let window = match Window::try_new(Box::new(winit_window), |window| { - match EGL::try_new( + let window = Rc::new(Window::try_new(Box::new(winit_window), |window| { + EGL::try_new( Box::new(match EGLContext::new_from_winit( &*window, attributes, @@ -128,20 +146,12 @@ where log.clone(), ) { Ok(context) => context, - Err(err) => { - error!(log, "EGLContext creation failed:\n {}", err); - return Err(err); - } + Err(err) => bail!(err), }), |context| context.create_surface(window), - ) { - Ok(x) => Ok(x), - Err(::rental::TryNewError(err, _)) => return Err(err), - } - }) { - Ok(x) => Rc::new(x), - Err(::rental::TryNewError(err, _)) => return Err(err), - }; + ).map_err(egl::Error::from) + .map_err(Error::from) + })?); Ok(( WinitGraphicsBackend { @@ -172,12 +182,13 @@ impl GraphicsBackend for WinitGraphicsBackend { type CursorFormat = MouseCursor; type Error = (); - fn set_cursor_position(&self, x: u32, y: u32) -> Result<(), ()> { + fn set_cursor_position(&self, x: u32, y: u32) -> ::std::result::Result<(), ()> { debug!(self.logger, "Setting cursor position to {:?}", (x, y)); self.window.head().set_cursor_position(x as i32, y as i32) } - fn set_cursor_representation(&self, cursor: Self::CursorFormat, _hotspot: (u32, u32)) -> Result<(), ()> { + fn set_cursor_representation(&self, cursor: Self::CursorFormat, _hotspot: (u32, u32)) + -> ::std::result::Result<(), ()> { // Cannot log this one, as `CursorFormat` is not `Debug` and should not be debug!(self.logger, "Changing cursor representation"); self.window.head().set_cursor(cursor); @@ -186,7 +197,7 @@ impl GraphicsBackend for WinitGraphicsBackend { } impl EGLGraphicsBackend for WinitGraphicsBackend { - fn swap_buffers(&self) -> Result<(), SwapBuffersError> { + fn swap_buffers(&self) -> ::std::result::Result<(), SwapBuffersError> { trace!(self.logger, "Swapping buffers"); self.window .rent(|egl| egl.rent(|surface| surface.swap_buffers())) @@ -208,7 +219,7 @@ impl EGLGraphicsBackend for WinitGraphicsBackend { self.window.rent(|egl| egl.head().is_current()) } - unsafe fn make_current(&self) -> Result<(), SwapBuffersError> { + unsafe fn make_current(&self) -> ::std::result::Result<(), SwapBuffersError> { debug!(self.logger, "Setting EGL context to be the current context"); self.window .rent(|egl| egl.rent(|surface| surface.make_current())) @@ -228,7 +239,7 @@ pub enum WinitInputError { WindowClosed, } -impl Error for WinitInputError { +impl error::Error for WinitInputError { fn description(&self) -> &str { match *self { WinitInputError::WindowClosed => "Glutin Window was closed", @@ -238,6 +249,7 @@ impl Error for WinitInputError { impl fmt::Display for WinitInputError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use std::error::Error; write!(f, "{}", self.description()) } } @@ -576,7 +588,7 @@ impl InputBackend for WinitInputBackend { /// /// The linked `WinitGraphicsBackend` will error with a lost Context and should /// not be used anymore as well. - fn dispatch_new_events(&mut self) -> Result<(), WinitInputError> { + fn dispatch_new_events(&mut self) -> ::std::result::Result<(), WinitInputError> { let mut closed = false; { @@ -831,12 +843,3 @@ impl From for MouseButtonState { } } } - -impl From for CreationError { - fn from(error: WinitCreationError) -> Self { - match error { - WinitCreationError::OsError(x) => CreationError::OsError(x), - WinitCreationError::NotSupported => CreationError::NotSupported, - } - } -} diff --git a/src/lib.rs b/src/lib.rs index 76050ec..2598c77 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,6 +6,8 @@ #![cfg_attr(feature = "clippy", feature(plugin))] #![cfg_attr(feature = "clippy", plugin(clippy))] +// `error_chain!` can recurse deeply +#![recursion_limit = "1024"] extern crate image; extern crate nix; @@ -36,6 +38,9 @@ extern crate glium; extern crate slog; extern crate slog_stdlog; +#[macro_use] +extern crate error_chain; + pub mod backend; pub mod compositor; pub mod shm; From 457d6d1c358ee25e142b8788e8654d7794a62d2e Mon Sep 17 00:00:00 2001 From: Drakulix Date: Tue, 19 Sep 2017 17:26:11 +0200 Subject: [PATCH 15/18] Free framebuffers correctly --- src/backend/drm/backend.rs | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/backend/drm/backend.rs b/src/backend/drm/backend.rs index 6d0dedb..7703d84 100644 --- a/src/backend/drm/backend.rs +++ b/src/backend/drm/backend.rs @@ -424,6 +424,36 @@ impl DrmBackend { } } +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) + self.0.borrow_mut().graphics.rent_all_mut(|graphics| { + if let Some(fb) = graphics.gbm.surface.rent(|egl| { + if let Some(mut next) = egl.buffers.next_buffer.take() { + return next.take_userdata(); + } else { + if let Ok(mut next) = graphics.gbm.surface.head().lock_front_buffer() { + return next.take_userdata(); + } + } + None + }) { + // ignore failure at this point + let _ = framebuffer::destroy(&*graphics.context.devices.drm, fb.handle()); + } + + if let Some(fb) = graphics.gbm.surface.rent_mut(|egl| { + let first = egl.buffers.front_buffer.get_mut(); + first.take_userdata() + }) { + // ignore failure at this point + let _ = framebuffer::destroy(&*graphics.context.devices.drm, fb.handle()); + } + }) + } +} + impl GraphicsBackend for DrmBackend { type CursorFormat = ImageBuffer, Vec>; type Error = Error; From 0758ec98bab8e18ab69c7f97021c6a8e7d49fe11 Mon Sep 17 00:00:00 2001 From: Drakulix Date: Wed, 20 Sep 2017 17:09:37 +0200 Subject: [PATCH 16/18] Fix the drm backend for wayland-rs 0.10 --- examples/drm.rs | 230 +++++-------------------------------- src/backend/drm/backend.rs | 4 +- src/backend/drm/mod.rs | 122 +++++++++----------- 3 files changed, 89 insertions(+), 267 deletions(-) diff --git a/examples/drm.rs b/examples/drm.rs index 1da4faa..30541b0 100644 --- a/examples/drm.rs +++ b/examples/drm.rs @@ -4,7 +4,6 @@ extern crate glium; extern crate rand; #[macro_use(define_roles)] extern crate smithay; -extern crate wayland_protocols; extern crate wayland_server; #[macro_use] @@ -18,153 +17,19 @@ 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 glium::Surface; -use helpers::GliumDrawer; -use slog::*; -use smithay::backend::drm::{DrmBackend, DrmDevice, DrmHandler, Id}; +use helpers::{shell_implementation, surface_implementation, GliumDrawer, Roles, SurfaceData}; +use slog::{Drain, Logger}; +use smithay::backend::drm::{drm_device_bind, DrmBackend, DrmDevice, DrmHandler, Id}; use smithay::backend::graphics::egl::EGLGraphicsBackend; use smithay::backend::graphics::glium::{GliumGraphicsBackend, IntoGlium}; -use smithay::compositor::{self, CompositorHandler, CompositorToken, SubsurfaceRole, TraversalAction}; +use smithay::compositor::{compositor_init, CompositorToken, SubsurfaceRole, TraversalAction}; use smithay::compositor::roles::Role; -use smithay::shell::{self, PopupConfigure, PopupSurface, ShellClient, ShellHandler, ShellSurfaceRole, - ToplevelConfigure, ToplevelSurface}; -use smithay::shm::{ShmGlobal, ShmToken}; +use smithay::shell::{shell_init, ShellState}; +use smithay::shm::init_shm_global; use std::fs::OpenOptions; use std::io::Error as IoError; -use std::os::unix::io::AsRawFd; use std::time::Duration; -use wayland_protocols::unstable::xdg_shell::server::{zxdg_shell_v6, zxdg_toplevel_v6}; -use wayland_server::{Client, EventLoopHandle}; -use wayland_server::protocol::{wl_callback, wl_compositor, wl_output, wl_seat, wl_shell, wl_shm, - wl_subcompositor, wl_surface}; -use wayland_server::sources::READ; - -define_roles!(Roles => [ ShellSurface, ShellSurfaceRole ] ); - -struct SurfaceHandler { - shm_token: ShmToken, -} - -#[derive(Default)] -struct SurfaceData { - buffer: Option<(Vec, (u32, u32))>, - location: Option<(i32, i32)>, -} - -impl compositor::Handler for SurfaceHandler { - fn commit(&mut self, _evlh: &mut EventLoopHandle, _client: &Client, surface: &wl_surface::WlSurface, - token: CompositorToken) { - // we retrieve the contents of the associated buffer and copy it - token.with_surface_data(surface, |attributes| { - match attributes.buffer.take() { - Some(Some((buffer, (_x, _y)))) => { - // we ignore hotspot coordinates in this simple example - self.shm_token - .with_buffer_contents(&buffer, |slice, data| { - let offset = data.offset as usize; - let stride = data.stride as usize; - let width = data.width as usize; - let height = data.height as usize; - let mut new_vec = Vec::with_capacity(width * height * 4); - for i in 0..height { - new_vec.extend( - &slice[(offset + i * stride)..(offset + i * stride + width * 4)], - ); - } - attributes.user_data.buffer = - Some((new_vec, (data.width as u32, data.height as u32))); - }) - .unwrap(); - buffer.release(); - } - Some(None) => { - // erase the contents - attributes.user_data.buffer = None; - } - None => {} - } - }); - } - - fn frame(&mut self, _evlh: &mut EventLoopHandle, _client: &Client, _surface: &wl_surface::WlSurface, - callback: wl_callback::WlCallback, - _token: CompositorToken) { - callback.done(0); - } -} - -struct ShellSurfaceHandler { - token: CompositorToken, -} - -impl ShellSurfaceHandler { - fn new(token: CompositorToken) -> ShellSurfaceHandler { - ShellSurfaceHandler { token } - } -} - -impl shell::Handler for ShellSurfaceHandler { - fn new_client(&mut self, _evlh: &mut EventLoopHandle, _client: ShellClient<()>) {} - fn client_pong(&mut self, _evlh: &mut EventLoopHandle, _client: ShellClient<()>) {} - fn new_toplevel(&mut self, _evlh: &mut EventLoopHandle, - surface: ToplevelSurface) - -> ToplevelConfigure { - let wl_surface = surface.get_surface().unwrap(); - self.token.with_surface_data(wl_surface, |data| { - // place the window at a random location in the [0;300]x[0;300] square - use rand::distributions::{IndependentSample, Range}; - let range = Range::new(0, 300); - let mut rng = rand::thread_rng(); - let x = range.ind_sample(&mut rng); - let y = range.ind_sample(&mut rng); - data.user_data.location = Some((x, y)) - }); - ToplevelConfigure { - size: None, - states: vec![], - serial: 42, - } - } - fn new_popup(&mut self, _evlh: &mut EventLoopHandle, - _surface: PopupSurface) - -> PopupConfigure { - PopupConfigure { - size: (10, 10), - position: (10, 10), - serial: 42, - } - } - fn move_(&mut self, _evlh: &mut EventLoopHandle, - _surface: ToplevelSurface, _seat: &wl_seat::WlSeat, - _serial: u32) { - } - fn resize(&mut self, _evlh: &mut EventLoopHandle, - _surface: ToplevelSurface, _seat: &wl_seat::WlSeat, - _serial: u32, _edges: zxdg_toplevel_v6::ResizeEdge) { - } - fn grab(&mut self, _evlh: &mut EventLoopHandle, - _surface: PopupSurface, _seat: &wl_seat::WlSeat, - _serial: u32) { - } - fn change_display_state(&mut self, _evlh: &mut EventLoopHandle, - _surface: ToplevelSurface, - _maximized: Option, _minimized: Option, _fullscreen: Option, - _output: Option<&wl_output::WlOutput>) - -> ToplevelConfigure { - ToplevelConfigure { - size: None, - states: vec![], - serial: 42, - } - } - fn show_window_menu(&mut self, _evlh: &mut EventLoopHandle, - _surface: ToplevelSurface, - _seat: &wl_seat::WlSeat, _serial: u32, _x: i32, _y: i32) { - } -} - - -type MyCompositorHandler = CompositorHandler; -type MyShellHandler = ShellHandler; +use wayland_server::{EventLoopHandle, StateToken}; fn main() { // A logger facility, here we use the terminal for this example @@ -220,52 +85,21 @@ fn main() { .unwrap(); /* - * Initialize wl_shm global + * Initialize the globals */ - // Insert the ShmGlobal as a handler to your event loop - // Here, we specify tha the standard Argb8888 and Xrgb8888 is the only supported. - let shm_handler_id = event_loop.add_handler_with_init(ShmGlobal::new(vec![], log.clone())); - // Register this handler to advertise a wl_shm global of version 1 - event_loop.register_global::(shm_handler_id, 1); - // retreive the token - let shm_token = { - let state = event_loop.state(); - state.get_handler::(shm_handler_id).get_token() - }; + init_shm_global(&mut event_loop, vec![], log.clone()); - /* - * Initialize the compositor global - */ - let compositor_handler_id = event_loop.add_handler_with_init(MyCompositorHandler::new( - SurfaceHandler { - shm_token: shm_token.clone(), - }, - log.clone(), - )); - // register it to handle wl_compositor and wl_subcompositor - event_loop.register_global::(compositor_handler_id, 4); - event_loop - .register_global::(compositor_handler_id, 1); - // retrieve the tokens - let compositor_token = { - let state = event_loop.state(); - state - .get_handler::(compositor_handler_id) - .get_token() - }; + let (compositor_token, _, _) = + compositor_init(&mut event_loop, surface_implementation(), (), log.clone()); - /* - * Initialize the shell global - */ - let shell_handler_id = event_loop.add_handler_with_init(MyShellHandler::new( - ShellSurfaceHandler::new(compositor_token), + let (shell_state_token, _, _) = shell_init( + &mut event_loop, + compositor_token, + shell_implementation(), compositor_token, log.clone(), - )); - event_loop.register_global::(shell_handler_id, 1); - event_loop.register_global::(shell_handler_id, 1); - + ); /* * Initialize glium @@ -281,29 +115,27 @@ fn main() { let name = display.add_socket_auto().unwrap().into_string().unwrap(); println!("Listening on socket: {}", name); - // Set the DrmHandler - device.set_handler(DrmHandlerImpl { - drawer: drawer, - shell_handler_id, - compositor_token, - logger: log, - }); - /* * Register the DrmDevice on the EventLoop */ - let fd = device.as_raw_fd(); - let drm_device_id = event_loop.add_handler(device); - let _drm_event_source = - event_loop.add_fd_event_source::>(fd, drm_device_id, READ); + let _source = drm_device_bind( + &mut event_loop, + device, + DrmHandlerImpl { + drawer, + shell_state_token, + compositor_token, + logger: log, + }, + ).unwrap(); event_loop.run().unwrap(); } pub struct DrmHandlerImpl { drawer: GliumDrawer>, - shell_handler_id: usize, - compositor_token: CompositorToken, + shell_state_token: StateToken>, + compositor_token: CompositorToken, logger: ::slog::Logger, } @@ -314,10 +146,8 @@ impl DrmHandler for DrmHandlerImpl { // redraw the frame, in a simple but inneficient way { let screen_dimensions = self.drawer.get_framebuffer_dimensions(); - for toplevel_surface in unsafe { - evlh.get_handler_unchecked::(self.shell_handler_id) - .toplevel_surfaces() - } { + let state = evlh.state(); + for toplevel_surface in state.get(&self.shell_state_token).toplevel_surfaces() { if let Some(wl_surface) = toplevel_surface.get_surface() { // this surface is a root of a subsurface tree that needs to be drawn let initial_place = self.compositor_token diff --git a/src/backend/drm/backend.rs b/src/backend/drm/backend.rs index 7703d84..4d2ba49 100644 --- a/src/backend/drm/backend.rs +++ b/src/backend/drm/backend.rs @@ -476,7 +476,7 @@ impl GraphicsBackend for DrmBackend { -> Result<()> { let (w, h) = buffer.dimensions(); debug!(self.0.borrow().logger, "Importing cursor"); - /// import the cursor into a buffer we can render + // import the cursor into a buffer we can render self.0 .borrow_mut() .graphics @@ -548,7 +548,7 @@ impl EGLGraphicsBackend for DrmBackend { return Err(SwapBuffersError::AlreadySwapped); } - /// flip normally + // flip normally graphics.gbm.surface.rent(|egl| egl.surface.swap_buffers())?; graphics.gbm.surface.rent_all(|surface| { diff --git a/src/backend/drm/mod.rs b/src/backend/drm/mod.rs index 9281b70..1dc2510 100644 --- a/src/backend/drm/mod.rs +++ b/src/backend/drm/mod.rs @@ -189,20 +189,16 @@ use backend::graphics::egl::{EGLContext, GlAttributes, PixelFormatRequirements}; use drm::Device as BasicDevice; use drm::control::{connector, crtc, encoder, Mode, ResourceInfo}; use drm::control::Device as ControlDevice; - use gbm::Device as GbmDevice; - use nix; - use std::cell::RefCell; use std::fs::File; -use std::io::Error as IoError; +use std::io::{Error as IoError, Result as IoResult}; use std::os::unix::io::{AsRawFd, RawFd}; use std::rc::{Rc, Weak}; use std::time::Duration; - use wayland_server::EventLoopHandle; -use wayland_server::sources::{FdEventSourceHandler, FdInterest}; +use wayland_server::sources::{FdEventSource, FdEventSourceImpl, READ}; mod backend; pub mod error; @@ -259,14 +255,13 @@ rental! { use self::devices::{Context, Devices}; /// Representation of an open drm device node to create rendering backends -pub struct DrmDevice { +pub struct DrmDevice { context: Rc, backends: Vec>>, - handler: Option, logger: ::slog::Logger, } -impl DrmDevice { +impl DrmDevice { /// Create a new `DrmDevice` from a raw file descriptor /// /// Returns an error of opening the device failed or context creation was not @@ -363,7 +358,7 @@ impl DrmDevice { context: Rc::new(Context::try_new( Box::new(Devices::try_new(Box::new(drm), |drm| { debug!(log, "Creating gbm device"); - GbmDevice::new_from_drm::>(drm).chain_err(|| ErrorKind::GbmInitFailed) + GbmDevice::new_from_drm::(drm).chain_err(|| ErrorKind::GbmInitFailed) })?), |devices| { debug!(log, "Creating egl context from gbm device"); @@ -381,7 +376,6 @@ impl DrmDevice { }, )?), backends: Vec::new(), - handler: None, logger: log, }) } @@ -433,8 +427,9 @@ impl DrmDevice { // configuration is valid, the kernel will figure out the rest let own_id = self.backends.len(); - let logger = self.logger - .new(o!("id" => format!("{}", own_id), "crtc" => format!("{:?}", crtc))); + let logger = self.logger.new( + o!("id" => format!("{}", own_id), "crtc" => format!("{:?}", crtc)), + ); let backend = Rc::new(RefCell::new(DrmBackendInternal::new( self.context.clone(), @@ -449,28 +444,16 @@ impl DrmDevice { Ok(DrmBackend::new(backend)) } - - /// Set a handler for handling finished rendering - pub fn set_handler(&mut self, handler: H) -> Option { - let res = self.handler.take(); - self.handler = Some(handler); - res - } - - /// Clear the currently set handler - pub fn clear_handler(&mut self) -> Option { - self.handler.take() - } } // for users convinience and FdEventSource registering -impl AsRawFd for DrmDevice { +impl AsRawFd for DrmDevice { fn as_raw_fd(&self) -> RawFd { self.context.head().head().as_raw_fd() } } -impl BasicDevice for DrmDevice {} -impl ControlDevice for DrmDevice {} +impl BasicDevice for DrmDevice {} +impl ControlDevice for DrmDevice {} /// Handler for drm node events /// @@ -489,50 +472,59 @@ pub trait DrmHandler { fn error(&mut self, evlh: &mut EventLoopHandle, error: IoError); } -impl FdEventSourceHandler for DrmDevice { - fn ready(&mut self, evlh: &mut EventLoopHandle, fd: RawFd, _mask: FdInterest) { - use std::any::Any; +/// Bind a `DrmDevice` to an EventLoop, +/// +/// This will cause it to recieve events and feed them into an `DrmHandler` +pub fn drm_device_bind(evlh: &mut EventLoopHandle, device: DrmDevice, handler: H) + -> IoResult> +where + H: DrmHandler + 'static, +{ + evlh.add_fd_event_source( + device.as_raw_fd(), + fd_event_source_implementation(), + (device, handler), + READ, + ) +} - struct DrmDeviceRef(RawFd); - impl AsRawFd for DrmDeviceRef { - fn as_raw_fd(&self) -> RawFd { - self.0 - } - } - impl BasicDevice for DrmDeviceRef {} - impl ControlDevice for DrmDeviceRef {} +fn fd_event_source_implementation() -> FdEventSourceImpl<(DrmDevice, H)> +where + H: DrmHandler + 'static, +{ + FdEventSourceImpl { + ready: |evlh, id, _, _| { + use std::any::Any; - struct PageFlipHandler<'a, 'b, H: DrmHandler + 'static>(&'a mut DrmDevice, &'b mut EventLoopHandle); + let &mut (ref dev, ref mut handler) = id; - impl<'a, 'b, H: DrmHandler + 'static> crtc::PageFlipHandler for PageFlipHandler<'a, 'b, H> { - fn handle_event(&mut self, _device: &DrmDeviceRef, frame: u32, duration: Duration, - userdata: Box) { - let id: Id = *userdata.downcast().unwrap(); - if let Some(backend) = self.0.backends[id.raw()].upgrade() { - // we can now unlock the buffer - trace!(self.0.logger, "Handling event for backend {:?}", id.raw()); - backend.borrow().unlock_buffer(); - if let Some(handler) = self.0.handler.as_mut() { + struct PageFlipHandler<'a, 'b, H: DrmHandler + 'static>(&'a mut H, &'b mut EventLoopHandle); + + impl<'a, 'b, H: DrmHandler + 'static> crtc::PageFlipHandler for PageFlipHandler<'a, 'b, H> { + fn handle_event(&mut self, device: &DrmDevice, frame: u32, duration: Duration, + userdata: Box) { + let id: Id = *userdata.downcast().unwrap(); + if let Some(backend) = device.backends[id.raw()].upgrade() { + // we can now unlock the buffer + backend.borrow().unlock_buffer(); + trace!(device.logger, "Handling event for backend {:?}", id.raw()); // and then call the user to render the next frame - handler.ready(self.1, id, frame, duration); + self.0.ready(self.1, id, frame, duration); } } } - } - crtc::handle_event( - &DrmDeviceRef(fd), - 2, - None::<&mut ()>, - Some(&mut PageFlipHandler(self, evlh)), - None::<&mut ()>, - ).unwrap(); - } - - fn error(&mut self, evlh: &mut EventLoopHandle, _fd: RawFd, error: IoError) { - if let Some(handler) = self.handler.as_mut() { - warn!(self.logger, "DrmDevice errored: {}", error); - handler.error(evlh, error) - } + crtc::handle_event( + dev, + 2, + None::<&mut ()>, + Some(&mut PageFlipHandler(handler, evlh)), + None::<&mut ()>, + ).unwrap(); + }, + error: |evlh, id, _, error| { + warn!(id.0.logger, "DrmDevice errored: {}", error); + id.1.error(evlh, error); + }, } } From 518f7dbdfcaa192e308b705e1397dee06a9934d3 Mon Sep 17 00:00:00 2001 From: Drakulix Date: Wed, 20 Sep 2017 19:48:58 +0200 Subject: [PATCH 17/18] Store DrmBackend in EventLoop state --- Cargo.toml | 3 - examples/drm.rs | 44 +++-- examples/helpers/glium.rs | 26 ++- examples/winit.rs | 5 +- src/backend/drm/backend.rs | 311 +++++++++++++--------------------- src/backend/drm/mod.rs | 129 +++++++------- src/backend/graphics/glium.rs | 13 +- 7 files changed, 228 insertions(+), 303 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index ce826fd..41d31b0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,7 +40,4 @@ backend_libinput = ["input"] renderer_glium = ["glium"] [replace] -"wayland-server:0.9.9" = { git = "https://github.com/Drakulix/wayland-rs", branch = "raw_handler_access"} -"wayland-protocols:0.9.9" = { git = "https://github.com/Drakulix/wayland-rs", branch = "raw_handler_access"} -"wayland-client:0.9.9" = { git = "https://github.com/Drakulix/wayland-rs", branch = "raw_handler_access"} "drm:0.2.1" = { git = "https://github.com/Drakulix/drm-rs", branch = "future" } diff --git a/examples/drm.rs b/examples/drm.rs index 30541b0..3c1d4eb 100644 --- a/examples/drm.rs +++ b/examples/drm.rs @@ -19,9 +19,8 @@ use drm::control::encoder::Info as EncoderInfo; use glium::Surface; use helpers::{shell_implementation, surface_implementation, GliumDrawer, Roles, SurfaceData}; use slog::{Drain, Logger}; -use smithay::backend::drm::{drm_device_bind, DrmBackend, DrmDevice, DrmHandler, Id}; +use smithay::backend::drm::{drm_device_bind, DrmBackend, DrmDevice, DrmHandler}; use smithay::backend::graphics::egl::EGLGraphicsBackend; -use smithay::backend::graphics::glium::{GliumGraphicsBackend, IntoGlium}; use smithay::compositor::{compositor_init, CompositorToken, SubsurfaceRole, TraversalAction}; use smithay::compositor::roles::Role; use smithay::shell::{shell_init, ShellState}; @@ -48,7 +47,7 @@ fn main() { let mut options = OpenOptions::new(); options.read(true); options.write(true); - let mut device = + let mut device: DrmDevice> = DrmDevice::new_from_file(options.clone().open("/dev/dri/card0").unwrap(), log.clone()).unwrap(); // Get a set of all modesetting resource handles (excluding planes): @@ -80,8 +79,8 @@ fn main() { 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 renderer = device - .create_backend(crtc, mode, vec![connector_info.handle()]) + let renderer_token = device + .create_backend(&mut event_loop, crtc, mode, vec![connector_info.handle()]) .unwrap(); /* @@ -104,10 +103,12 @@ fn main() { /* * Initialize glium */ - let drawer = GliumDrawer::new(renderer.into_glium()); - let mut frame = drawer.draw(); - frame.clear_color(0.8, 0.8, 0.9, 1.0); - frame.finish().unwrap(); + { + let drawer = event_loop.state().get(&renderer_token); + let mut frame = drawer.draw(); + frame.clear_color(0.8, 0.8, 0.9, 1.0); + frame.finish().unwrap(); + } /* * Add a listening socket: @@ -122,7 +123,6 @@ fn main() { &mut event_loop, device, DrmHandlerImpl { - drawer, shell_state_token, compositor_token, logger: log, @@ -133,20 +133,21 @@ fn main() { } pub struct DrmHandlerImpl { - drawer: GliumDrawer>, shell_state_token: StateToken>, compositor_token: CompositorToken, logger: ::slog::Logger, } -impl DrmHandler for DrmHandlerImpl { - fn ready(&mut self, evlh: &mut EventLoopHandle, _id: Id, _frame: u32, _duration: Duration) { - let mut frame = self.drawer.draw(); +impl DrmHandler> for DrmHandlerImpl { + fn ready(&mut self, evlh: &mut EventLoopHandle, _device: &mut DrmDevice>, + backend: &StateToken>, _frame: u32, _duration: Duration) { + let state = evlh.state(); + let drawer = state.get(backend); + let mut frame = drawer.draw(); frame.clear_color(0.8, 0.8, 0.9, 1.0); // redraw the frame, in a simple but inneficient way { - let screen_dimensions = self.drawer.get_framebuffer_dimensions(); - let state = evlh.state(); + let screen_dimensions = drawer.get_framebuffer_dimensions(); for toplevel_surface in state.get(&self.shell_state_token).toplevel_surfaces() { if let Some(wl_surface) = toplevel_surface.get_surface() { // this surface is a root of a subsurface tree that needs to be drawn @@ -163,13 +164,7 @@ impl DrmHandler for DrmHandlerImpl { x += subdata.x; y += subdata.y; } - self.drawer.render( - &mut frame, - contents, - (w, h), - (x, y), - screen_dimensions, - ); + drawer.render(&mut frame, contents, (w, h), (x, y), screen_dimensions); TraversalAction::DoChildren((x, y)) } else { // we are not display, so our children are neither @@ -184,7 +179,8 @@ impl DrmHandler for DrmHandlerImpl { frame.finish().unwrap(); } - fn error(&mut self, _evlh: &mut EventLoopHandle, error: IoError) { + fn error(&mut self, _evlh: &mut EventLoopHandle, _device: &mut DrmDevice>, + error: IoError) { panic!("{:?}", error); } } diff --git a/examples/helpers/glium.rs b/examples/helpers/glium.rs index 936d183..a70f91f 100644 --- a/examples/helpers/glium.rs +++ b/examples/helpers/glium.rs @@ -1,7 +1,8 @@ use glium; -use glium::Surface; +use glium::{Frame, Surface}; use glium::index::PrimitiveType; - +use smithay::backend::graphics::egl::EGLGraphicsBackend; +use smithay::backend::graphics::glium::GliumGraphicsBackend; use std::ops::Deref; #[derive(Copy, Clone)] @@ -12,23 +13,25 @@ struct Vertex { implement_vertex!(Vertex, position, tex_coords); -pub struct GliumDrawer { - display: F, +pub struct GliumDrawer { + display: GliumGraphicsBackend, vertex_buffer: glium::VertexBuffer, index_buffer: glium::IndexBuffer, program: glium::Program, } -impl Deref for GliumDrawer { +impl Deref for GliumDrawer { type Target = F; fn deref(&self) -> &F { - &self.display + &*self.display } } -impl GliumDrawer { - pub fn new(display: F) -> GliumDrawer { +impl> + EGLGraphicsBackend + 'static> From for GliumDrawer { + fn from(backend: T) -> 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, @@ -93,7 +96,9 @@ impl GliumDrawer { program, } } +} +impl GliumDrawer { pub fn render(&self, target: &mut glium::Frame, contents: &[u8], surface_dimensions: (u32, u32), surface_location: (i32, i32), screen_size: (u32, u32)) { let image = glium::texture::RawImage2d { @@ -130,4 +135,9 @@ impl GliumDrawer { ) .unwrap(); } + + #[inline] + pub fn draw(&self) -> Frame { + self.display.draw() + } } diff --git a/examples/winit.rs b/examples/winit.rs index c1b63e9..037ece2 100644 --- a/examples/winit.rs +++ b/examples/winit.rs @@ -7,7 +7,6 @@ extern crate slog_async; extern crate slog_term; #[macro_use(define_roles)] extern crate smithay; -extern crate wayland_protocols; extern crate wayland_server; mod helpers; @@ -16,8 +15,6 @@ use glium::Surface; use helpers::{shell_implementation, surface_implementation, GliumDrawer}; use slog::{Drain, Logger}; use smithay::backend::graphics::egl::EGLGraphicsBackend; - -use smithay::backend::graphics::glium::IntoGlium; use smithay::backend::input::InputBackend; use smithay::backend::winit; use smithay::compositor::{compositor_init, SubsurfaceRole, TraversalAction}; @@ -57,7 +54,7 @@ fn main() { /* * Initialize glium */ - let drawer = GliumDrawer::new(renderer.into_glium()); + let drawer = GliumDrawer::from(renderer); /* * Add a listening socket: diff --git a/src/backend/drm/backend.rs b/src/backend/drm/backend.rs index 4d2ba49..65b8253 100644 --- a/src/backend/drm/backend.rs +++ b/src/backend/drm/backend.rs @@ -8,18 +8,9 @@ use drm::control::ResourceInfo; use gbm::{BufferObject, BufferObjectFlags, Format as GbmFormat, Surface as GbmSurface, SurfaceBufferHandle}; use image::{ImageBuffer, Rgba}; use nix::c_void; -use std::cell::{Cell, RefCell}; +use std::cell::Cell; use std::rc::Rc; -/// Backend based on a `DrmDevice` and a given crtc -pub struct DrmBackend(Rc>); - -impl DrmBackend { - pub(crate) fn new(drm: Rc>) -> DrmBackend { - DrmBackend(drm) - } -} - /* Dependency graph - drm @@ -32,7 +23,7 @@ impl DrmBackend { */ pub(crate) struct GbmTypes<'dev, 'context> { - cursor: BufferObject<'dev, ()>, + cursor: Cell>, surface: Surface<'context>, } @@ -70,60 +61,32 @@ rental! { } use self::graphics::{Graphics, Surface}; -/// Id of a `DrmBackend` related to its `DrmDevice`. -/// -/// Used to track which `DrmBackend` finished page-flipping -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct Id(usize); -impl Id { - pub(crate) fn raw(&self) -> usize { - self.0 - } -} - -pub(crate) struct DrmBackendInternal { +/// Backend based on a `DrmDevice` and a given crtc +pub struct DrmBackend { graphics: Graphics, crtc: crtc::Handle, mode: Mode, connectors: Vec, - own_id: Id, logger: ::slog::Logger, } -impl DrmBackendInternal { - pub(crate) fn new(context: Rc, crtc: crtc::Handle, mode: Mode, connectors: I, - own_id: usize, logger: L) - -> Result - where - I: Into>, - L: Into>, - { +impl DrmBackend { + pub(crate) fn new(context: Rc, crtc: crtc::Handle, mode: Mode, + connectors: Vec, logger: ::slog::Logger) + -> Result { // logger already initialized by the DrmDevice let log = ::slog_or_stdlog(logger); info!(log, "Initializing DrmBackend"); - let connectors = connectors.into(); - - // check the connectors, if they suite the mode - for connector in connectors.iter() { - if !connector::Info::load_from_device(context.head().head(), *connector) - .chain_err(|| ErrorKind::DrmDev(format!("{:?}", context.head().head())))? - .modes() - .contains(&mode) - { - bail!(ErrorKind::ModeNotSuitable(mode)) - } - } - let (w, h) = mode.size(); - Ok(DrmBackendInternal { + Ok(DrmBackend { graphics: Graphics::try_new(context, |context| { Ok(GbmTypes { cursor: { // Create an unused cursor buffer (we don't want an Option here) - context + Cell::new(context .devices .gbm .create_buffer_object( @@ -132,7 +95,7 @@ impl DrmBackendInternal { GbmFormat::ARGB8888, &[BufferObjectFlags::Cursor, BufferObjectFlags::Write], ) - .chain_err(|| ErrorKind::GbmInitFailed)? + .chain_err(|| ErrorKind::GbmInitFailed)?) }, surface: Surface::try_new( { @@ -202,7 +165,6 @@ impl DrmBackendInternal { crtc, mode, connectors, - own_id: Id(own_id), logger: log.clone(), }) } @@ -225,85 +187,67 @@ impl DrmBackendInternal { }); } - pub(crate) fn is_crtc(&self, crtc: crtc::Handle) -> bool { - crtc == self.crtc - } -} - -impl DrmBackend { /// 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.0.borrow().graphics.head().head().head(), connector) - .chain_err(|| { - ErrorKind::DrmDev(format!( - "{:?}", - self.0.borrow().graphics.head().head().head() - )) - })?; + let info = connector::Info::load_from_device(self.graphics.head().head().head(), connector) + .chain_err(|| { + ErrorKind::DrmDev(format!("{:?}", self.graphics.head().head().head())) + })?; // check if the connector can handle the current mode - let mut internal = self.0.borrow_mut(); - if info.modes().contains(&internal.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.0.borrow().graphics.head().head().head(), *encoder) - .chain_err(|| { - ErrorKind::DrmDev(format!( - "{:?}", - self.0.borrow().graphics.head().head().head() - )) - }) + encoder::Info::load_from_device(self.graphics.head().head().head(), *encoder).chain_err( + || ErrorKind::DrmDev(format!("{:?}", self.graphics.head().head().head())), + ) }) .collect::>>()?; // and if any encoder supports the selected crtc if !encoders .iter() - .any(|encoder| encoder.supports_crtc(self.0.borrow().crtc)) + .any(|encoder| encoder.supports_crtc(self.crtc)) { - bail!(ErrorKind::NoSuitableEncoder(info, self.0.borrow().crtc)); + bail!(ErrorKind::NoSuitableEncoder(info, self.crtc)); } info!( - self.0.borrow().logger, + self.logger, "Adding new connector: {:?}", info.connector_type() ); - internal.connectors.push(connector); + self.connectors.push(connector); Ok(()) } else { - bail!(ErrorKind::ModeNotSuitable(self.0.borrow().mode)) + bail!(ErrorKind::ModeNotSuitable(self.mode)) } } - /// Returns a copy of the currently set connectors - pub fn used_connectors(&self) -> Vec { - // thanks to the RefCell we can sadly not return a `&[connector::Handle]` - self.0.borrow().connectors.clone() + /// 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.0.borrow().graphics.head().head().head(), connector) - { + if let Ok(info) = connector::Info::load_from_device(self.graphics.head().head().head(), connector) { info!( - self.0.borrow().logger, + self.logger, "Removing connector: {:?}", info.connector_type() ); } else { - info!(self.0.borrow().logger, "Removing unknown connector"); + info!(self.logger, "Removing unknown connector"); } - self.0.borrow_mut().connectors.retain(|x| *x != connector); + self.connectors.retain(|x| *x != connector); } /// Changes the currently set mode @@ -315,13 +259,10 @@ impl DrmBackend { /// Other errors might occur. pub fn use_mode(&mut self, mode: Mode) -> Result<()> { // check the connectors - for connector in self.0.borrow().connectors.iter() { - if !connector::Info::load_from_device(self.0.borrow().graphics.head().head().head(), *connector) + for connector in self.connectors.iter() { + if !connector::Info::load_from_device(self.graphics.head().head().head(), *connector) .chain_err(|| { - ErrorKind::DrmDev(format!( - "{:?}", - self.0.borrow().graphics.head().head().head() - )) + ErrorKind::DrmDev(format!("{:?}", self.graphics.head().head().head())) })? .modes() .contains(&mode) @@ -332,21 +273,25 @@ impl DrmBackend { // borrow & clone stuff because rust cannot figure out the upcoming // closure otherwise. - let crtc = self.0.borrow().crtc; - let mut internal = self.0.borrow_mut(); - let connectors = internal.connectors.clone(); - let logger = internal.logger.clone(); + let crtc = self.crtc; + let connectors_ref = &self.connectors; + let logger_ref = &self.logger; let (w, h) = mode.size(); - internal.graphics.rent_all_mut(|graphics| -> Result<()> { + self.graphics.rent_all_mut(|graphics| -> Result<()> { // Recreate the surface and the related resources to match the new // resolution. - debug!(logger, "Reinitializing surface for new mode: {}:{}", w, h); + debug!( + logger_ref, + "Reinitializing surface for new mode: {}:{}", + w, + h + ); graphics.gbm.surface = Surface::try_new( { // create a new gbm surface - debug!(logger, "Creating GbmSurface"); + debug!(logger_ref, "Creating GbmSurface"); Box::new(graphics .context .devices @@ -361,7 +306,7 @@ impl DrmBackend { }, |surface| { // create an egl surface from the gbm one - debug!(logger, "Creating EGLSurface"); + debug!(logger_ref, "Creating EGLSurface"); let egl_surface = graphics.context.egl.create_surface(&surface)?; // make it active for the first `crtc::set` @@ -378,18 +323,22 @@ impl DrmBackend { let mut front_bo = surface .lock_front_buffer() .chain_err(|| ErrorKind::FailedToSwap)?; - debug!(logger, "FrontBuffer color format: {:?}", front_bo.format()); + debug!( + logger_ref, + "FrontBuffer color format: {:?}", + front_bo.format() + ); // we need a framebuffer per front_buffer let fb = framebuffer::create(graphics.context.devices.drm, &*front_bo).chain_err(|| { ErrorKind::DrmDev(format!("{:?}", graphics.context.devices.drm)) })?; - debug!(logger, "Initialize screen"); + debug!(logger_ref, "Initialize screen"); crtc::set( graphics.context.devices.drm, crtc, fb.handle(), - &connectors, + connectors_ref, (0, 0), Some(mode), ).chain_err(|| { @@ -410,25 +359,17 @@ impl DrmBackend { Ok(()) })?; - info!(logger, "Setting new mode: {:?}", mode.name()); - internal.mode = mode; + info!(self.logger, "Setting new mode: {:?}", mode.name()); + self.mode = mode; Ok(()) } - - /// Checks of the `DrmBackend` is of the given `Id` - /// - /// Only produces valid results, if the `Id` is from the `DrmDevice`, - /// that created this backend. - pub fn is(&self, id: Id) -> bool { - self.0.borrow().own_id == id - } } 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) - self.0.borrow_mut().graphics.rent_all_mut(|graphics| { + self.graphics.rent_all_mut(|graphics| { if let Some(fb) = graphics.gbm.surface.rent(|egl| { if let Some(mut next) = egl.buffers.next_buffer.take() { return next.take_userdata(); @@ -459,84 +400,72 @@ impl GraphicsBackend for DrmBackend { type Error = Error; fn set_cursor_position(&self, x: u32, y: u32) -> Result<()> { - trace!(self.0.borrow().logger, "Move the cursor to {},{}", x, y); + trace!(self.logger, "Move the cursor to {},{}", x, y); crtc::move_cursor( - self.0.borrow().graphics.head().head().head(), - self.0.borrow().crtc, + self.graphics.head().head().head(), + self.crtc, (x as i32, y as i32), ).chain_err(|| { - ErrorKind::DrmDev(format!( - "{:?}", - self.0.borrow().graphics.head().head().head() - )) + ErrorKind::DrmDev(format!("{:?}", self.graphics.head().head().head())) }) } fn set_cursor_representation(&self, buffer: ImageBuffer, Vec>, hotspot: (u32, u32)) -> Result<()> { let (w, h) = buffer.dimensions(); - debug!(self.0.borrow().logger, "Importing cursor"); - // import the cursor into a buffer we can render - self.0 - .borrow_mut() - .graphics - .rent_all_mut(|graphics| -> Result<()> { - graphics.gbm.cursor = { - let mut cursor = graphics - .context - .devices - .gbm - .create_buffer_object( - w, - h, - GbmFormat::ARGB8888, - &[BufferObjectFlags::Cursor, BufferObjectFlags::Write], - ) - .chain_err(|| ErrorKind::GbmInitFailed)?; - cursor - .write(&*buffer.into_raw()) - .chain_err(|| ErrorKind::GbmInitFailed)?; - cursor - }; - Ok(()) - })?; - trace!(self.0.borrow().logger, "Set the new imported cursor"); - // and set it - if crtc::set_cursor2( - self.0.borrow().graphics.head().head().head(), - self.0.borrow().crtc, - self.0 - .borrow() - .graphics - .rent(|gbm| Buffer::handle(&gbm.cursor)), - (w, h), - (hotspot.0 as i32, hotspot.1 as i32), - ).is_err() - { - crtc::set_cursor( - self.0.borrow().graphics.head().head().head(), - self.0.borrow().crtc, - self.0 - .borrow() - .graphics - .rent(|gbm| Buffer::handle(&gbm.cursor)), - (w, h), - ).chain_err(|| { - ErrorKind::DrmDev(format!( - "{:?}", - self.0.borrow().graphics.head().head().head() - )) - }) - } else { + debug!(self.logger, "Importing cursor"); + + self.graphics.rent_all(|graphics| -> Result<()> { + graphics.gbm.cursor.set({ + // import the cursor into a buffer we can render + let mut cursor = graphics + .context + .devices + .gbm + .create_buffer_object( + w, + h, + GbmFormat::ARGB8888, + &[BufferObjectFlags::Cursor, BufferObjectFlags::Write], + ) + .chain_err(|| ErrorKind::GbmInitFailed)?; + cursor + .write(&*buffer.into_raw()) + .chain_err(|| ErrorKind::GbmInitFailed)?; + + trace!(self.logger, "Set the new imported cursor"); + + // and set it + if crtc::set_cursor2( + self.graphics.head().head().head(), + self.crtc, + Buffer::handle(&cursor), + (w, h), + (hotspot.0 as i32, hotspot.1 as i32), + ).is_err() + { + crtc::set_cursor( + self.graphics.head().head().head(), + self.crtc, + Buffer::handle(&cursor), + (w, h), + ).chain_err(|| { + ErrorKind::DrmDev(format!("{:?}", self.graphics.head().head().head())) + })?; + } + + // and store it + cursor + }); Ok(()) - } + }) } } impl EGLGraphicsBackend for DrmBackend { fn swap_buffers(&self) -> ::std::result::Result<(), SwapBuffersError> { - self.0.borrow().graphics.rent_all(|graphics| { + self.graphics.rent_all(|graphics| { // We cannot call lock_front_buffer anymore without releasing the previous buffer, which will happen when the page flip is done if graphics.gbm.surface.rent(|egl| { let next = egl.buffers.next_buffer.take(); @@ -544,7 +473,7 @@ impl EGLGraphicsBackend for DrmBackend { egl.buffers.next_buffer.set(next); res }) { - warn!(self.0.borrow().logger, "Tried to swap a DrmBackend with a queued flip"); + warn!(self.logger, "Tried to swap a DrmBackend with a queued flip"); return Err(SwapBuffersError::AlreadySwapped); } @@ -570,48 +499,36 @@ impl EGLGraphicsBackend for DrmBackend { }; surface.egl.buffers.next_buffer.set(Some(next_bo)); - trace!(self.0.borrow().logger, "Queueing Page flip"); - - let id: Id = self.0.borrow().own_id; + trace!(self.logger, "Queueing Page flip"); // and flip - crtc::page_flip(graphics.context.devices.drm, self.0.borrow().crtc, fb.handle(), &[crtc::PageFlipFlags::PageFlipEvent], id).map_err(|_| SwapBuffersError::ContextLost) + crtc::page_flip(graphics.context.devices.drm, self.crtc, fb.handle(), &[crtc::PageFlipFlags::PageFlipEvent], self.crtc).map_err(|_| SwapBuffersError::ContextLost) }) }) } unsafe fn get_proc_address(&self, symbol: &str) -> *const c_void { - self.0 - .borrow() - .graphics + self.graphics .head() .rent(|context| context.get_proc_address(symbol)) } fn get_framebuffer_dimensions(&self) -> (u32, u32) { - let (w, h) = self.0.borrow().mode.size(); + let (w, h) = self.mode.size(); (w as u32, h as u32) } fn is_current(&self) -> bool { - self.0 - .borrow() - .graphics - .head() - .rent(|context| context.is_current()) + self.graphics.head().rent(|context| context.is_current()) } unsafe fn make_current(&self) -> ::std::result::Result<(), SwapBuffersError> { - self.0 - .borrow() - .graphics + self.graphics .rent(|gbm| gbm.surface.rent(|egl| egl.surface.make_current())) } fn get_pixel_format(&self) -> PixelFormat { - self.0 - .borrow() - .graphics + self.graphics .head() .rent(|context| context.get_pixel_format()) } diff --git a/src/backend/drm/mod.rs b/src/backend/drm/mod.rs index 1dc2510..fea1d12 100644 --- a/src/backend/drm/mod.rs +++ b/src/backend/drm/mod.rs @@ -191,20 +191,21 @@ use drm::control::{connector, crtc, encoder, Mode, ResourceInfo}; use drm::control::Device as ControlDevice; use gbm::Device as GbmDevice; use nix; -use std::cell::RefCell; +use std::collections::HashMap; use std::fs::File; use std::io::{Error as IoError, Result as IoResult}; +use std::marker::PhantomData; +use std::ops::Deref; use std::os::unix::io::{AsRawFd, RawFd}; -use std::rc::{Rc, Weak}; +use std::rc::Rc; use std::time::Duration; -use wayland_server::EventLoopHandle; +use wayland_server::{EventLoopHandle, StateToken}; use wayland_server::sources::{FdEventSource, FdEventSourceImpl, READ}; mod backend; pub mod error; -pub use self::backend::{DrmBackend, Id}; -use self::backend::DrmBackendInternal; +pub use self::backend::DrmBackend; use self::error::*; /// Internal struct as required by the drm crate @@ -255,13 +256,13 @@ rental! { use self::devices::{Context, Devices}; /// Representation of an open drm device node to create rendering backends -pub struct DrmDevice { +pub struct DrmDevice + 'static> { context: Rc, - backends: Vec>>, + backends: HashMap>, logger: ::slog::Logger, } -impl DrmDevice { +impl + Deref + 'static> DrmDevice { /// Create a new `DrmDevice` from a raw file descriptor /// /// Returns an error of opening the device failed or context creation was not @@ -358,7 +359,7 @@ impl DrmDevice { context: Rc::new(Context::try_new( Box::new(Devices::try_new(Box::new(drm), |drm| { debug!(log, "Creating gbm device"); - GbmDevice::new_from_drm::(drm).chain_err(|| ErrorKind::GbmInitFailed) + GbmDevice::new_from_drm::>(drm).chain_err(|| ErrorKind::GbmInitFailed) })?), |devices| { debug!(log, "Creating egl context from gbm device"); @@ -375,7 +376,7 @@ impl DrmDevice { ).map_err(Error::from) }, )?), - backends: Vec::new(), + backends: HashMap::new(), logger: log, }) } @@ -385,29 +386,32 @@ impl DrmDevice { /// /// Errors if initialization fails or the mode is not available on all given /// connectors. - pub fn create_backend(&mut self, crtc: crtc::Handle, mode: Mode, connectors: I) -> Result + pub fn create_backend(&mut self, evlh: &mut EventLoopHandle, crtc: crtc::Handle, mode: Mode, + connectors: I) + -> Result> where I: Into>, { - for backend in self.backends.iter() { - if let Some(backend) = backend.upgrade() { - if backend.borrow().is_crtc(crtc) { - bail!(ErrorKind::CrtcAlreadyInUse(crtc)) - } - } + if self.backends.contains_key(&crtc) { + bail!(ErrorKind::CrtcAlreadyInUse(crtc)); } // check if the given connectors and crtc match let connectors = connectors.into(); - // check if we have an encoder for every connector + // check if we have an encoder for every connector and the mode mode for connector in connectors.iter() { let con_info = connector::Info::load_from_device(self.context.head().head(), *connector) .chain_err(|| { ErrorKind::DrmDev(format!("{:?}", self.context.head().head())) })?; - // then check for every connector which encoders it does support + // 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() @@ -426,59 +430,50 @@ impl DrmDevice { // configuration is valid, the kernel will figure out the rest - let own_id = self.backends.len(); - let logger = self.logger.new( - o!("id" => format!("{}", own_id), "crtc" => format!("{:?}", crtc)), - ); + let logger = self.logger.new(o!("crtc" => format!("{:?}", crtc))); + let backend = DrmBackend::new(self.context.clone(), crtc, mode, connectors, logger)?; + let token = evlh.state().insert(backend.into()); + self.backends.insert(crtc, token.clone()); - let backend = Rc::new(RefCell::new(DrmBackendInternal::new( - self.context.clone(), - crtc, - mode, - connectors, - own_id, - logger, - )?)); - - self.backends.push(Rc::downgrade(&backend)); - - Ok(DrmBackend::new(backend)) + Ok(token) } } // for users convinience and FdEventSource registering -impl AsRawFd for DrmDevice { +impl + 'static> AsRawFd for DrmDevice { fn as_raw_fd(&self) -> RawFd { self.context.head().head().as_raw_fd() } } -impl BasicDevice for DrmDevice {} -impl ControlDevice for DrmDevice {} +impl + 'static> BasicDevice for DrmDevice {} +impl + 'static> ControlDevice for DrmDevice {} /// Handler for drm node events /// /// See module-level documentation for its use -pub trait DrmHandler { +pub trait DrmHandler + 'static> { /// A `DrmBackend` has finished swapping buffers and new frame can now /// (and should be immediately) be rendered. /// /// The `id` argument is the `Id` of the `DrmBackend` that finished rendering, /// check using `DrmBackend::is`. - fn ready(&mut self, evlh: &mut EventLoopHandle, id: Id, frame: u32, duration: Duration); + fn ready(&mut self, evlh: &mut EventLoopHandle, device: &mut DrmDevice, backend: &StateToken, + 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, evlh: &mut EventLoopHandle, error: IoError); + fn error(&mut self, evlh: &mut EventLoopHandle, device: &mut DrmDevice, error: IoError); } /// Bind a `DrmDevice` to an EventLoop, /// /// This will cause it to recieve events and feed them into an `DrmHandler` -pub fn drm_device_bind(evlh: &mut EventLoopHandle, device: DrmDevice, handler: H) - -> IoResult> +pub fn drm_device_bind(evlh: &mut EventLoopHandle, device: DrmDevice, handler: H) + -> IoResult, H)>> where - H: DrmHandler + 'static, + B: Deref + 'static, + H: DrmHandler + 'static, { evlh.add_fd_event_source( device.as_raw_fd(), @@ -488,28 +483,44 @@ where ) } -fn fd_event_source_implementation() -> FdEventSourceImpl<(DrmDevice, H)> +fn fd_event_source_implementation() -> FdEventSourceImpl<(DrmDevice, H)> where - H: DrmHandler + 'static, + B: Deref + 'static, + H: DrmHandler + 'static, { FdEventSourceImpl { ready: |evlh, id, _, _| { use std::any::Any; - let &mut (ref dev, ref mut handler) = id; + let &mut (ref mut dev, ref mut handler) = id; - struct PageFlipHandler<'a, 'b, H: DrmHandler + 'static>(&'a mut H, &'b mut EventLoopHandle); + struct PageFlipHandler< + 'a, + 'b, + B: Deref + 'static, + H: DrmHandler + 'static, + > { + handler: &'a mut H, + evlh: &'b mut EventLoopHandle, + _marker: PhantomData, + }; - impl<'a, 'b, H: DrmHandler + 'static> crtc::PageFlipHandler for PageFlipHandler<'a, 'b, H> { - fn handle_event(&mut self, device: &DrmDevice, frame: u32, duration: Duration, + impl<'a, 'b, B, H> crtc::PageFlipHandler> for PageFlipHandler<'a, 'b, B, H> + where + B: Deref + 'static, + H: DrmHandler + 'static, + { + fn handle_event(&mut self, device: &mut DrmDevice, frame: u32, duration: Duration, userdata: Box) { - let id: Id = *userdata.downcast().unwrap(); - if let Some(backend) = device.backends[id.raw()].upgrade() { + let crtc_id: crtc::Handle = *userdata.downcast().unwrap(); + let token = device.backends.get(&crtc_id).cloned(); + if let Some(token) = token { // we can now unlock the buffer - backend.borrow().unlock_buffer(); - trace!(device.logger, "Handling event for backend {:?}", id.raw()); + (**self.evlh.state().get(&token)).unlock_buffer(); + trace!(device.logger, "Handling event for backend {:?}", crtc_id); // and then call the user to render the next frame - self.0.ready(self.1, id, frame, duration); + self.handler + .ready(self.evlh, device, &token, frame, duration); } } } @@ -518,13 +529,17 @@ where dev, 2, None::<&mut ()>, - Some(&mut PageFlipHandler(handler, evlh)), + Some(&mut PageFlipHandler { + handler, + evlh, + _marker: PhantomData, + }), None::<&mut ()>, ).unwrap(); }, error: |evlh, id, _, error| { warn!(id.0.logger, "DrmDevice errored: {}", error); - id.1.error(evlh, error); + id.1.error(evlh, &mut id.0, error); }, } } diff --git a/src/backend/graphics/glium.rs b/src/backend/graphics/glium.rs index 3a820d3..c7ea06c 100644 --- a/src/backend/graphics/glium.rs +++ b/src/backend/graphics/glium.rs @@ -7,7 +7,6 @@ use glium::backend::{Backend, Context, Facade}; use glium::debug::DebugCallbackBehavior; use std::ops::Deref; use std::os::raw::c_void; - use std::rc::Rc; impl From for GliumSwapBuffersError { @@ -69,15 +68,9 @@ impl Facade for GliumGraphicsBackend { } } -/// Converter trait to expose `glium` compatibility for all `EGLGraphicsBackend`s -pub trait IntoGlium: EGLGraphicsBackend + Sized { - /// Wrap the given `EGLGraphicsBackend` to a `GliumGraphicBackend` - fn into_glium(self) -> GliumGraphicsBackend; -} - -impl IntoGlium for T { - fn into_glium(self) -> GliumGraphicsBackend { - GliumGraphicsBackend::new(self) +impl From for GliumGraphicsBackend { + fn from(backend: T) -> Self { + GliumGraphicsBackend::new(backend) } } From f09bdd0a305dae487e67bc4add47a4e13b39bd03 Mon Sep 17 00:00:00 2001 From: Drakulix Date: Wed, 20 Sep 2017 20:06:58 +0200 Subject: [PATCH 18/18] cargo fmt --- build.rs | 1 - src/backend/input.rs | 1 - src/backend/libinput.rs | 1 - src/compositor/region.rs | 5 +++-- src/keyboard/mod.rs | 4 ---- src/shell/xdg_handlers.rs | 4 ++-- 6 files changed, 5 insertions(+), 11 deletions(-) diff --git a/build.rs b/build.rs index 11b884e..4692ccd 100644 --- a/build.rs +++ b/build.rs @@ -1,7 +1,6 @@ extern crate gl_generator; use gl_generator::{Api, Fallbacks, Profile, Registry}; - use std::env; use std::fs::File; use std::path::PathBuf; diff --git a/src/backend/input.rs b/src/backend/input.rs index cb8998a..6baf26b 100644 --- a/src/backend/input.rs +++ b/src/backend/input.rs @@ -1,7 +1,6 @@ //! Common traits for input backends to receive input from. use backend::{SeatInternal, TouchSlotInternal}; - use std::error::Error; /// A seat describes a group of input devices and at least one diff --git a/src/backend/libinput.rs b/src/backend/libinput.rs index 4cbad35..c55ddf8 100644 --- a/src/backend/libinput.rs +++ b/src/backend/libinput.rs @@ -6,7 +6,6 @@ use input as libinput; use input::event; use std::collections::hash_map::{DefaultHasher, Entry, HashMap}; use std::hash::{Hash, Hasher}; - use std::io::Error as IoError; use std::rc::Rc; diff --git a/src/compositor/region.rs b/src/compositor/region.rs index b4f0b48..23952af 100644 --- a/src/compositor/region.rs +++ b/src/compositor/region.rs @@ -11,8 +11,9 @@ pub struct RegionData { impl RegionData { /// Initialize the user_data of a region, must be called right when the surface is created pub unsafe fn init(region: &wl_region::WlRegion) { - region.set_user_data(Box::into_raw(Box::new(Mutex::new(RegionData::default()))) - as *mut _) + region.set_user_data( + Box::into_raw(Box::new(Mutex::new(RegionData::default()))) as *mut _, + ) } /// Cleans the user_data of that surface, must be called when it is destroyed diff --git a/src/keyboard/mod.rs b/src/keyboard/mod.rs index 0b444de..abb44e5 100644 --- a/src/keyboard/mod.rs +++ b/src/keyboard/mod.rs @@ -21,14 +21,10 @@ use backend::input::KeyState; use std::io::{Error as IoError, Write}; use std::os::unix::io::AsRawFd; use std::sync::{Arc, Mutex}; - use tempfile::tempfile; - use wayland_server::{Liveness, Resource}; use wayland_server::protocol::{wl_keyboard, wl_surface}; - use xkbcommon::xkb; - pub use xkbcommon::xkb::{keysyms, Keysym}; /// Represents the current state of the keyboard modifiers diff --git a/src/shell/xdg_handlers.rs b/src/shell/xdg_handlers.rs index d1f27cc..c2faaec 100644 --- a/src/shell/xdg_handlers.rs +++ b/src/shell/xdg_handlers.rs @@ -682,8 +682,8 @@ where destroy: |evlh, idata, _, popup| { let ptr = popup.get_user_data(); let &(ref surface, _, _) = unsafe { - &*(ptr - as *mut ( + &*(ptr as + *mut ( wl_surface::WlSurface, zxdg_shell_v6::ZxdgShellV6, zxdg_surface_v6::ZxdgSurfaceV6,