From 0698775153c13c7d37061b35f29d1e57d64591af Mon Sep 17 00:00:00 2001 From: Drakulix Date: Wed, 13 Sep 2017 22:51:35 +0200 Subject: [PATCH] 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")]