diff --git a/Cargo.toml b/Cargo.toml index 67db4f2..41d31b0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,12 +13,16 @@ 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" +error-chain = "0.11.0" [build-dependencies] gl_generator = "0.5" @@ -29,7 +33,11 @@ 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] +"drm:0.2.1" = { git = "https://github.com/Drakulix/drm-rs", branch = "future" } 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/examples/drm.rs b/examples/drm.rs new file mode 100644 index 0000000..3c1d4eb --- /dev/null +++ b/examples/drm.rs @@ -0,0 +1,186 @@ +extern crate drm; +#[macro_use] +extern crate glium; +extern crate rand; +#[macro_use(define_roles)] +extern crate smithay; +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 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}; +use smithay::backend::graphics::egl::EGLGraphicsBackend; +use smithay::compositor::{compositor_init, CompositorToken, SubsurfaceRole, TraversalAction}; +use smithay::compositor::roles::Role; +use smithay::shell::{shell_init, ShellState}; +use smithay::shm::init_shm_global; +use std::fs::OpenOptions; +use std::io::Error as IoError; +use std::time::Duration; +use wayland_server::{EventLoopHandle, StateToken}; + +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(); + + /* + * Initialize the drm backend + */ + // "Find" a suitable drm device + let mut options = OpenOptions::new(); + options.read(true); + options.write(true); + 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): + let res_handles = device.resource_handles().unwrap(); + + // Use first connected connector + let connector_info = res_handles + .connectors() + .iter() + .map(|conn| { + ConnectorInfo::load_from_device(&device, *conn).unwrap() + }) + .find(|conn| conn.connection_state() == ConnectorState::Connected) + .unwrap(); + + // Use the first encoder + let encoder_info = EncoderInfo::load_from_device(&device, connector_info.encoders()[0]).unwrap(); + + // use the connected crtc if any + let crtc = encoder_info.current_crtc() + // or use the first one that is compatible with the encoder + .unwrap_or_else(|| + *res_handles.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.) + + // Initialize the hardware backend + let renderer_token = device + .create_backend(&mut event_loop, crtc, mode, vec![connector_info.handle()]) + .unwrap(); + + /* + * Initialize the globals + */ + + init_shm_global(&mut event_loop, vec![], log.clone()); + + let (compositor_token, _, _) = + compositor_init(&mut event_loop, surface_implementation(), (), log.clone()); + + let (shell_state_token, _, _) = shell_init( + &mut event_loop, + compositor_token, + shell_implementation(), + compositor_token, + log.clone(), + ); + + /* + * Initialize glium + */ + { + 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: + */ + let name = display.add_socket_auto().unwrap().into_string().unwrap(); + println!("Listening on socket: {}", name); + + /* + * Register the DrmDevice on the EventLoop + */ + let _source = drm_device_bind( + &mut event_loop, + device, + DrmHandlerImpl { + shell_state_token, + compositor_token, + logger: log, + }, + ).unwrap(); + + event_loop.run().unwrap(); +} + +pub struct DrmHandlerImpl { + shell_state_token: StateToken>, + compositor_token: CompositorToken, + logger: ::slog::Logger, +} + +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 = 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 + 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; + } + 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, _device: &mut DrmDevice>, + error: IoError) { + panic!("{:?}", error); + } +} diff --git a/examples/helpers/glium.rs b/examples/helpers/glium.rs index d407b46..a70f91f 100644 --- a/examples/helpers/glium.rs +++ b/examples/helpers/glium.rs @@ -1,6 +1,9 @@ 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)] struct Vertex { @@ -10,18 +13,28 @@ struct Vertex { implement_vertex!(Vertex, position, tex_coords); -pub struct GliumDrawer<'a, F: 'a> { - display: &'a F, +pub struct GliumDrawer { + display: GliumGraphicsBackend, 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> + 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, + &display, &[ Vertex { position: [0.0, 0.0], @@ -44,10 +57,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 @@ -83,16 +96,18 @@ impl<'a, F: glium::backend::Facade + 'a> GliumDrawer<'a, F> { program, } } +} - pub fn draw(&self, target: &mut glium::Frame, contents: &[u8], surface_dimensions: (u32, u32), - surface_location: (i32, i32), screen_size: (u32, u32)) { +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 { 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); @@ -120,4 +135,9 @@ impl<'a, F: glium::backend::Facade + 'a> GliumDrawer<'a, F> { ) .unwrap(); } + + #[inline] + pub fn draw(&self) -> Frame { + self.display.draw() + } } diff --git a/examples/simple.rs b/examples/winit.rs similarity index 89% rename from examples/simple.rs rename to examples/winit.rs index 38ae92a..037ece2 100644 --- a/examples/simple.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; @@ -15,7 +14,7 @@ mod helpers; use glium::Surface; use helpers::{shell_implementation, surface_implementation, GliumDrawer}; use slog::{Drain, Logger}; -use smithay::backend::graphics::glium::IntoGlium; +use smithay::backend::graphics::egl::EGLGraphicsBackend; use smithay::backend::input::InputBackend; use smithay::backend::winit; use smithay::compositor::{compositor_init, SubsurfaceRole, TraversalAction}; @@ -55,9 +54,7 @@ fn main() { /* * Initialize glium */ - let context = renderer.into_glium(); - - let drawer = GliumDrawer::new(&context); + let drawer = GliumDrawer::from(renderer); /* * Add a listening socket: @@ -68,11 +65,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 +87,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..65b8253 --- /dev/null +++ b/src/backend/drm/backend.rs @@ -0,0 +1,535 @@ +use super::devices; +use super::error::*; +use backend::graphics::GraphicsBackend; +use backend::graphics::egl::{EGLGraphicsBackend, EGLSurface, PixelFormat, SwapBuffersError}; +use drm::buffer::Buffer; +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; +use std::rc::Rc; + +/* + Dependency graph + - drm + - gbm + - context + - gbm_surface + - egl_surface + - gbm_buffers + - cursor +*/ + +pub(crate) struct GbmTypes<'dev, 'context> { + cursor: Cell>, + 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}; + + +/// Backend based on a `DrmDevice` and a given crtc +pub struct DrmBackend { + graphics: Graphics, + crtc: crtc::Handle, + mode: Mode, + connectors: Vec, + logger: ::slog::Logger, +} + +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 (w, h) = mode.size(); + + Ok(DrmBackend { + graphics: Graphics::try_new(context, |context| { + Ok(GbmTypes { + cursor: { + // Create an unused cursor buffer (we don't want an Option here) + Cell::new(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], + ) + .chain_err(|| ErrorKind::GbmInitFailed)?) + }, + |surface| { + // create an egl surface from the gbm one + debug!(log, "Creating EGLSurface"); + let egl_surface = 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() + .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() + .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) + .chain_err(|| ErrorKind::DrmDev(format!("{:?}", context.devices.drm)))?; + + debug!(log, "Initialize screen"); + crtc::set( + context.devices.drm, + crtc, + fb.handle(), + &connectors, + (0, 0), + Some(mode), + ).chain_err( + || ErrorKind::DrmDev(format!("{:?}", 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), + }, + }) + }, + ).map_err(Error::from)?, + }) + })?, + crtc, + mode, + connectors, + logger: log.clone(), + }) + } + + 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); + + 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 { + unreachable!(); + } + }) + }); + } + + /// 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.graphics.head().head().head(), connector) + .chain_err(|| { + ErrorKind::DrmDev(format!("{:?}", self.graphics.head().head().head())) + })?; + + // check if the connector can handle the current mode + if info.modes().contains(&self.mode) { + // check if there is a valid encoder + let encoders = info.encoders() + .iter() + .map(|encoder| { + encoder::Info::load_from_device(self.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.crtc)) + { + bail!(ErrorKind::NoSuitableEncoder(info, self.crtc)); + } + + info!( + self.logger, + "Adding new connector: {:?}", + info.connector_type() + ); + self.connectors.push(connector); + Ok(()) + } else { + bail!(ErrorKind::ModeNotSuitable(self.mode)) + } + } + + /// Returns the currently set connectors + pub fn used_connectors(&self) -> &[connector::Handle] { + &*self.connectors + } + + /// Removes a currently set connector + pub fn remove_connector(&mut self, connector: connector::Handle) { + if let Ok(info) = connector::Info::load_from_device(self.graphics.head().head().head(), connector) { + info!( + self.logger, + "Removing connector: {:?}", + info.connector_type() + ); + } else { + info!(self.logger, "Removing unknown connector"); + } + + self.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<()> { + // check the connectors + for connector in self.connectors.iter() { + if !connector::Info::load_from_device(self.graphics.head().head().head(), *connector) + .chain_err(|| { + ErrorKind::DrmDev(format!("{:?}", self.graphics.head().head().head())) + })? + .modes() + .contains(&mode) + { + bail!(ErrorKind::ModeNotSuitable(mode)); + } + } + + // borrow & clone stuff because rust cannot figure out the upcoming + // closure otherwise. + let crtc = self.crtc; + let connectors_ref = &self.connectors; + let logger_ref = &self.logger; + + let (w, h) = mode.size(); + + self.graphics.rent_all_mut(|graphics| -> Result<()> { + // Recreate the surface and the related resources to match the new + // resolution. + debug!( + logger_ref, + "Reinitializing surface for new mode: {}:{}", + w, + h + ); + graphics.gbm.surface = Surface::try_new( + { + // create a new gbm surface + debug!(logger_ref, "Creating GbmSurface"); + Box::new(graphics + .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 + debug!(logger_ref, "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() + .chain_err(|| ErrorKind::FailedToSwap)? + }; + egl_surface + .swap_buffers() + .chain_err(|| ErrorKind::FailedToSwap)?; + + let mut front_bo = surface + .lock_front_buffer() + .chain_err(|| ErrorKind::FailedToSwap)?; + 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_ref, "Initialize screen"); + crtc::set( + graphics.context.devices.drm, + crtc, + fb.handle(), + connectors_ref, + (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(()) + })?; + + info!(self.logger, "Setting new mode: {:?}", mode.name()); + self.mode = mode; + Ok(()) + } +} + +impl Drop for DrmBackend { + fn drop(&mut self) { + // Drop framebuffers attached to the userdata of the gbm surface buffers. + // (They don't implement drop, as they need the device) + 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(); + } 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; + + fn set_cursor_position(&self, x: u32, y: u32) -> Result<()> { + trace!(self.logger, "Move the cursor to {},{}", x, y); + crtc::move_cursor( + self.graphics.head().head().head(), + self.crtc, + (x as i32, y as i32), + ).chain_err(|| { + 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.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.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 + }) { + warn!(self.logger, "Tried to swap a DrmBackend with a queued flip"); + return Err(SwapBuffersError::AlreadySwapped); + } + + // flip normally + graphics.gbm.surface.rent(|egl| egl.surface.swap_buffers())?; + + graphics.gbm.surface.rent_all(|surface| { + // 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 + } 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.logger, "Queueing Page flip"); + + // and flip + 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.graphics + .head() + .rent(|context| context.get_proc_address(symbol)) + } + + fn get_framebuffer_dimensions(&self) -> (u32, u32) { + let (w, h) = self.mode.size(); + (w as u32, h as u32) + } + + fn is_current(&self) -> bool { + self.graphics.head().rent(|context| context.is_current()) + } + + unsafe fn make_current(&self) -> ::std::result::Result<(), SwapBuffersError> { + self.graphics + .rent(|gbm| gbm.surface.rent(|egl| egl.surface.make_current())) + } + + fn get_pixel_format(&self) -> PixelFormat { + self.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..00cd73c --- /dev/null +++ b/src/backend/drm/error.rs @@ -0,0 +1,57 @@ +//! +//! Errors thrown by the `DrmDevice` and `DrmBackend` +//! + +use backend::graphics::egl; +use drm::control::{connector, crtc, Mode}; +use rental::TryNewError; + +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), + } + + #[doc = "Creation of gbm resource failed"] + GbmInitFailed { + description("Creation of gbm resource failed"), + display("Creation of gbm resource failed"), + } + + #[doc = "Swapping front buffers failed"] + FailedToSwap { + description("Swapping front buffers failed"), + display("Swapping front buffers failed"), + } + + #[doc = "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), + } + + #[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), + } + } + + links { + EGL(egl::Error, egl::ErrorKind) #[doc = "EGL error"]; + } +} + +impl From> for Error { + fn from(err: TryNewError) -> Error { + err.0 + } +} diff --git a/src/backend/drm/mod.rs b/src/backend/drm/mod.rs new file mode 100644 index 0000000..fea1d12 --- /dev/null +++ b/src/backend/drm/mod.rs @@ -0,0 +1,545 @@ +//! 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`. +//! +//! 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 +//! +//! ### 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,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 +//! 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 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]; +//! +//! // Create the backend +//! let backend = device.create_backend( +//! crtc, +//! 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); +//! # } +//! ``` +//! +//! ### 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; +use drm::control::{connector, crtc, encoder, Mode, ResourceInfo}; +use drm::control::Device as ControlDevice; +use gbm::Device as GbmDevice; +use nix; +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; +use std::time::Duration; +use wayland_server::{EventLoopHandle, StateToken}; +use wayland_server::sources::{FdEventSource, FdEventSourceImpl, READ}; + +mod backend; +pub mod error; + +pub use self::backend::DrmBackend; +use self::error::*; + +/// Internal struct as required by the drm crate +#[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}; + +/// Representation of an open drm device node to create rendering backends +pub struct DrmDevice + 'static> { + context: Rc, + backends: HashMap>, + logger: ::slog::Logger, +} + +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 + /// 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>, + { + DrmDevice::new( + DrmDev::new_from_fd(fd), + GlAttributes { + version: None, + profile: None, + debug: cfg!(debug_assertions), + vsync: true, + }, + logger, + ) + } + + /// 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 + L: Into>, + { + 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>, + { + DrmDevice::new( + DrmDev::new_from_file(file), + GlAttributes { + version: None, + profile: None, + debug: cfg!(debug_assertions), + vsync: true, + }, + logger, + ) + } + + /// 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 + 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")); + + /* 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, + ); + } + + 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).chain_err(|| ErrorKind::GbmInitFailed) + })?), + |devices| { + debug!(log, "Creating egl context from gbm device"); + 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(Error::from) + }, + )?), + backends: HashMap::new(), + logger: log, + }) + } + + /// 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, evlh: &mut EventLoopHandle, crtc: crtc::Handle, mode: Mode, + connectors: I) + -> Result> + where + I: Into>, + { + 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 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())) + })?; + + // check the mode + if !con_info.modes().contains(&mode) { + bail!(ErrorKind::ModeNotSuitable(mode)); + } + + // check for every connector which encoders it does support + let encoders = con_info + .encoders() + .iter() + .map(|encoder| { + encoder::Info::load_from_device(self.context.head().head(), *encoder).chain_err(|| { + ErrorKind::DrmDev(format!("{:?}", self.context.head().head())) + }) + }) + .collect::>>()?; + + // and if any encoder supports the selected crtc + if !encoders.iter().any(|encoder| encoder.supports_crtc(crtc)) { + bail!(ErrorKind::NoSuitableEncoder(con_info, crtc)) + } + } + + // configuration is valid, the kernel will figure out the rest + + let logger = self.logger.new(o!("crtc" => format!("{:?}", crtc))); + let backend = DrmBackend::new(self.context.clone(), crtc, mode, connectors, logger)?; + let token = evlh.state().insert(backend.into()); + self.backends.insert(crtc, token.clone()); + + Ok(token) + } +} + +// for users convinience and FdEventSource registering +impl + 'static> AsRawFd for DrmDevice { + fn as_raw_fd(&self) -> RawFd { + self.context.head().head().as_raw_fd() + } +} +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 + '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, 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, 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, H)>> +where + B: Deref + 'static, + H: DrmHandler + 'static, +{ + evlh.add_fd_event_source( + device.as_raw_fd(), + fd_event_source_implementation(), + (device, handler), + READ, + ) +} + +fn fd_event_source_implementation() -> FdEventSourceImpl<(DrmDevice, H)> +where + B: Deref + 'static, + H: DrmHandler + 'static, +{ + FdEventSourceImpl { + ready: |evlh, id, _, _| { + use std::any::Any; + + let &mut (ref mut dev, ref mut handler) = id; + + struct PageFlipHandler< + 'a, + 'b, + B: Deref + 'static, + H: DrmHandler + 'static, + > { + handler: &'a mut H, + evlh: &'b mut EventLoopHandle, + _marker: PhantomData, + }; + + 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 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 + (**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.handler + .ready(self.evlh, device, &token, frame, duration); + } + } + } + + crtc::handle_event( + dev, + 2, + None::<&mut ()>, + 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, &mut id.0, error); + }, + } +} diff --git a/src/backend/graphics/egl.rs b/src/backend/graphics/egl.rs index b6c5e51..7c32f43 100644 --- a/src/backend/graphics/egl.rs +++ b/src/backend/graphics/egl.rs @@ -5,20 +5,26 @@ /// /// 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 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; +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 { @@ -46,7 +52,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 +64,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. @@ -67,76 +73,156 @@ pub enum NativeSurface { Gbm(ffi::NativeWindowType), } -#[derive(Clone, Copy, PartialEq)] -enum NativeType { +/// Enumerates all supported backends +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum NativeType { + /// X11 window & surface X11, + /// Wayland surface 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 From> for Error { + fn from(err: TryNewError) -> Error { + err.0 } } -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", +/// 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)>; +} + +#[cfg(feature = "backend_winit")] +unsafe impl NativeSurface for WinitWindow { + type Keep = Option; + + 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 { + 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(); + 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 { + bail!(ErrorKind::NonMatchingSurfaceType( + NativeType::X11, + NativeType::Wayland + )); + }, + x => bail!(ErrorKind::NonMatchingSurfaceType(NativeType::Unknown, x)), } } +} - fn cause(&self) -> Option<&error::Error> { - match *self { - CreationError::IoError(ref err) => Some(err), - _ => None, +#[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)> { + match backend { + NativeType::Gbm => Ok((NativeSurfacePtr::Gbm(self.as_raw() as *const _), ())), + x => bail!(ErrorKind::NonMatchingSurfaceType(NativeType::Gbm, x)), } } } +unsafe impl NativeSurface for () { + type Keep = (); + fn surface(&self, _backend: NativeType) -> Result<(NativeSurfacePtr, ())> { + bail!(ErrorKind::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 +232,69 @@ 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, ()> { + /// 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) + -> Result> 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"); + bail!(ErrorKind::NotSupported) + }, + attributes, + reqs, + log, + ) + } + } + + /// 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) + -> Result>> + 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> + 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,22 +303,22 @@ 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); } } } - 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!( @@ -209,11 +326,12 @@ impl EGLContext { "OpenGLES {:?} is unknown and not supported by the EGL renderer backend", version ); - return Err(CreationError::OpenGlVersionNotSupported); + bail!(ErrorKind::OpenGlVersionNotSupported(version)); } }; - let lib = Library::new("libEGL.so.1")?; + trace!(log, "Loading libEGL"); + 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()); @@ -243,35 +361,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 +410,7 @@ impl EGLContext { ) } - NativeDisplay::Wayland(display) + NativeDisplayPtr::Wayland(display) if has_dp_extension("EGL_EXT_platform_wayland") && egl.GetPlatformDisplayEXT.is_loaded() => { trace!( @@ -299,18 +424,25 @@ 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"); + bail!(ErrorKind::DisplayNotSupported); + } + let egl_version = { let mut major: ffi::egl::types::EGLint = mem::uninitialized(); 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"); @@ -336,7 +468,7 @@ impl EGLContext { log, "OpenGLES not supported by the underlying EGL implementation" ); - return Err(CreationError::OpenGlVersionNotSupported); + bail!(ErrorKind::OpenGlesNotSupported); } let descriptor = { @@ -362,7 +494,7 @@ impl EGLContext { 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); @@ -377,7 +509,7 @@ impl EGLContext { 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); @@ -446,7 +578,7 @@ impl EGLContext { 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); @@ -464,13 +596,11 @@ impl EGLContext { &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 @@ -481,7 +611,7 @@ impl EGLContext { 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 } @@ -539,14 +669,8 @@ impl EGLContext { 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"); @@ -583,51 +707,46 @@ 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, }) } /// 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 unsafe fn create_surface<'a>(&'a self, native: NativeSurface) - -> Result, CreationError> { + pub fn create_surface<'b>(&'a self, native: &'b T) -> Result> { 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() { + bail!(ErrorKind::SurfaceCreationFailed); + } + + debug!(self.logger, "EGL surface successfully created"); Ok(EGLSurface { context: &self, - surface: surface, + surface: egl_surface, + keep, + _lifetime_surface: PhantomData, }) } @@ -651,9 +770,45 @@ 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> { + pub fn swap_buffers(&self) -> ::std::result::Result<(), SwapBuffersError> { let ret = unsafe { self.context .egl @@ -676,7 +831,7 @@ impl<'a> EGLSurface<'a> { /// /// 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 _, @@ -695,24 +850,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 +885,28 @@ pub enum SwapBuffersError { AlreadySwapped, } +impl fmt::Display for SwapBuffersError { + fn fmt(&self, formatter: &mut fmt::Formatter) -> ::std::result::Result<(), fmt::Error> { + use std::error::Error; + write!(formatter, "{}", self.description()) + } +} + +impl 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 { @@ -828,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. /// @@ -851,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/graphics/glium.rs b/src/backend/graphics/glium.rs index dacfb51..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 { @@ -34,7 +33,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 +55,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 } } @@ -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) } } diff --git a/src/backend/graphics/mod.rs b/src/backend/graphics/mod.rs index cb04c21..0a082d7 100644 --- a/src/backend/graphics/mod.rs +++ b/src/backend/graphics/mod.rs @@ -8,6 +8,9 @@ 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. /// Useful as well for e.g. pointer wrapping. /// @@ -19,14 +22,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/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/backend/mod.rs b/src/backend/mod.rs index 1102635..09bb18f 100644 --- a/src/backend/mod.rs +++ b/src/backend/mod.rs @@ -7,6 +7,7 @@ //! Supported graphics backends: //! //! - winit +//! - drm //! //! Supported input backends: //! @@ -18,6 +19,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..04d262f 100644 --- a/src/backend/winit.rs +++ b/src/backend/winit.rs @@ -2,43 +2,65 @@ 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::{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 wayland_client::egl as wegl; -use winit::{CreationError as WinitCreationError, ElementState, Event, EventsLoop, KeyboardInput, - MouseButton as WinitMouseButton, MouseCursor, MouseScrollDelta, Touch, TouchPhase, Window, - WindowBuilder, WindowEvent}; -use winit::os::unix::{WindowExt, get_x11_xconnection}; +use winit::{ElementState, Event, EventsLoop, KeyboardInput, MouseButton as WinitMouseButton, MouseCursor, + MouseScrollDelta, Touch, TouchPhase, WindowBuilder, WindowEvent}; 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}; + +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 { window: Rc, - context: egl::RentEGL, logger: ::slog::Logger, } @@ -48,7 +70,6 @@ pub struct WinitGraphicsBackend { pub struct WinitInputBackend { events_loop: EventsLoop, window: Rc, - surface: Option, time_counter: u32, key_counter: u32, seat: Seat, @@ -60,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>, { @@ -77,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>, { @@ -97,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>, { @@ -107,69 +127,40 @@ 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) + .chain_err(|| ErrorKind::InitFailed)?; 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(), - ) { - Ok(context) => context, - Err(err) => { - error!(log, "EGLContext creation failed:\n {}", err); - return Err(err); - } - } - }; + let window = Rc::new(Window::try_new(Box::new(winit_window), |window| { + 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) => bail!(err), + }), + |context| context.create_surface(window), + ).map_err(egl::Error::from) + .map_err(Error::from) + })?); 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 +180,53 @@ 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) -> ::std::result::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)) + -> ::std::result::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> { + fn swap_buffers(&self) -> ::std::result::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> { + unsafe fn make_current(&self) -> ::std::result::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()) } } @@ -242,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", @@ -252,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()) } } @@ -310,16 +308,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 +422,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 +434,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 +473,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 } } @@ -564,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; { @@ -578,7 +602,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 +610,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 { @@ -818,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/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/lib.rs b/src/lib.rs index 98ef926..2598c77 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,7 +6,10 @@ #![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; #[macro_use] extern crate rental; @@ -15,6 +18,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")] @@ -31,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; 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,