From 5d7e96103daa246d3062c95b9b5f7f1e52bdfb8b Mon Sep 17 00:00:00 2001 From: Drakulix Date: Thu, 21 Dec 2017 16:01:16 +0100 Subject: [PATCH] Rework EGL module --- Cargo.toml | 23 +- examples/helpers/glium.rs | 59 +- examples/helpers/implementations.rs | 55 +- examples/winit.rs | 43 +- src/backend/drm/backend.rs | 619 ++++++-------- src/backend/drm/error.rs | 9 +- src/backend/drm/mod.rs | 274 ++---- src/backend/graphics/egl.rs | 1222 --------------------------- src/backend/graphics/egl/context.rs | 622 ++++++++++++++ src/backend/graphics/egl/error.rs | 70 ++ src/backend/graphics/egl/ffi.rs | 144 ++++ src/backend/graphics/egl/mod.rs | 184 ++++ src/backend/graphics/egl/native.rs | 254 ++++++ src/backend/graphics/egl/surface.rs | 125 +++ src/backend/udev.rs | 56 +- src/backend/winit.rs | 224 ++--- src/lib.rs | 4 +- src/wayland/drm/mod.rs | 87 +- src/wayland/mod.rs | 1 + 19 files changed, 2051 insertions(+), 2024 deletions(-) delete mode 100644 src/backend/graphics/egl.rs create mode 100644 src/backend/graphics/egl/context.rs create mode 100644 src/backend/graphics/egl/error.rs create mode 100644 src/backend/graphics/egl/ffi.rs create mode 100644 src/backend/graphics/egl/mod.rs create mode 100644 src/backend/graphics/egl/native.rs create mode 100644 src/backend/graphics/egl/surface.rs diff --git a/Cargo.toml b/Cargo.toml index 0c850db..5a8d11e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,32 +7,31 @@ description = "Smithay is a library for writing wayland compositors." repository = "https://github.com/Smithay/smithay" [dependencies] -wayland-server = "0.12.0" +wayland-server = "0.12.4" nix = "0.9.0" xkbcommon = "0.2.1" tempfile = "2.1.5" -slog = { version = "2.0.0" } -slog-stdlog = "2.0.0-0.2" +slog = { version = "2.1.1" } +slog-stdlog = "3.0.2" libloading = "0.4.0" -wayland-client = { version = "0.9.9", optional = true } -winit = { version = "0.8.2", optional = true } +wayland-client = { version = "0.12.4", optional = true } +winit = { version = "0.9.0", optional = true } drm = { version = "^0.3.1", optional = true } -gbm = { version = "^0.3.0", optional = true, default-features = false, features = ["drm-support"] } -glium = { version = "0.17.1", optional = true, default-features = false } +gbm = { version = "^0.4.0", git = "https://github.com/Smithay/gbm.rs.git", branch = "feature/ref_counted_api", optional = true, default-features = false, features = ["drm-support"] } +glium = { version = "0.19.0", optional = true, default-features = false } input = { version = "0.4.0", optional = true } udev = { version = "0.2.0", optional = true } -rental = "0.4.11" -wayland-protocols = { version = "0.12.0", features = ["unstable_protocols", "server"] } +wayland-protocols = { version = "0.12.4", features = ["unstable_protocols", "server"] } image = "0.17.0" error-chain = "0.11.0" lazy_static = "1.0.0" [build-dependencies] -gl_generator = "0.5" +gl_generator = "0.7" [dev-dependencies] -slog-term = "2.0" -slog-async = "2.0" +slog-term = "2.3" +slog-async = "2.2" rand = "0.3" ctrlc = { version = "3.0", features = ["termination"] } diff --git a/examples/helpers/glium.rs b/examples/helpers/glium.rs index ec35656..d8a53ba 100644 --- a/examples/helpers/glium.rs +++ b/examples/helpers/glium.rs @@ -1,7 +1,9 @@ use glium; -use glium::{Frame, Surface}; +use glium::{Frame, Surface, GlObject}; +use glium::backend::Facade; use glium::index::PrimitiveType; -use smithay::backend::graphics::egl::EGLGraphicsBackend; +use glium::texture::{MipmapsOption, UncompressedFloatFormat, Texture2d}; +use smithay::backend::graphics::egl::{EGLGraphicsBackend, EGLImage}; use smithay::backend::graphics::glium::GliumGraphicsBackend; use std::borrow::Borrow; use std::ops::Deref; @@ -106,58 +108,37 @@ impl> + EGLGraphicsBackend + 'static> From fo } impl GliumDrawer { - pub fn render_shm(&self, target: &mut glium::Frame, contents: &[u8], surface_dimensions: (u32, u32), - surface_location: (i32, i32), screen_size: (u32, u32)) { + pub fn texture_from_mem(&self, contents: &[u8], surface_dimensions: (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 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); - - let x = 2.0 * (surface_location.0 as f32) / (screen_size.0 as f32) - 1.0; - let y = 1.0 - 2.0 * (surface_location.1 as f32) / (screen_size.1 as f32); - - let uniforms = uniform! { - matrix: [ - [xscale, 0.0 , 0.0, 0.0], - [ 0.0 , yscale , 0.0, 0.0], - [ 0.0 , 0.0 , 1.0, 0.0], - [ x , y , 0.0, 1.0] - ], - tex: &opengl_texture - }; - - target - .draw( - &self.vertex_buffer, - &self.index_buffer, - &self.program, - &uniforms, - &Default::default(), - ) - .unwrap(); + let opengl_texture = Texture2d::new(&self.display, image).unwrap(); } - pub fn render_egl(&self, target: &mut glium::Frame, images: Vec, - format: UncompressedFloatFormat, y_inverted: bool, surface_dimensions: (u32, u32), - surface_location: (i32, i32), screen_size: (u32, u32)) + pub fn texture_from_egl(&self, image: EGLImage, format: UncompressedFloatFormat, + surface_dimensions: (u32, u32)) + -> Texture2d { - let opengl_texture = glium::texture::Texture2d::empty_with_format( + let opengl_texture = Texture2d::empty_with_format( &self.display, - MipmapsOption::NoMipmap, format, + MipmapsOption::NoMipmap, surface_dimensions.0, surface_dimensions.1, ).unwrap(); - self.display.exec_in_context(|| { - self.display.borrow().egl_image_to_texture(images[0], opengl_texture.get_id()); + self.display.get_context().exec_in_context(|| { + self.display.borrow().egl_image_to_texture(image, opengl_texture.get_id()); }); + opengl_texture + } + pub fn render_texture(&self, target: &mut glium::Frame, texture: Texture2d, + y_inverted: bool, surface_dimensions: (u32, u32), + surface_location: (i32, i32), screen_size: (u32, u32)) + { let xscale = 2.0 * (surface_dimensions.0 as f32) / (screen_size.0 as f32); let mut yscale = -2.0 * (surface_dimensions.1 as f32) / (screen_size.1 as f32); if y_inverted { @@ -174,7 +155,7 @@ impl GliumDrawer { [ 0.0 , 0.0 , 1.0, 0.0], [ x , y , 0.0, 1.0] ], - tex: &opengl_texture + tex: &texture, }; target diff --git a/examples/helpers/implementations.rs b/examples/helpers/implementations.rs index 8cc3df2..746d294 100644 --- a/examples/helpers/implementations.rs +++ b/examples/helpers/implementations.rs @@ -1,11 +1,13 @@ -use super::WindowMap; +use super::{GliumDrawer, WindowMap}; +use glium::texture::{Texture2d, UncompressedFloatFormat}; use rand; +use smithay::backend::graphics::egl::{EGLGraphicsBackend, EGLImage}; use smithay::wayland::compositor::{compositor_init, CompositorToken, SurfaceAttributes, SurfaceUserImplementation}; use smithay::wayland::shell::{shell_init, PopupConfigure, ShellState, ShellSurfaceRole, ShellSurfaceUserImplementation, ToplevelConfigure}; use smithay::wayland::shm::with_buffer_contents as shm_buffer_contents; -use smithay::wayland::drm::{with_buffer_contents as drm_buffer_contents, Attributes, EGLImage}; +use smithay::wayland::drm::{with_buffer_contents as drm_buffer_contents, Attributes, Format}; use std::cell::RefCell; use std::rc::Rc; use wayland_server::{EventLoop, StateToken}; @@ -14,7 +16,8 @@ define_roles!(Roles => [ ShellSurface, ShellSurfaceRole ] ); #[derive(Default)] pub struct SurfaceData { - pub buffer: Option<(Vec, (u32, u32))>, + pub texture: Option, + pub buffer: Option, } pub enum Buffer { @@ -22,17 +25,34 @@ pub enum Buffer { Shm { data: Vec, size: (u32, u32) }, } -pub fn surface_implementation() -> SurfaceUserImplementation { +unsafe impl Send for Buffer {} + +pub fn surface_implementation() -> SurfaceUserImplementation> { SurfaceUserImplementation { - commit: |_, _, surface, token| { + commit: |_, drawer, surface, token| { // 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 - if let Ok(_) = drm_buffer_contents(&buffer, |attributes, images| { + match attributes.buffer() { + Some(ref buffer) => { + // we ignore hotspot coordinates in this simple example (`attributes.buffer_coordinates()`) + if drm_buffer_contents(&buffer, drawer.borrow(), |attributes, images| { + let format = match attributes.format { + Format::RGB => UncompressedFloatFormat::U8U8U8, + Format::RGBA => UncompressedFloatFormat::U8U8U8U8, + _ => { + // we don't handle the more complex formats here. + attributes.user_data.buffer = None; + attributes.user_data.texture = None; + return; + }, + }; + attributes.user_data.texture = Some(drawer.texture_from_egl( + images[0] /*Both simple formats only have one plane*/, + format, + (attributes.width, attributes.height) + )); attributes.user_data.buffer = Some(Buffer::Egl { images, attributes }); - }) {} else { + }).is_err() { shm_buffer_contents(&buffer, |slice, data| { let offset = data.offset as usize; let stride = data.stride as usize; @@ -43,17 +63,16 @@ pub fn surface_implementation() -> SurfaceUserImplementation { + None => { // erase the contents attributes.user_data.buffer = None; } - None => {} } }); }, @@ -126,14 +145,14 @@ pub type MyWindowMap = WindowMap< fn(&SurfaceAttributes) -> Option<(i32, i32)>, >; -pub fn init_shell( - evl: &mut EventLoop, log: ::slog::Logger) +pub fn init_shell( + evl: &mut EventLoop, log: ::slog::Logger, data: ID) -> ( - CompositorToken, - StateToken>, + CompositorToken, + StateToken>, Rc>, ) { - let (compositor_token, _, _) = compositor_init(evl, surface_implementation(), (), log.clone()); + let (compositor_token, _, _) = compositor_init(evl, surface_implementation(), data, log.clone()); let window_map = Rc::new(RefCell::new(WindowMap::<_, _, _, (), _>::new( compositor_token, diff --git a/examples/winit.rs b/examples/winit.rs index f19e867..39ff869 100644 --- a/examples/winit.rs +++ b/examples/winit.rs @@ -12,7 +12,6 @@ extern crate wayland_server; mod helpers; use glium::Surface; -use glium::texture::UncompressedFloatFormat; use helpers::{init_shell, GliumDrawer, MyWindowMap, Buffer}; use slog::{Drain, Logger}; use smithay::backend::graphics::egl::EGLGraphicsBackend; @@ -137,13 +136,19 @@ fn main() { let (mut display, mut event_loop) = wayland_server::create_display(); + if let Ok(_) = renderer.bind_wl_display(&display) { + info!(log, "EGL hardware-acceleration enabled"); + } + + let drawer = Rc::new(GliumDrawer::from(renderer)); + /* * Initialize the globals */ init_shm_global(&mut event_loop, vec![], log.clone()); - let (compositor_token, _shell_state_token, window_map) = init_shell(&mut event_loop, log.clone()); + let (compositor_token, _shell_state_token, window_map) = init_shell(&mut event_loop, log.clone(), drawer.clone()); let (seat_token, _) = Seat::new(&mut event_loop, "winit".into(), log.clone()); @@ -192,7 +197,6 @@ fn main() { /* * Initialize glium */ - let drawer = GliumDrawer::from(renderer); input.set_handler(WinitInputHandler { log: log.clone(), @@ -227,36 +231,23 @@ fn main() { wl_surface, initial_place, |_surface, attributes, role, &(mut x, mut y)| { - if let Some(Buffer::Egl { images, attributes }) = attributes.user_data.buffer { // there is actually something to draw ! + if let Some(texture) = attributes.user_data.texture { if let Ok(subdata) = Role::::data(role) { x += subdata.x; y += subdata.y; } - drawer.render_egl( + drawer.render_texture( &mut frame, - images, - format: match attributes.format { - Format::RGB => UncompressedFloatFormat::U8U8U8, - Format::RGBA => UncompressedFloatFormat::U8U8U8U8, - _ => unimplemented!(), + texture, + match *attributes.user_data.buffer.as_ref().unwrap() { + Buffer::Egl { ref attributes, .. } => attributes.y_inverted, + Buffer::Shm { .. } => false, + }, + match *attributes.user_data.buffer.as_ref().unwrap() { + Buffer::Egl { ref attributes, .. } => (attributes.width, attributes.height), + Buffer::Shm { ref size, .. } => *size, }, - attributes.y_inverted, - (attributes.width, attributes.height), - (x, y), - screen_dimensions, - ); - TraversalAction::DoChildren((x, y)) - } else if let Some(Buffer::Shm { data, size: (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_shm( - &mut frame, - data, - (w, h), (x, y), screen_dimensions, ); diff --git a/src/backend/drm/backend.rs b/src/backend/drm/backend.rs index 768d888..49175d5 100644 --- a/src/backend/drm/backend.rs +++ b/src/backend/drm/backend.rs @@ -1,185 +1,114 @@ -use super::devices; use super::error::*; +use super::DevPath; use backend::graphics::GraphicsBackend; -use backend::graphics::egl::{EGLGraphicsBackend, EGLSurface, PixelFormat, SwapBuffersError}; +use backend::graphics::egl::{EGLGraphicsBackend, EGLContext, EGLSurface, EGLImage, PixelFormat, SwapBuffersError, EglExtensionNotSupportedError}; +use backend::graphics::egl::native::{Gbm, GbmSurfaceArguments}; use drm::control::{Device, ResourceInfo}; use drm::control::{connector, crtc, encoder, framebuffer, Mode}; -use gbm::{BufferObject, BufferObjectFlags, Format as GbmFormat, Surface as GbmSurface, SurfaceBufferHandle}; +use gbm::{Device as GbmDevice, BufferObject, BufferObjectFlags, Format as GbmFormat, Surface as GbmSurface, SurfaceBufferHandle}; use image::{ImageBuffer, Rgba}; -use nix::libc::c_void; +use nix::libc::{c_void, c_uint}; 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}; +use wayland_server::Display; /// Backend based on a `DrmDevice` and a given crtc -pub struct DrmBackend { - graphics: Graphics, +pub struct DrmBackend { + context: Rc, GbmDevice>>, + surface: EGLSurface>, + cursor: Cell>, + front_buffer: Cell>, + next_buffer: Cell>>, crtc: crtc::Handle, mode: Mode, connectors: Vec, logger: ::slog::Logger, } -impl DrmBackend { +impl DrmBackend { pub(crate) fn new( - context: Rc, crtc: crtc::Handle, mode: Mode, connectors: Vec, + context: Rc, GbmDevice>>, + crtc: crtc::Handle, + mode: Mode, + connectors: Vec, logger: ::slog::Logger, - ) -> Result { + ) -> Result { // logger already initialized by the DrmDevice let log = ::slog_or_stdlog(logger); info!(log, "Initializing DrmBackend"); let (w, h) = mode.size(); + debug!(log, "Creating Surface"); + let surface = context.create_surface( + GbmSurfaceArguments { + size: (w as u32, h as u32), + format: GbmFormat::XRGB8888, + flags: BufferObjectFlags::SCANOUT | BufferObjectFlags::RENDERING, + } + ).chain_err(|| ErrorKind::GbmInitFailed)?; + + // make it active for the first `crtc::set` + // (which is needed before the first page_flip) + unsafe { + surface + .make_current() + .chain_err(|| ErrorKind::FailedToSwap)? + }; + surface + .swap_buffers() + .chain_err(|| ErrorKind::FailedToSwap)?; + + // init the first screen + // (must be done before calling page_flip for the first time) + let mut front_bo = surface + .lock_front_buffer() + .chain_err(|| ErrorKind::FailedToSwap)?; + + debug!(log, "FrontBuffer color format: {:?}", front_bo.format()); + + // we need a framebuffer for the front buffer + let fb = framebuffer::create(&*context, &*front_bo) + .chain_err(|| ErrorKind::DrmDev(format!("Error creating framebuffer on {:?}", context.dev_path())))?; + + debug!(log, "Initialize screen"); + crtc::set( + &*context, + crtc, + fb.handle(), + &connectors, + (0, 0), + Some(mode), + ).chain_err(|| ErrorKind::DrmDev(format!("Error setting crtc {:?} on {:?}", crtc, context.dev_path())))?; + front_bo.set_userdata(fb).unwrap(); + + let cursor = Cell::new(context.create_buffer_object( + 1, 1, + GbmFormat::ARGB8888, + BufferObjectFlags::CURSOR | BufferObjectFlags::WRITE, + ).chain_err(|| ErrorKind::GbmInitFailed)?); + 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)?, - }) - })?, + context, + surface, + cursor, + front_buffer: Cell::new(front_bo), + next_buffer: Cell::new(None), crtc, mode, connectors, - logger: log.clone(), + logger: log, }) } 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 old front buffer"); - egl.buffers.front_buffer.set(next_buffer); - // drop and release the old buffer - } - }) - }); + if let Some(next_buffer) = self.next_buffer.replace(None) { + trace!(self.logger, "Releasing old front buffer"); + self.front_buffer.set(next_buffer); + // drop and release the old buffer + } } /// Add a connector to backend @@ -188,8 +117,8 @@ impl DrmBackend { /// /// 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())))?; + let info = connector::Info::load_from_device(&*self.context, connector) + .chain_err(|| ErrorKind::DrmDev(format!("Error loading connector info on {:?}", self.context.dev_path())))?; // check if the connector can handle the current mode if info.modes().contains(&self.mode) { @@ -197,18 +126,15 @@ impl DrmBackend { 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()))) + encoder::Info::load_from_device(&*self.context, *encoder) + .chain_err(|| ErrorKind::DrmDev(format!("Error loading encoder info on {:?}", self.context.dev_path()))) }) .collect::>>()?; // and if any encoder supports the selected crtc - let resource_handles = self.graphics - .head() - .head() - .head() + let resource_handles = self.context .resource_handles() - .chain_err(|| ErrorKind::DrmDev(format!("{:?}", self.graphics.head().head().head())))?; + .chain_err(|| ErrorKind::DrmDev(format!("Error loading resources on {:?}", self.context.dev_path())))?; if !encoders .iter() .map(|encoder| encoder.possible_crtcs()) @@ -239,7 +165,7 @@ impl DrmBackend { /// Removes a currently set connector pub fn remove_connector(&mut self, connector: connector::Handle) { - if let Ok(info) = connector::Info::load_from_device(self.graphics.head().head().head(), connector) { + if let Ok(info) = connector::Info::load_from_device(&*self.context, connector) { info!( self.logger, "Removing connector: {:?}", @@ -262,8 +188,8 @@ impl DrmBackend { pub fn use_mode(&mut self, mode: Mode) -> Result<()> { // check the connectors for connector in &self.connectors { - if !connector::Info::load_from_device(self.graphics.head().head().head(), *connector) - .chain_err(|| ErrorKind::DrmDev(format!("{:?}", self.graphics.head().head().head())))? + if !connector::Info::load_from_device(&*self.context, *connector) + .chain_err(|| ErrorKind::DrmDev(format!("Error loading connector info on {:?}", self.context.dev_path())))? .modes() .contains(&mode) { @@ -271,90 +197,82 @@ impl DrmBackend { } } - // 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; - + info!(self.logger, "Setting new mode: {:?}", mode.name()); 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 + // Recreate the surface and the related resources to match the new + // resolution. + debug!(self.logger, "Reinitializing surface for new mode: {}:{}", w, h); + let surface = self.context.create_surface( + GbmSurfaceArguments { + size: (w as u32, h as u32), + format: GbmFormat::XRGB8888, + flags: BufferObjectFlags::SCANOUT | BufferObjectFlags::RENDERING, + } + ).chain_err(|| ErrorKind::GbmInitFailed)?; + + // make it active for the first `crtc::set` + // (which is needed before the first page_flip) + unsafe { + surface + .make_current() + .chain_err(|| ErrorKind::FailedToSwap)? + }; + surface + .swap_buffers() + .chain_err(|| ErrorKind::FailedToSwap)?; + + // Clean up next_buffer + { + if let Some(mut old_bo) = self.next_buffer.take() { + if let Ok(Some(fb)) = old_bo.take_userdata() { + if let Err(err) = framebuffer::destroy(&*self.context, fb.handle()) { + warn!(self.logger, "Error releasing old back_buffer framebuffer: {:?}", err); + } + } + } + } + + // Cleanup front_buffer and init the first screen on the new front_buffer + // (must be done before calling page_flip for the first time) + let fb = { + let mut old_front_bo = self.front_buffer.replace( + surface + .lock_front_buffer() + .chain_err(|| ErrorKind::FailedToSwap)? ); - 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)?; + if let Ok(Some(fb)) = old_front_bo.take_userdata() { + if let Err(err) = framebuffer::destroy(&*self.context, fb.handle()) { + warn!(self.logger, "Error releasing old front_buffer framebuffer: {:?}", err); + } + } - // 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 front_bo = self.front_buffer.get_mut(); + debug!(self.logger, "FrontBuffer color format: {:?}", front_bo.format()); - 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)))?; + // we also need a new framebuffer for the front buffer + let dev_path = self.context.dev_path(); + let fb = framebuffer::create(&*self.context, &**front_bo) + .chain_err(|| ErrorKind::DrmDev(format!("Error creating framebuffer on {:?}", dev_path)))?; - 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); + front_bo.set_userdata(fb).unwrap(); - Ok(EGL { - surface: egl_surface, - buffers: GbmBuffers { - front_buffer: Cell::new(front_bo), - next_buffer: Cell::new(None), - }, - }) - }, - )?; + fb + }; - Ok(()) - })?; + debug!(self.logger, "Setting screen"); + crtc::set( + &*self.context, + self.crtc, + fb.handle(), + &self.connectors, + (0, 0), + Some(mode), + ).chain_err(|| ErrorKind::DrmDev(format!("Error setting crtc {:?} on {:?}", self.crtc, self.context.dev_path())))?; - info!(self.logger, "Setting new mode: {:?}", mode.name()); + + // Drop the old surface after cleanup + self.surface = surface; self.mode = mode; Ok(()) } @@ -365,157 +283,138 @@ impl DrmBackend { } } -impl Drop for DrmBackend { +impl Drop for DrmBackend { fn drop(&mut self) { // Drop framebuffers attached to the userdata of the gbm surface buffers. // (They don't implement drop, as they need the device) - let crtc = self.crtc; - 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() { - next.take_userdata() - } else if let Ok(mut next) = graphics.gbm.surface.head().lock_front_buffer() { - next.take_userdata() - } else { - None - } - }) { - // ignore failure at this point - let _ = framebuffer::destroy(&*graphics.context.devices.drm, fb.handle()); + if let Ok(Some(fb)) = { + if let Some(mut next) = self.next_buffer.take() { + next.take_userdata() + } else if let Ok(mut next) = self.surface.lock_front_buffer() { + next.take_userdata() + } else { + Ok(None) } - - 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()); - } - + } { // ignore failure at this point - let _ = crtc::clear_cursor(&*graphics.context.devices.drm, crtc); - }) + let _ = framebuffer::destroy(&*self.context, fb.handle()); + } + + if let Ok(Some(fb)) = self.front_buffer.get_mut().take_userdata() { + // ignore failure at this point + let _ = framebuffer::destroy(&*self.context, fb.handle()); + } + + // ignore failure at this point + let _ = crtc::clear_cursor(&*self.context, self.crtc); } } -impl GraphicsBackend for DrmBackend { +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.context, self.crtc, (x as i32, y as i32), - ).chain_err(|| ErrorKind::DrmDev(format!("{:?}", self.graphics.head().head().head()))) + ).chain_err(|| ErrorKind::DrmDev(format!("Error moving cursor on {:?}", self.context.dev_path()))) } 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) - .chain_err(|| ErrorKind::GbmInitFailed)?; + // import the cursor into a buffer we can render + let mut cursor = self + .context + .create_buffer_object( + w, + h, + GbmFormat::ARGB8888, + BufferObjectFlags::CURSOR | BufferObjectFlags::WRITE, + ) + .chain_err(|| ErrorKind::GbmInitFailed)?; + cursor + .write(&**buffer) + .chain_err(|| ErrorKind::GbmInitFailed)? + .chain_err(|| ErrorKind::GbmInitFailed)?; - trace!(self.logger, "Set the new imported cursor"); + trace!(self.logger, "Setting the new imported cursor"); - // and set it - if crtc::set_cursor2( - self.graphics.head().head().head(), - self.crtc, - &cursor, - (hotspot.0 as i32, hotspot.1 as i32), - ).is_err() - { - crtc::set_cursor(self.graphics.head().head().head(), self.crtc, &cursor).chain_err( - || ErrorKind::DrmDev(format!("{:?}", self.graphics.head().head().head())), - )?; - } + // and set it + if crtc::set_cursor2( + &*self.context, + self.crtc, + &cursor, + (hotspot.0 as i32, hotspot.1 as i32), + ).is_err() + { + crtc::set_cursor(&*self.context, self.crtc, &cursor).chain_err( + || ErrorKind::DrmDev(format!("Failed to set cursor on {:?}", self.context.dev_path())), + )?; + } - // and store it - cursor - }); - Ok(()) - }) + // and store it + self.cursor.set(cursor); + Ok(()) } } -impl EGLGraphicsBackend for DrmBackend { +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); - } + // We cannot call lock_front_buffer anymore without releasing the previous buffer, which will happen when the page flip is done + if { + let nb = self.next_buffer.take(); + let res = nb.is_some(); + self.next_buffer.set(nb); + 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())?; + // flip normally + self.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"); + // supporting only one buffer would cause a lot of inconvinience and + // would most likely result in a lot of flickering. + // neither weston, wlc or wlroots bother with that as well. + // so we just assume we got at least two buffers to do flipping. + let mut next_bo = self.surface + .lock_front_buffer() + .expect("Surface only has one front buffer. Not supported by smithay"); - // create a framebuffer if the front buffer does not have one already - // (they are reused by gbm) - let maybe_fb = next_bo.userdata().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)); + // create a framebuffer if the front buffer does not have one already + // (they are reused by gbm) + let maybe_fb = next_bo.userdata().map_err(|_| SwapBuffersError::ContextLost)?.cloned(); + let fb = if let Some(info) = maybe_fb { + info + } else { + let fb = framebuffer::create(&*self.context, &*next_bo) + .map_err(|_| SwapBuffersError::ContextLost)?; + next_bo.set_userdata(fb).unwrap(); + fb + }; + self.next_buffer.set(Some(next_bo)); - trace!(self.logger, "Queueing Page flip"); + trace!(self.logger, "Queueing Page flip"); - // and flip - crtc::page_flip( - graphics.context.devices.drm, - self.crtc, - fb.handle(), - &[crtc::PageFlipFlags::PageFlipEvent], - ).map_err(|_| SwapBuffersError::ContextLost) - }) - }) + // and flip + crtc::page_flip( + &*self.context, + self.crtc, + fb.handle(), + &[crtc::PageFlipFlags::PageFlipEvent], + ).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)) + self.context.get_proc_address(symbol) } fn get_framebuffer_dimensions(&self) -> (u32, u32) { @@ -524,19 +423,27 @@ impl EGLGraphicsBackend for DrmBackend { } fn is_current(&self) -> bool { - self.graphics.rent_all(|graphics| { - graphics.context.egl.is_current() && graphics.gbm.surface.rent(|egl| egl.surface.is_current()) - }) + self.context.is_current() && self.surface.is_current() } unsafe fn make_current(&self) -> ::std::result::Result<(), SwapBuffersError> { - self.graphics - .rent(|gbm| gbm.surface.rent(|egl| egl.surface.make_current())) + self.surface.make_current() } fn get_pixel_format(&self) -> PixelFormat { - self.graphics - .head() - .rent(|context| context.get_pixel_format()) + self.context.get_pixel_format() } + + fn bind_wl_display(&self, display: &Display) -> ::std::result::Result<(), EglExtensionNotSupportedError> { + self.context.bind_wl_display(display).map_err(EglExtensionNotSupportedError::from) + } + + fn unbind_wl_display(&self, display: &Display) -> ::std::result::Result<(), EglExtensionNotSupportedError> { + self.context.unbind_wl_display(display).map_err(EglExtensionNotSupportedError::from) + } + + /*unsafe fn egl_image_to_texture(&self, image: EGLImage, tex_id: c_uint) -> ::std::result::Result<(), EglExtensionNotSupportedError> { + self.graphics.head().rent(|context| context.egl_image_to_texture(image, tex_id))?; + Ok(()) + }*/ } diff --git a/src/backend/drm/error.rs b/src/backend/drm/error.rs index 35517ee..681982b 100644 --- a/src/backend/drm/error.rs +++ b/src/backend/drm/error.rs @@ -2,9 +2,8 @@ //! Errors thrown by the `DrmDevice` and `DrmBackend` //! -use backend::graphics::egl; +use backend::graphics::egl::error as egl; use drm::control::{connector, crtc, Mode}; -use rental::TryNewError; error_chain! { errors { @@ -60,9 +59,3 @@ error_chain! { 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 index bc321b6..7ad355d 100644 --- a/src/backend/drm/mod.rs +++ b/src/backend/drm/mod.rs @@ -187,26 +187,27 @@ //! # } //! ``` -use backend::graphics::egl::{EGLContext, GlAttributes, PixelFormatRequirements}; +#[cfg(feature = "backend_session")] use backend::graphics::egl::EGLGraphicsBackend; +use backend::graphics::egl::context::{EGLContext, GlAttributes, PixelFormatRequirements}; +use backend::graphics::egl::native::Gbm; #[cfg(feature = "backend_session")] use backend::session::SessionObserver; use drm::Device as BasicDevice; use drm::control::{connector, crtc, encoder, Mode, ResourceInfo}; use drm::control::Device as ControlDevice; use drm::result::Error as DrmError; +use drm::control::framebuffer; use gbm::Device as GbmDevice; use nix; -use nix::Result as NixResult; -use nix::unistd::close; use std::borrow::Borrow; use std::collections::HashMap; -use std::fs::File; use std::hash::{Hash, Hasher}; use std::io::Result as IoResult; -use std::mem; -use std::os::unix::io::{AsRawFd, IntoRawFd, RawFd}; +use std::os::unix::io::{AsRawFd, RawFd}; use std::rc::Rc; +use std::sync::{Once, ONCE_INIT}; +use std::path::PathBuf; use std::time::Duration; use wayland_server::{EventLoopHandle, StateProxy, StateToken}; use wayland_server::sources::{FdEventSource, FdEventSourceImpl, FdInterest}; @@ -217,76 +218,28 @@ 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(RawFd); - -impl AsRawFd for DrmDev { - fn as_raw_fd(&self) -> RawFd { - self.0 - } -} -impl BasicDevice for DrmDev {} -impl ControlDevice for DrmDev {} - -impl DrmDev { - unsafe fn new_from_fd(fd: RawFd) -> Self { - DrmDev(fd) - } - - fn new_from_file(file: File) -> Self { - DrmDev(file.into_raw_fd()) - } -} - -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}; +static LOAD: Once = ONCE_INIT; /// Representation of an open drm device node to create rendering backends -pub struct DrmDevice + 'static> { - context: Rc, +pub struct DrmDevice> + 'static> { + context: Rc, GbmDevice>>, backends: HashMap>, old_state: HashMap)>, active: bool, logger: ::slog::Logger, } -impl + Borrow + 'static> DrmDevice { - /// Create a new `DrmDevice` from a raw file descriptor +impl> + Borrow> + 'static> DrmDevice { + /// Create a new `DrmDevice` from an open drm node /// - /// Returns an error of opening the device failed or context creation was not + /// Returns an error if the file is no valid drm node 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 + pub fn new(dev: A, logger: L) -> Result where L: Into>, { - DrmDevice::new( - DrmDev::new_from_fd(fd), + DrmDevice::new_with_gl_attr( + dev, GlAttributes { version: None, profile: None, @@ -297,53 +250,11 @@ impl + Borrow + 'static> DrmDevice { ) } - /// Create a new `DrmDevice` from a raw file descriptor and given `GlAttributes` - /// - /// Returns an error of opening the device failed or context creation was not - /// successful. - /// - /// # Safety - /// The file descriptor might not be valid and needs to be owned by smithay, - /// make sure not to share it. Otherwise undefined behavior might occur. - pub unsafe fn new_from_fd_with_gl_attr(fd: RawFd, attributes: GlAttributes, logger: L) -> Result - where - L: Into>, - { - DrmDevice::new(DrmDev::new_from_fd(fd), attributes, logger) - } - - /// Create a new `DrmDevice` from a `File` of an open drm node + /// Create a new `DrmDevice` from 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(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 + pub fn new_with_gl_attr(dev: A, attributes: GlAttributes, logger: L) -> Result where L: Into>, { @@ -351,35 +262,58 @@ impl + Borrow + 'static> DrmDevice { /* 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, + * 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 { + LOAD.call_once(|| unsafe { nix::libc::dlopen( "libglapi.so.0".as_ptr() as *const _, nix::libc::RTLD_LAZY | nix::libc::RTLD_GLOBAL, ); - } + }); + + let mut drm = DrmDevice { + // Open the gbm device from the drm device and create a context based on that + context: Rc::new(EGLContext::new( + { + debug!(log, "Creating gbm device"); + let gbm = GbmDevice::new(dev).chain_err(|| ErrorKind::GbmInitFailed)?; + debug!(log, "Creating egl context from gbm device"); + gbm + }, + attributes, + PixelFormatRequirements { + hardware_accelerated: Some(true), + color_bits: Some(24), + alpha_bits: Some(8), + ..Default::default() + }, + log.clone(), + ).map_err(Error::from)?), + backends: HashMap::new(), + old_state: HashMap::new(), + active: true, + logger: log.clone(), + }; info!(log, "DrmDevice initializing"); // we want to mode-set, so we better be the master drm.set_master().chain_err(|| ErrorKind::DrmMasterFailed)?; - let mut old_state = HashMap::new(); let res_handles = drm.resource_handles() - .chain_err(|| ErrorKind::DrmDev(format!("Loading drm resources on {:?}", drm)))?; + .chain_err(|| ErrorKind::DrmDev(format!("Error loading drm resources on {:?}", drm.dev_path())))?; for &con in res_handles.connectors() { let con_info = connector::Info::load_from_device(&drm, con) - .chain_err(|| ErrorKind::DrmDev(format!("Loading connector info on {:?}", drm)))?; + .chain_err(|| ErrorKind::DrmDev(format!("Error loading connector info on {:?}", drm.dev_path())))?; if let Some(enc) = con_info.current_encoder() { let enc_info = encoder::Info::load_from_device(&drm, enc) - .chain_err(|| ErrorKind::DrmDev(format!("Loading encoder info on {:?}", drm)))?; + .chain_err(|| ErrorKind::DrmDev(format!("Error loading encoder info on {:?}", drm.dev_path())))?; if let Some(crtc) = enc_info.current_crtc() { let info = crtc::Info::load_from_device(&drm, crtc) - .chain_err(|| ErrorKind::DrmDev(format!("Loading crtc info on {:?}", drm)))?; - old_state + .chain_err(|| ErrorKind::DrmDev(format!("Error loading crtc info on {:?}", drm.dev_path())))?; + drm.old_state .entry(crtc) .or_insert((info, Vec::new())) .1 @@ -388,33 +322,7 @@ impl + Borrow + 'static> DrmDevice { } } - // Open the gbm device from the drm device and create a context based on that - Ok(DrmDevice { - context: Rc::new(Context::try_new( - Box::new(Devices::try_new(Box::new(drm), |drm| { - 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(), - old_state, - active: true, - logger: log, - }) + Ok(drm) } /// Create a new backend on a given crtc with a given `Mode` for a given amount @@ -442,12 +350,9 @@ impl + Borrow + 'static> DrmDevice { // check if we have an encoder for every connector and the mode mode for connector in &connectors { - let con_info = connector::Info::load_from_device(self.context.head().head(), *connector) + let con_info = connector::Info::load_from_device(self, *connector) .chain_err(|| { - ErrorKind::DrmDev(format!( - "Loading connector info on {:?}", - self.context.head().head() - )) + ErrorKind::DrmDev(format!("Error loading connector info on {:?}", self.dev_path())) })?; // check the mode @@ -460,21 +365,15 @@ impl + Borrow + 'static> DrmDevice { .encoders() .iter() .map(|encoder| { - encoder::Info::load_from_device(self.context.head().head(), *encoder).chain_err(|| { - ErrorKind::DrmDev(format!( - "Loading encoder info on {:?}", - self.context.head().head() - )) + encoder::Info::load_from_device(self, *encoder).chain_err(|| { + ErrorKind::DrmDev(format!("Error loading encoder info on {:?}", self.dev_path())) }) }) .collect::>>()?; // and if any encoder supports the selected crtc let resource_handles = self.resource_handles().chain_err(|| { - ErrorKind::DrmDev(format!( - "Loading drm resources on {:?}", - self.context.head().head() - )) + ErrorKind::DrmDev(format!("Error loading drm resources on {:?}", self.dev_path())) })?; if !encoders .iter() @@ -517,37 +416,38 @@ impl + Borrow + 'static> DrmDevice { state.into().remove(token); } } +} - /// Close the device - /// - /// ## Warning - /// Never call this function if the device is managed by another backend e.g. the `UdevBackend`. - /// Only use this function for manually initialized devices. - pub fn close(self) -> NixResult<()> { - let fd = self.as_raw_fd(); - mem::drop(self); - close(fd) +pub trait DevPath { + fn dev_path(&self) -> Option; +} + +impl DevPath for A { + fn dev_path(&self) -> Option { + use std::fs; + + fs::read_link(format!("/proc/self/fd/{:?}", self.as_raw_fd())).ok() } } // for users convinience and FdEventSource registering -impl + 'static> AsRawFd for DrmDevice { +impl> + 'static> AsRawFd for DrmDevice { fn as_raw_fd(&self) -> RawFd { - self.context.head().head().as_raw_fd() + self.context.as_raw_fd() } } -impl + 'static> BasicDevice for DrmDevice {} -impl + 'static> ControlDevice for DrmDevice {} +impl> + 'static> BasicDevice for DrmDevice {} +impl> + 'static> ControlDevice for DrmDevice {} -impl + 'static> Drop for DrmDevice { +impl> + 'static> Drop for DrmDevice { fn drop(&mut self) { if Rc::strong_count(&self.context) > 1 { panic!("Pending DrmBackends. Please free all backends before the DrmDevice gets destroyed"); } for (handle, (info, connectors)) in self.old_state.drain() { if let Err(err) = crtc::set( - self.context.head().head(), + &*self.context, handle, info.fb(), &connectors, @@ -569,7 +469,7 @@ impl + 'static> Drop for DrmDevice { } } -impl + 'static> Hash for DrmDevice { +impl> + 'static> Hash for DrmDevice { fn hash(&self, state: &mut H) { self.as_raw_fd().hash(state) } @@ -578,7 +478,7 @@ impl + 'static> Hash for DrmDevice { /// Handler for drm node events /// /// See module-level documentation for its use -pub trait DrmHandler + 'static> { +pub trait DrmHandler> + 'static> { /// A `DrmBackend` has finished swapping buffers and new frame can now /// (and should be immediately) be rendered. /// @@ -589,7 +489,7 @@ pub trait DrmHandler + 'static> { /// The device is already borrowed from the given `state`. Borrowing it again will panic /// and is not necessary as it is already provided via the `device` parameter. fn ready<'a, S: Into>>( - &mut self, state: S, device: &mut DrmDevice, backend: &StateToken, crtc: crtc::Handle, + &mut self, state: S, device: &mut DrmDevice, backend: &StateToken, crtc: crtc::Handle, frame: u32, duration: Duration, ); /// The `DrmDevice` has thrown an error. @@ -600,18 +500,19 @@ pub trait DrmHandler + 'static> { /// ## Panics /// The device is already borrowed from the given `state`. Borrowing it again will panic /// and is not necessary as it is already provided via the `device` parameter. - fn error<'a, S: Into>>(&mut self, state: S, device: &mut DrmDevice, error: DrmError); + fn error<'a, S: Into>>(&mut self, state: S, device: &mut DrmDevice, error: DrmError); } /// Bind a `DrmDevice` to an `EventLoop`, /// /// This will cause it to recieve events and feed them into an `DrmHandler` -pub fn drm_device_bind( - evlh: &mut EventLoopHandle, device: StateToken>, handler: H -) -> IoResult>, H)>> +pub fn drm_device_bind( + evlh: &mut EventLoopHandle, device: StateToken>, handler: H +) -> IoResult>, H)>> where - B: From + Borrow + 'static, - H: DrmHandler + 'static, + A: ControlDevice + 'static, + B: From> + Borrow> + 'static, + H: DrmHandler + 'static, { let fd = evlh.state().get(&device).as_raw_fd(); evlh.add_fd_event_source( @@ -622,10 +523,11 @@ where ) } -fn fd_event_source_implementation() -> FdEventSourceImpl<(StateToken>, H)> +fn fd_event_source_implementation() -> FdEventSourceImpl<(StateToken>, H)> where - B: From + Borrow + 'static, - H: DrmHandler + 'static, + A: ControlDevice + 'static, + B: From> + Borrow> + 'static, + H: DrmHandler + 'static, { FdEventSourceImpl { ready: |evlh, &mut (ref mut dev_token, ref mut handler), _, _| { @@ -674,9 +576,9 @@ where } #[cfg(feature = "backend_session")] -impl + 'static> SessionObserver for StateToken> { +impl> + 'static> SessionObserver for StateToken> { fn pause<'a>(&mut self, state: &mut StateProxy<'a>) { - let device: &mut DrmDevice = state.get_mut(self); + let device: &mut DrmDevice = state.get_mut(self); device.active = false; if let Err(err) = device.drop_master() { error!( diff --git a/src/backend/graphics/egl.rs b/src/backend/graphics/egl.rs deleted file mode 100644 index 8470fd1..0000000 --- a/src/backend/graphics/egl.rs +++ /dev/null @@ -1,1222 +0,0 @@ -//! Common traits and types for egl context creation and rendering - -/// Large parts of the following file are taken from -/// https://github.com/tomaka/glutin/tree/044e651edf67a2029eecc650dd42546af1501414/src/api/egl/ -/// -/// It therefore falls under glutin's Apache 2.0 license -/// (see https://github.com/tomaka/glutin/tree/044e651edf67a2029eecc650dd42546af1501414/LICENSE) -use super::GraphicsBackend; -#[cfg(feature = "backend_drm")] -use gbm::{AsRaw, Device as GbmDevice, Surface as GbmSurface}; -use nix::libc::{c_int, c_uint, c_void}; -use rental::TryNewError; -use slog; -use std::error; -use std::ffi::{CStr, CString}; -use std::fmt; -use std::marker::PhantomData; -use std::mem; -use std::ops::{Deref, DerefMut}; -use std::ptr; -#[cfg(feature = "backend_winit")] -use wayland_client::egl as wegl; -#[cfg(feature = "backend_winit")] -use winit::Window as WinitWindow; -#[cfg(feature = "backend_winit")] -use winit::os::unix::WindowExt; -use wayland_server::Display; - -#[allow(non_camel_case_types, dead_code, unused_mut)] -pub(crate) mod ffi { - use nix::libc::{c_long, c_void, int32_t, uint64_t}; - - pub type khronos_utime_nanoseconds_t = khronos_uint64_t; - pub type khronos_uint64_t = uint64_t; - pub type khronos_ssize_t = c_long; - pub type EGLint = int32_t; - pub type EGLNativeDisplayType = NativeDisplayType; - pub type EGLNativePixmapType = NativePixmapType; - pub type EGLNativeWindowType = NativeWindowType; - pub type NativeDisplayType = *const c_void; - pub type NativePixmapType = *const c_void; - pub type NativeWindowType = *const c_void; - - pub mod gl { - include!(concat!(env!("OUT_DIR"), "/egl_bindings.rs")); - } - - pub mod egl { - use super::*; - use libloading::Library; - use std::sync::{Once, ONCE_INIT}; - - lazy_static! { - pub static ref LIB: Library = { - Library::new("libEGL.so.1").expect("Failed to load LibEGL") - }; - } - - pub static LOAD: Once = ONCE_INIT; - - include!(concat!(env!("OUT_DIR"), "/egl_bindings.rs")); - - /* - * `gl_generator` cannot generate bindings for the `EGL_WL_bind_wayland_display` extension. - * Lets do it ourselves... - */ - - #[allow(non_snake_case, unused_variables, dead_code)] #[inline] - pub unsafe fn BindWaylandDisplayWL(dpy: types::EGLDisplay, display: *mut __gl_imports::raw::c_void) -> types::EGLBoolean { - __gl_imports::mem::transmute::<_, extern "system" fn(types::EGLDisplay, *mut __gl_imports::raw::c_void) -> types::EGLBoolean>(wayland_storage::BindWaylandDisplayWL.f)(dpy, display) - } - - #[allow(non_snake_case, unused_variables, dead_code)] #[inline] - pub unsafe fn UnbindWaylandDisplayWL(dpy: types::EGLDisplay, display: *mut __gl_imports::raw::c_void) -> types::EGLBoolean { - __gl_imports::mem::transmute::<_, extern "system" fn(types::EGLDisplay, *mut __gl_imports::raw::c_void) -> types::EGLBoolean>(wayland_storage::UnbindWaylandDisplayWL.f)(dpy, display) - } - - #[allow(non_snake_case, unused_variables, dead_code)] #[inline] - pub unsafe fn QueryWaylandBufferWL(dpy: types::EGLDisplay, buffer: *mut __gl_imports::raw::c_void, attribute: types::EGLint, value: *mut types::EGLint) -> types::EGLBoolean { - __gl_imports::mem::transmute::<_, extern "system" fn(types::EGLDisplay, *mut __gl_imports::raw::c_void, types::EGLint, *mut types::EGLint) -> types::EGLBoolean>(wayland_storage::QueryWaylandBufferWL.f)(dpy, buffer, attribute, value) - } - - mod wayland_storage { - use super::__gl_imports::raw; - use super::FnPtr; - pub static mut BindWaylandDisplayWL: FnPtr = FnPtr { - f: super::missing_fn_panic as *const raw::c_void, - is_loaded: false - }; - pub static mut UnbindWaylandDisplayWL: FnPtr = FnPtr { - f: super::missing_fn_panic as *const raw::c_void, - is_loaded: false - }; - pub static mut QueryWaylandBufferWL: FnPtr = FnPtr { - f: super::missing_fn_panic as *const raw::c_void, - is_loaded: false - }; - } - - #[allow(non_snake_case)] - pub mod BindWaylandDisplayWL { - use super::{wayland_storage, metaloadfn}; - use super::__gl_imports::raw; - use super::FnPtr; - - #[inline] - #[allow(dead_code)] - pub fn is_loaded() -> bool { - unsafe { wayland_storage::BindWaylandDisplayWL.is_loaded } - } - - #[allow(dead_code)] - pub fn load_with(mut loadfn: F) where F: FnMut(&str) -> *const raw::c_void { - unsafe { - wayland_storage::BindWaylandDisplayWL = FnPtr::new(metaloadfn(&mut loadfn, "eglBindWaylandDisplayWL", &[])) - } - } - } - - #[allow(non_snake_case)] - pub mod UnbindWaylandDisplayWL { - use super::{wayland_storage, metaloadfn}; - use super::__gl_imports::raw; - use super::FnPtr; - - #[inline] - #[allow(dead_code)] - pub fn is_loaded() -> bool { - unsafe { wayland_storage::UnbindWaylandDisplayWL.is_loaded } - } - - #[allow(dead_code)] - pub fn load_with(mut loadfn: F) where F: FnMut(&str) -> *const raw::c_void { - unsafe { - wayland_storage::UnbindWaylandDisplayWL = FnPtr::new(metaloadfn(&mut loadfn, "eglUnbindWaylandDisplayWL", &[])) - } - } - } - - #[allow(non_snake_case)] - pub mod QueryWaylandBufferWL { - use super::{wayland_storage, metaloadfn}; - use super::__gl_imports::raw; - use super::FnPtr; - - #[inline] - #[allow(dead_code)] - pub fn is_loaded() -> bool { - unsafe { wayland_storage::QueryWaylandBufferWL.is_loaded } - } - - #[allow(dead_code)] - pub fn load_with(mut loadfn: F) where F: FnMut(&str) -> *const raw::c_void { - unsafe { - wayland_storage::QueryWaylandBufferWL = FnPtr::new(metaloadfn(&mut loadfn, "eglQueryWaylandBufferWL", &[])) - } - } - } - - // Accepted as in eglCreateImageKHR - pub const WAYLAND_BUFFER_WL = 0x31D5; - // Accepted in the parameter of eglCreateImageKHR: - pub const EGL_WAYLAND_PLANE_WL = 0x31D6; - // Possible values for EGL_TEXTURE_FORMAT: - pub const EGL_TEXTURE_Y_U_V_WL = 0x31D7; - pub const EGL_TEXTURE_Y_UV_WL = 0x31D8; - pub const EGL_TEXTURE_Y_XUXV_WL = 0x31D9; - pub const EGL_TEXTURE_EXTERNAL_WL = 0x31DA; - // Accepted in the parameter of eglQueryWaylandBufferWL: - pub const EGL_TEXTURE_FORMAT = 0x3080; - pub const EGL_WAYLAND_Y_INVERTED_WL = 0x31DB; - } -} - -/// Native types to create an `EGLContext` from. -/// Currently supported providers are X11, Wayland and GBM. -#[derive(Clone, Copy)] -enum NativeDisplayPtr { - /// X11 Display to create an `EGLContext` upon. - X11(ffi::NativeDisplayType), - /// Wayland Display to create an `EGLContext` upon. - Wayland(ffi::NativeDisplayType), - /// GBM Display - Gbm(ffi::NativeDisplayType), -} - -/// Native types to create an `EGLSurface` from. -/// Currently supported providers are X11, Wayland and GBM. -#[derive(Clone, Copy)] -pub enum NativeSurfacePtr { - /// X11 Window to create an `EGLSurface` upon. - X11(ffi::NativeWindowType), - /// Wayland Surface to create an `EGLSurface` upon. - Wayland(ffi::NativeWindowType), - /// GBM Surface - Gbm(ffi::NativeWindowType), -} - -/// 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_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), - } - - #[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 required 'EGL_WL_bind_wayland_display' extension is not supported by the underlying EGL implementation"] - WlExtensionNotSupported { - description("The required 'EGL_WL_bind_wayland_display' extension is not supported by the underlying EGL implementation") - } - - #[doc = "Only one EGLDisplay may be bound to a given WlDisplay at any time"] - OtherEGLDisplayAlreadyBound { - description("Only one EGLDisplay may be bound to a given WlDisplay at any time") - } - - #[doc = "No EGLDisplay is currently bound to this WlDisplay"] - NoEGLDisplayBound { - description("No EGLDisplay is currently bound to this WlDisplay") - } - - #[doc = "The reason of failure could not be determined"] - Unknown(err_no: u32) - } -} - -impl From> for Error { - fn from(err: TryNewError) -> Error { - err.0 - } -} - -/// 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)), - } - } -} - -#[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<'a, T: NativeSurface> { - context: ffi::egl::types::EGLContext, - pub(crate) display: ffi::egl::types::EGLDisplay, - config_id: ffi::egl::types::EGLConfig, - surface_attributes: Vec, - pixel_format: PixelFormat, - backend_type: NativeType, - pub(crate) wl_drm_support: bool, - logger: slog::Logger, - _lifetime: PhantomData<&'a ()>, - _type: PhantomData, -} - -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 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 { - Some((3, x)) => (3, x), - Some((2, x)) => (2, x), - None => { - debug!(log, "Trying to initialize EGL with OpenGLES 3.0"); - attributes.version = Some((3, 0)); - 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, log); - } - } - } - Some((1, x)) => { - error!( - log, - "OpenGLES 1.* is not supported by the EGL renderer backend" - ); - bail!(ErrorKind::OpenGlVersionNotSupported((1, x))); - } - Some(version) => { - error!( - log, - "OpenGLES {:?} is unknown and not supported by the EGL renderer backend", version - ); - bail!(ErrorKind::OpenGlVersionNotSupported(version)); - } - }; - - ffi::egl::LOAD.call_once(|| { - ffi::egl::load_with(|sym| { - let name = CString::new(sym).unwrap(); - let symbol = ffi::egl::LIB.get::<*mut c_void>(name.as_bytes()); - match symbol { - Ok(x) => *x as *const _, - Err(_) => ptr::null(), - } - }); - ffi::gl::load_with(|sym| { - let name = CString::new(sym).unwrap(); - let symbol = ffi::egl::LIB.get::<*mut c_void>(name.as_bytes()); - match symbol { - Ok(x) => *x as *const _, - Err(_) => ptr::null(), - } - }); - }); - - // the first step is to query the list of extensions without any display, if supported - let dp_extensions = { - let p = ffi::egl::QueryString(ffi::egl::NO_DISPLAY, ffi::egl::EXTENSIONS as i32); - - // this possibility is available only with EGL 1.5 or EGL_EXT_platform_base, otherwise - // `eglQueryString` returns an error - if p.is_null() { - vec![] - } else { - let p = CStr::from_ptr(p); - let list = String::from_utf8(p.to_bytes().to_vec()).unwrap_or_else(|_| String::new()); - list.split(' ').map(|e| e.to_string()).collect::>() - } - }; - - debug!(log, "EGL No-Display Extensions: {:?}", dp_extensions); - - let has_dp_extension = |e: &str| dp_extensions.iter().any(|s| s == e); - - let display = match native { - NativeDisplayPtr::X11(display) - if has_dp_extension("EGL_KHR_platform_x11") && ffi::egl::GetPlatformDisplay::is_loaded() => - { - trace!(log, "EGL Display Initialization via EGL_KHR_platform_x11"); - ffi::egl::GetPlatformDisplay(ffi::egl::PLATFORM_X11_KHR, display as *mut _, ptr::null()) - } - - NativeDisplayPtr::X11(display) - if has_dp_extension("EGL_EXT_platform_x11") && ffi::egl::GetPlatformDisplayEXT::is_loaded() => - { - trace!(log, "EGL Display Initialization via EGL_EXT_platform_x11"); - ffi::egl::GetPlatformDisplayEXT(ffi::egl::PLATFORM_X11_EXT, display as *mut _, ptr::null()) - } - - NativeDisplayPtr::Gbm(display) - if has_dp_extension("EGL_KHR_platform_gbm") && ffi::egl::GetPlatformDisplay::is_loaded() => - { - trace!(log, "EGL Display Initialization via EGL_KHR_platform_gbm"); - ffi::egl::GetPlatformDisplay(ffi::egl::PLATFORM_GBM_KHR, display as *mut _, ptr::null()) - } - - NativeDisplayPtr::Gbm(display) - if has_dp_extension("EGL_MESA_platform_gbm") && ffi::egl::GetPlatformDisplayEXT::is_loaded() => - { - trace!(log, "EGL Display Initialization via EGL_MESA_platform_gbm"); - ffi::egl::GetPlatformDisplayEXT(ffi::egl::PLATFORM_GBM_MESA, display as *mut _, ptr::null()) - } - - NativeDisplayPtr::Gbm(display) - if has_dp_extension("EGL_MESA_platform_gbm") && ffi::egl::GetPlatformDisplay::is_loaded() => - { - trace!(log, "EGL Display Initialization via EGL_MESA_platform_gbm"); - ffi::egl::GetPlatformDisplay(ffi::egl::PLATFORM_GBM_MESA, display as *mut _, ptr::null()) - } - - NativeDisplayPtr::Wayland(display) - if has_dp_extension("EGL_KHR_platform_wayland") && ffi::egl::GetPlatformDisplay::is_loaded() => - { - trace!( - log, - "EGL Display Initialization via EGL_KHR_platform_wayland" - ); - ffi::egl::GetPlatformDisplay( - ffi::egl::PLATFORM_WAYLAND_KHR, - display as *mut _, - ptr::null(), - ) - } - - NativeDisplayPtr::Wayland(display) - if has_dp_extension("EGL_EXT_platform_wayland") && ffi::egl::GetPlatformDisplayEXT::is_loaded() => - { - trace!( - log, - "EGL Display Initialization via EGL_EXT_platform_wayland" - ); - ffi::egl::GetPlatformDisplayEXT( - ffi::egl::PLATFORM_WAYLAND_EXT, - display as *mut _, - ptr::null(), - ) - } - - NativeDisplayPtr::X11(display) - | NativeDisplayPtr::Gbm(display) - | NativeDisplayPtr::Wayland(display) => { - trace!(log, "Default EGL Display Initialization via GetDisplay"); - ffi::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 ffi::egl::Initialize(display, &mut major, &mut minor) == 0 { - bail!(ErrorKind::InitFailed); - } - - info!(log, "EGL Initialized"); - info!(log, "EGL Version: {:?}", (major, minor)); - - (major, minor) - }; - - // the list of extensions supported by the client once initialized is different from the - // list of extensions obtained earlier - let extensions = if egl_version >= (1, 2) { - let p = CStr::from_ptr(ffi::egl::QueryString(display, ffi::egl::EXTENSIONS as i32)); - let list = String::from_utf8(p.to_bytes().to_vec()).unwrap_or_else(|_| String::new()); - list.split(' ').map(|e| e.to_string()).collect::>() - } else { - vec![] - }; - - info!(log, "EGL Extensions: {:?}", extensions); - - if egl_version >= (1, 2) && ffi::egl::BindAPI(ffi::egl::OPENGL_ES_API) == 0 { - error!( - log, - "OpenGLES not supported by the underlying EGL implementation" - ); - bail!(ErrorKind::OpenGlesNotSupported); - } - - let descriptor = { - let mut out: Vec = Vec::with_capacity(37); - - if egl_version >= (1, 2) { - trace!(log, "Setting COLOR_BUFFER_TYPE to RGB_BUFFER"); - out.push(ffi::egl::COLOR_BUFFER_TYPE as c_int); - out.push(ffi::egl::RGB_BUFFER as c_int); - } - - trace!(log, "Setting SURFACE_TYPE to WINDOW"); - - out.push(ffi::egl::SURFACE_TYPE as c_int); - // TODO: Some versions of Mesa report a BAD_ATTRIBUTE error - // if we ask for PBUFFER_BIT as well as WINDOW_BIT - out.push((ffi::egl::WINDOW_BIT) as c_int); - - match version { - (3, _) => { - if egl_version < (1, 3) { - error!( - log, - "OpenglES 3.* is not supported on EGL Versions lower then 1.3" - ); - bail!(ErrorKind::NoAvailablePixelFormat); - } - trace!(log, "Setting RENDERABLE_TYPE to OPENGL_ES3"); - out.push(ffi::egl::RENDERABLE_TYPE as c_int); - out.push(ffi::egl::OPENGL_ES3_BIT as c_int); - trace!(log, "Setting CONFORMANT to OPENGL_ES3"); - out.push(ffi::egl::CONFORMANT as c_int); - out.push(ffi::egl::OPENGL_ES3_BIT as c_int); - } - (2, _) => { - if egl_version < (1, 3) { - error!( - log, - "OpenglES 2.* is not supported on EGL Versions lower then 1.3" - ); - bail!(ErrorKind::NoAvailablePixelFormat); - } - trace!(log, "Setting RENDERABLE_TYPE to OPENGL_ES2"); - out.push(ffi::egl::RENDERABLE_TYPE as c_int); - out.push(ffi::egl::OPENGL_ES2_BIT as c_int); - trace!(log, "Setting CONFORMANT to OPENGL_ES2"); - out.push(ffi::egl::CONFORMANT as c_int); - out.push(ffi::egl::OPENGL_ES2_BIT as c_int); - } - (_, _) => unreachable!(), - }; - - if let Some(hardware_accelerated) = reqs.hardware_accelerated { - out.push(ffi::egl::CONFIG_CAVEAT as c_int); - out.push(if hardware_accelerated { - trace!(log, "Setting CONFIG_CAVEAT to NONE"); - ffi::egl::NONE as c_int - } else { - trace!(log, "Setting CONFIG_CAVEAT to SLOW_CONFIG"); - ffi::egl::SLOW_CONFIG as c_int - }); - } - - if let Some(color) = reqs.color_bits { - trace!(log, "Setting RED_SIZE to {}", color / 3); - out.push(ffi::egl::RED_SIZE as c_int); - out.push((color / 3) as c_int); - trace!( - log, - "Setting GREEN_SIZE to {}", - color / 3 + if color % 3 != 0 { 1 } else { 0 } - ); - out.push(ffi::egl::GREEN_SIZE as c_int); - out.push((color / 3 + if color % 3 != 0 { 1 } else { 0 }) as c_int); - trace!( - log, - "Setting BLUE_SIZE to {}", - color / 3 + if color % 3 == 2 { 1 } else { 0 } - ); - out.push(ffi::egl::BLUE_SIZE as c_int); - out.push((color / 3 + if color % 3 == 2 { 1 } else { 0 }) as c_int); - } - - if let Some(alpha) = reqs.alpha_bits { - trace!(log, "Setting ALPHA_SIZE to {}", alpha); - out.push(ffi::egl::ALPHA_SIZE as c_int); - out.push(alpha as c_int); - } - - if let Some(depth) = reqs.depth_bits { - trace!(log, "Setting DEPTH_SIZE to {}", depth); - out.push(ffi::egl::DEPTH_SIZE as c_int); - out.push(depth as c_int); - } - - if let Some(stencil) = reqs.stencil_bits { - trace!(log, "Setting STENCIL_SIZE to {}", stencil); - out.push(ffi::egl::STENCIL_SIZE as c_int); - out.push(stencil as c_int); - } - - if let Some(multisampling) = reqs.multisampling { - trace!(log, "Setting SAMPLES to {}", multisampling); - out.push(ffi::egl::SAMPLES as c_int); - out.push(multisampling as c_int); - } - - if reqs.stereoscopy { - error!(log, "Stereoscopy is currently unsupported (sorry!)"); - bail!(ErrorKind::NoAvailablePixelFormat); - } - - out.push(ffi::egl::NONE as c_int); - out - }; - - // calling `eglChooseConfig` - let mut config_id = mem::uninitialized(); - let mut num_configs = mem::uninitialized(); - if ffi::egl::ChooseConfig( - display, - descriptor.as_ptr(), - &mut config_id, - 1, - &mut num_configs, - ) == 0 - { - bail!(ErrorKind::ConfigFailed); - } - if num_configs == 0 { - error!(log, "No matching color format found"); - bail!(ErrorKind::NoAvailablePixelFormat); - } - - // analyzing each config - macro_rules! attrib { - ($display:expr, $config:expr, $attr:expr) => ( - { - let mut value = mem::uninitialized(); - let res = ffi::egl::GetConfigAttrib($display, $config, - $attr as ffi::egl::types::EGLint, &mut value); - if res == 0 { - bail!(ErrorKind::ConfigFailed); - } - value - } - ) - }; - - let desc = PixelFormat { - hardware_accelerated: attrib!(display, config_id, ffi::egl::CONFIG_CAVEAT) - != ffi::egl::SLOW_CONFIG as i32, - color_bits: attrib!(display, config_id, ffi::egl::RED_SIZE) as u8 - + attrib!(display, config_id, ffi::egl::BLUE_SIZE) as u8 - + attrib!(display, config_id, ffi::egl::GREEN_SIZE) as u8, - alpha_bits: attrib!(display, config_id, ffi::egl::ALPHA_SIZE) as u8, - depth_bits: attrib!(display, config_id, ffi::egl::DEPTH_SIZE) as u8, - stencil_bits: attrib!(display, config_id, ffi::egl::STENCIL_SIZE) as u8, - stereoscopy: false, - double_buffer: true, - multisampling: match attrib!(display, config_id, ffi::egl::SAMPLES) { - 0 | 1 => None, - a => Some(a as u16), - }, - srgb: false, // TODO: use EGL_KHR_gl_colorspace to know that - }; - - info!(log, "Selected color format: {:?}", desc); - - let mut context_attributes = Vec::with_capacity(10); - - if egl_version >= (1, 5) || extensions.iter().any(|s| *s == "EGL_KHR_create_context") { - trace!(log, "Setting CONTEXT_MAJOR_VERSION to {}", version.0); - context_attributes.push(ffi::egl::CONTEXT_MAJOR_VERSION as i32); - context_attributes.push(version.0 as i32); - trace!(log, "Setting CONTEXT_MINOR_VERSION to {}", version.1); - context_attributes.push(ffi::egl::CONTEXT_MINOR_VERSION as i32); - context_attributes.push(version.1 as i32); - - if attributes.debug && egl_version >= (1, 5) { - trace!(log, "Setting CONTEXT_OPENGL_DEBUG to TRUE"); - context_attributes.push(ffi::egl::CONTEXT_OPENGL_DEBUG as i32); - context_attributes.push(ffi::egl::TRUE as i32); - } - - context_attributes.push(ffi::egl::CONTEXT_FLAGS_KHR as i32); - context_attributes.push(0); - } else if egl_version >= (1, 3) { - trace!(log, "Setting CONTEXT_CLIENT_VERSION to {}", version.0); - context_attributes.push(ffi::egl::CONTEXT_CLIENT_VERSION as i32); - context_attributes.push(version.0 as i32); - } - - context_attributes.push(ffi::egl::NONE as i32); - - trace!(log, "Creating EGL context..."); - let context = ffi::egl::CreateContext(display, config_id, ptr::null(), context_attributes.as_ptr()); - - if context.is_null() { - match ffi::egl::GetError() as u32 { - ffi::egl::BAD_ATTRIBUTE => bail!(ErrorKind::CreationFailed), - err_no => bail!(ErrorKind::Unknown(err_no)), - } - } - debug!(log, "EGL context successfully created"); - - let surface_attributes = { - let mut out: Vec = Vec::with_capacity(3); - - match reqs.double_buffer { - Some(true) => { - trace!(log, "Setting RENDER_BUFFER to BACK_BUFFER"); - out.push(ffi::egl::RENDER_BUFFER as c_int); - out.push(ffi::egl::BACK_BUFFER as c_int); - } - Some(false) => { - trace!(log, "Setting RENDER_BUFFER to SINGLE_BUFFER"); - out.push(ffi::egl::RENDER_BUFFER as c_int); - out.push(ffi::egl::SINGLE_BUFFER as c_int); - } - None => {} - } - - out.push(ffi::egl::NONE as i32); - out - }; - - info!(log, "EGL context created"); - - Ok(EGLContext { - context: context as *const _, - display: display as *const _, - config_id: config_id, - surface_attributes: surface_attributes, - pixel_format: desc, - backend_type: match native { - NativeDisplayPtr::X11(_) => NativeType::X11, - NativeDisplayPtr::Wayland(_) => NativeType::Wayland, - NativeDisplayPtr::Gbm(_) => NativeType::Gbm, - }, - wl_drm_support: extensions.iter().any(|s| *s == "EGL_WL_bind_wayland_display"), - logger: log, - _lifetime: PhantomData, - _type: PhantomData, - }) - } - - /// Creates a surface bound to the given egl context for rendering - pub fn create_surface<'b>(&'a self, native: &'b T) -> Result> { - trace!(self.logger, "Creating EGL window surface..."); - - let (surface, keep) = native.surface(self.backend_type)?; - - let egl_surface = unsafe { - ffi::egl::CreateWindowSurface( - self.display, - self.config_id, - match surface { - NativeSurfacePtr::X11(ptr) - | NativeSurfacePtr::Wayland(ptr) - | NativeSurfacePtr::Gbm(ptr) => ptr, - }, - self.surface_attributes.as_ptr(), - ) - }; - - if egl_surface.is_null() { - bail!(ErrorKind::SurfaceCreationFailed); - } - - debug!(self.logger, "EGL surface successfully created"); - - Ok(EGLSurface { - context: self, - surface: egl_surface, - keep, - _lifetime_surface: PhantomData, - }) - } - - /// Returns the address of an OpenGL function. - /// - /// Supposes that the context has been made current before this function is called. - pub unsafe fn get_proc_address(&self, symbol: &str) -> *const c_void { - let addr = CString::new(symbol.as_bytes()).unwrap(); - let addr = addr.as_ptr(); - ffi::egl::GetProcAddress(addr) as *const _ - } - - /// Returns true if the OpenGL context is the current one in the thread. - pub fn is_current(&self) -> bool { - unsafe { ffi::egl::GetCurrentContext() == self.context as *const _ } - } - - /// Returns the pixel format of the main framebuffer of the context. - pub fn get_pixel_format(&self) -> PixelFormat { - self.pixel_format - } - - /// Binds this EGL context to the given Wayland display. - /// - /// This will allow clients to utilize EGL to create hardware-accelerated - /// surfaces. The server will need to be able to handle egl-wl_buffers. - /// See the `wayland::drm` module. - /// - /// ## Errors - /// - /// This might return `WlExtensionNotSupported` if binding is not supported - /// by the EGL implementation. - /// - /// This might return `OtherEGLDisplayAlreadyBound` if called for the same - /// `Display` multiple times, as only one context may be bound at any given time. - pub fn bind_wl_display(&self, display: &Display) -> Result<()> { - if !self.wl_drm_support { - bail!(ErrorKind::WlExtensionNotSupported); - } - let res = ffi::egl::BindWaylandDisplayWL(self.display, display.ptr()); - if res == 0 { - bail!(ErrorKind::OtherEGLDisplayAlreadyBound); - } - Ok(()) - } - - /// Unbinds this EGL context from the given Wayland display. - /// - /// This will stop clients from using previously available extensions - /// to utilize hardware-accelerated surface via EGL. - /// - /// ## Errors - /// - /// This might return `WlExtensionNotSupported` if binding is not supported - /// by the EGL implementation. - /// - /// This might return `OtherEGLDisplayAlreadyBound` if called for the same - /// `Display` multiple times, as only one context may be bound at any given time. - pub fn unbind_wl_display(&self, display: &Display) -> Result<()> { - if !self.wl_drm_support { - bail!(ErrorKind::WlExtensionNotSupported); - } - let res = ffi::egl::UnbindWaylandDisplayWL(self.display, display.ptr()); - if res == 0 { - bail!(ErrorKind::NoEGLDisplayBound); - } - Ok(()) - } - - pub fn egl_image_to_texture(&self, image: ffi::EGLImage, tex_id: c_uint) { - ffi::gl::EGLImageTargetTexture2DOES(tex_id, image); - } -} - -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 - ffi::egl::DestroyContext(self.display as *const _, self.context as *const _); - ffi::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) -> ::std::result::Result<(), SwapBuffersError> { - let ret = unsafe { - ffi::egl::SwapBuffers(self.context.display as *const _, self.surface as *const _) - }; - - if ret == 0 { - match unsafe { ffi::egl::GetError() } as u32 { - ffi::egl::CONTEXT_LOST => Err(SwapBuffersError::ContextLost), - err => Err(SwapBuffersError::Unknown(err)), - } - } else { - Ok(()) - } - } - - /// Makes the OpenGL context the current context in the current thread. - /// - /// # Unsafety - /// - /// This function is marked unsafe, because the context cannot be made current - /// on multiple threads. - pub unsafe fn make_current(&self) -> ::std::result::Result<(), SwapBuffersError> { - let ret = ffi::egl::MakeCurrent( - self.context.display as *const _, - self.surface as *const _, - self.surface as *const _, - self.context.context as *const _, - ); - - if ret == 0 { - match ffi::egl::GetError() as u32 { - ffi::egl::CONTEXT_LOST => Err(SwapBuffersError::ContextLost), - err => panic!("eglMakeCurrent failed (eglGetError returned 0x{:x})", err), - } - } else { - Ok(()) - } - } - - /// Returns true if the OpenGL surface is the current one in the thread. - pub fn is_current(&self) -> bool { - unsafe { - self.context.egl.GetCurrentSurface(ffi::egl::DRAW as _) == self.surface as *const _ - && self.context.egl.GetCurrentSurface(ffi::egl::READ as _) == self.surface as *const _ - } - } -} - -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<'a, 'b, T: NativeSurface> Drop for EGLSurface<'a, 'b, T> { - fn drop(&mut self) { - unsafe { - ffi::egl::DestroySurface(self.context.display as *const _, self.surface as *const _); - } - } -} - -/// Error that can happen when swapping buffers. -#[derive(Debug, Clone)] -pub enum SwapBuffersError { - /// The OpenGL context has been lost and needs to be recreated. - /// - /// All the objects associated to it (textures, buffers, programs, etc.) - /// need to be recreated from scratch. - /// - /// Operations will have no effect. Functions that read textures, buffers, etc. - /// from OpenGL will return uninitialized data instead. - /// - /// A context loss usually happens on mobile devices when the user puts the - /// application on sleep and wakes it up later. However any OpenGL implementation - /// can theoretically lose the context at any time. - ContextLost, - /// The buffers have already been swapped. - /// - /// This error can be returned when `swap_buffers` has been called multiple times - /// without any modification in between. - AlreadySwapped, - /// Unknown GL error - Unknown(u32), -} - -impl fmt::Display for SwapBuffersError { - fn fmt(&self, formatter: &mut fmt::Formatter) -> ::std::result::Result<(), fmt::Error> { - use std::error::Error; - write!(formatter, "{}", self.description()) - } -} - -impl error::Error for SwapBuffersError { - fn description(&self) -> &str { - match *self { - SwapBuffersError::ContextLost => "The context has been lost, it needs to be recreated", - SwapBuffersError::AlreadySwapped => { - "Buffers are already swapped, swap_buffers was called too many times" - } - SwapBuffersError::Unknown(_) => "Unknown Open GL error occurred", - } - } - - fn cause(&self) -> Option<&error::Error> { - None - } -} - -/// Attributes to use when creating an OpenGL context. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct GlAttributes { - /// Describes the OpenGL API and version that are being requested when a context is created. - /// - /// `Some(3, 0)` will request a OpenGL ES 3.0 context for example. - /// `None` means "don't care" (minimum will be 2.0). - pub version: Option<(u8, u8)>, - /// OpenGL profile to use - pub profile: Option, - /// Whether to enable the debug flag of the context. - /// - /// Debug contexts are usually slower but give better error reporting. - pub debug: bool, - /// Whether to use vsync. If vsync is enabled, calling swap_buffers will block until the screen refreshes. - /// This is typically used to prevent screen tearing. - pub vsync: bool, -} - -/// Describes the requested OpenGL context profiles. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum GlProfile { - /// Include all the immediate more functions and definitions. - Compatibility, - /// Include all the future-compatible functions and definitions. - Core, -} - -/// Describes how the backend should choose a pixel format. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] -pub struct PixelFormatRequirements { - /// If `true`, only hardware-accelerated formats will be conisdered. If `false`, only software renderers. - /// `None` means "don't care". Default is `None`. - pub hardware_accelerated: Option, - /// Minimum number of bits for the color buffer, excluding alpha. None means "don't care". The default is `None``. - pub color_bits: Option, - /// If `true`, the color buffer must be in a floating point format. Default is `false`. - /// - /// Using floating points allows you to write values outside of the `[0.0, 1.0]` range. - pub float_color_buffer: bool, - /// Minimum number of bits for the alpha in the color buffer. `None` means "don't care". The default is `None`. - pub alpha_bits: Option, - /// Minimum number of bits for the depth buffer. `None` means "don't care". The default value is `None`. - pub depth_bits: Option, - /// Minimum number of bits for the depth buffer. `None` means "don't care". The default value is `None`. - pub stencil_bits: Option, - /// If `true`, only double-buffered formats will be considered. If `false`, only single-buffer formats. - /// `None` means "don't care". The default is `None`. - pub double_buffer: Option, - /// Contains the minimum number of samples per pixel in the color, depth and stencil buffers. - /// `None` means "don't care". Default is `None`. A value of `Some(0)` indicates that multisampling must not be enabled. - pub multisampling: Option, - /// If `true`, only stereoscopic formats will be considered. If `false`, only non-stereoscopic formats. - /// The default is `false`. - pub stereoscopy: bool, -} - -/// Describes the pixel format of the main framebuffer -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct PixelFormat { - /// is the format hardware accelerated - pub hardware_accelerated: bool, - /// number of bits used for colors - pub color_bits: u8, - /// number of bits used for alpha channel - pub alpha_bits: u8, - /// number of bits used for depth channel - pub depth_bits: u8, - /// number of bits used for stencil buffer - pub stencil_bits: u8, - /// is stereoscopy enabled - pub stereoscopy: bool, - /// is double buffering enabled - pub double_buffer: bool, - /// number of samples used for multisampling if enabled - pub multisampling: Option, - /// is srgb enabled - pub srgb: bool, -} - -/// Trait that describes objects that have an OpenGL context -/// and can be used to render upon -pub trait EGLGraphicsBackend: GraphicsBackend { - /// Swaps buffers at the end of a frame. - fn swap_buffers(&self) -> ::std::result::Result<(), SwapBuffersError>; - - /// Returns the address of an OpenGL function. - /// - /// Supposes that the context has been made current before this function is called. - unsafe fn get_proc_address(&self, symbol: &str) -> *const c_void; - - /// Returns the dimensions of the window, or screen, etc in points. - /// - /// That are the scaled pixels of the underlying graphics backend. - /// For nested compositors this will respect the scaling of the root compositor. - /// For drawing directly onto hardware this unit will be equal to actual pixels. - fn get_framebuffer_dimensions(&self) -> (u32, u32); - - /// Returns true if the OpenGL context is the current one in the thread. - fn is_current(&self) -> bool; - - /// Makes the OpenGL context the current context in the current thread. - /// - /// # Unsafety - /// - /// This function is marked unsafe, because the context cannot be made current - /// on multiple threads. - unsafe fn make_current(&self) -> ::std::result::Result<(), SwapBuffersError>; - - /// Returns the pixel format of the main framebuffer of the context. - fn get_pixel_format(&self) -> PixelFormat; -} diff --git a/src/backend/graphics/egl/context.rs b/src/backend/graphics/egl/context.rs new file mode 100644 index 0000000..236c132 --- /dev/null +++ b/src/backend/graphics/egl/context.rs @@ -0,0 +1,622 @@ +use super::{ffi, EGLSurface, PixelFormat}; +use super::error::*; +use super::native; + +#[cfg(feature = "backend_drm")] +use gbm::{Device as GbmDevice}; +#[cfg(feature = "backend_drm")] +use drm::{Device as BasicDevice}; +#[cfg(feature = "backend_drm")] +use drm::control::{Device as ControlDevice}; +use nix::libc::{c_void, c_int}; +use slog; +use std::ffi::{CString, CStr}; +use std::marker::PhantomData; +use std::rc::Rc; +use std::ptr; +use std::mem; +use std::ops::{Deref, DerefMut}; +#[cfg(feature = "backend_drm")] +use std::os::unix::io::{AsRawFd, RawFd}; +use wayland_server::Display; + +/// EGL context for rendering +pub struct EGLContext> { + native: N, + pub(crate) context: Rc, + pub(crate) display: Rc, + pub(crate) config_id: ffi::egl::types::EGLConfig, + pub(crate) surface_attributes: Vec, + pixel_format: PixelFormat, + wl_drm_support: bool, + egl_to_texture_support: bool, + logger: slog::Logger, + _backend: PhantomData, +} + +impl> Deref for EGLContext { + type Target = N; + fn deref(&self) -> &N { + &self.native + } +} + +impl> DerefMut for EGLContext { + fn deref_mut(&mut self) -> &mut N { + &mut self.native + } +} + +impl> EGLContext { + pub fn new( + native: N, + attributes: GlAttributes, + reqs: PixelFormatRequirements, + logger: L + ) -> Result> + where + L: Into> + { + let log = ::slog_or_stdlog(logger.into()).new(o!("smithay_module" => "renderer_egl")); + let ptr = native.ptr()?; + let ( + context, + display, + config_id, + surface_attributes, + pixel_format, + wl_drm_support, + egl_to_texture_support, + ) = unsafe { EGLContext::::new_internal(ptr, attributes, reqs, log.clone()) }?; + + Ok(EGLContext { + native, + context, + display, + config_id, + surface_attributes, + pixel_format, + wl_drm_support, + egl_to_texture_support, + logger: log, + _backend: PhantomData, + }) + } + + unsafe fn new_internal( + ptr: ffi::NativeDisplayType, + mut attributes: GlAttributes, + reqs: PixelFormatRequirements, + log: ::slog::Logger, + ) -> Result<( + Rc, + Rc, + ffi::egl::types::EGLConfig, + Vec, + PixelFormat, + bool, + bool, + )> + { + // If no version is given, try OpenGLES 3.0, if available, + // fallback to 2.0 otherwise + let version = match attributes.version { + Some((3, x)) => (3, x), + Some((2, x)) => (2, x), + None => { + debug!(log, "Trying to initialize EGL with OpenGLES 3.0"); + attributes.version = Some((3, 0)); + match EGLContext::::new_internal(ptr, 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_internal(ptr, attributes, reqs, log); + } + } + } + Some((1, x)) => { + error!( + log, + "OpenGLES 1.* is not supported by the EGL renderer backend" + ); + bail!(ErrorKind::OpenGlVersionNotSupported((1, x))); + } + Some(version) => { + error!( + log, + "OpenGLES {:?} is unknown and not supported by the EGL renderer backend", version + ); + bail!(ErrorKind::OpenGlVersionNotSupported(version)); + } + }; + + ffi::egl::LOAD.call_once(|| { + ffi::egl::load_with(|sym| { + let name = CString::new(sym).unwrap(); + let symbol = ffi::egl::LIB.get::<*mut c_void>(name.as_bytes()); + match symbol { + Ok(x) => *x as *const _, + Err(_) => ptr::null(), + } + }); + ffi::gl::load_with(|sym| { + let name = CString::new(sym).unwrap(); + let symbol = ffi::egl::LIB.get::<*mut c_void>(name.as_bytes()); + match symbol { + Ok(x) => *x as *const _, + Err(_) => ptr::null(), + } + }); + }); + + // the first step is to query the list of extensions without any display, if supported + let dp_extensions = { + let p = ffi::egl::QueryString(ffi::egl::NO_DISPLAY, ffi::egl::EXTENSIONS as i32); + + // this possibility is available only with EGL 1.5 or EGL_EXT_platform_base, otherwise + // `eglQueryString` returns an error + if p.is_null() { + vec![] + } else { + let p = CStr::from_ptr(p); + let list = String::from_utf8(p.to_bytes().to_vec()).unwrap_or_else(|_| String::new()); + list.split(' ').map(|e| e.to_string()).collect::>() + } + }; + + debug!(log, "EGL No-Display Extensions: {:?}", dp_extensions); + + let display = B::get_display(ptr, |e: &str| dp_extensions.iter().any(|s| s == e), log.clone()); + 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 ffi::egl::Initialize(display, &mut major, &mut minor) == 0 { + bail!(ErrorKind::InitFailed); + } + + info!(log, "EGL Initialized"); + info!(log, "EGL Version: {:?}", (major, minor)); + + (major, minor) + }; + + // the list of extensions supported by the client once initialized is different from the + // list of extensions obtained earlier + let extensions = if egl_version >= (1, 2) { + let p = CStr::from_ptr(ffi::egl::QueryString(display, ffi::egl::EXTENSIONS as i32)); + let list = String::from_utf8(p.to_bytes().to_vec()).unwrap_or_else(|_| String::new()); + list.split(' ').map(|e| e.to_string()).collect::>() + } else { + vec![] + }; + + info!(log, "EGL Extensions: {:?}", extensions); + + if egl_version >= (1, 2) && ffi::egl::BindAPI(ffi::egl::OPENGL_ES_API) == 0 { + error!( + log, + "OpenGLES not supported by the underlying EGL implementation" + ); + bail!(ErrorKind::OpenGlesNotSupported); + } + + let descriptor = { + let mut out: Vec = Vec::with_capacity(37); + + if egl_version >= (1, 2) { + trace!(log, "Setting COLOR_BUFFER_TYPE to RGB_BUFFER"); + out.push(ffi::egl::COLOR_BUFFER_TYPE as c_int); + out.push(ffi::egl::RGB_BUFFER as c_int); + } + + trace!(log, "Setting SURFACE_TYPE to WINDOW"); + + out.push(ffi::egl::SURFACE_TYPE as c_int); + // TODO: Some versions of Mesa report a BAD_ATTRIBUTE error + // if we ask for PBUFFER_BIT as well as WINDOW_BIT + out.push((ffi::egl::WINDOW_BIT) as c_int); + + match version { + (3, _) => { + if egl_version < (1, 3) { + error!( + log, + "OpenglES 3.* is not supported on EGL Versions lower then 1.3" + ); + bail!(ErrorKind::NoAvailablePixelFormat); + } + trace!(log, "Setting RENDERABLE_TYPE to OPENGL_ES3"); + out.push(ffi::egl::RENDERABLE_TYPE as c_int); + out.push(ffi::egl::OPENGL_ES3_BIT as c_int); + trace!(log, "Setting CONFORMANT to OPENGL_ES3"); + out.push(ffi::egl::CONFORMANT as c_int); + out.push(ffi::egl::OPENGL_ES3_BIT as c_int); + } + (2, _) => { + if egl_version < (1, 3) { + error!( + log, + "OpenglES 2.* is not supported on EGL Versions lower then 1.3" + ); + bail!(ErrorKind::NoAvailablePixelFormat); + } + trace!(log, "Setting RENDERABLE_TYPE to OPENGL_ES2"); + out.push(ffi::egl::RENDERABLE_TYPE as c_int); + out.push(ffi::egl::OPENGL_ES2_BIT as c_int); + trace!(log, "Setting CONFORMANT to OPENGL_ES2"); + out.push(ffi::egl::CONFORMANT as c_int); + out.push(ffi::egl::OPENGL_ES2_BIT as c_int); + } + (_, _) => unreachable!(), + }; + + if let Some(hardware_accelerated) = reqs.hardware_accelerated { + out.push(ffi::egl::CONFIG_CAVEAT as c_int); + out.push(if hardware_accelerated { + trace!(log, "Setting CONFIG_CAVEAT to NONE"); + ffi::egl::NONE as c_int + } else { + trace!(log, "Setting CONFIG_CAVEAT to SLOW_CONFIG"); + ffi::egl::SLOW_CONFIG as c_int + }); + } + + if let Some(color) = reqs.color_bits { + trace!(log, "Setting RED_SIZE to {}", color / 3); + out.push(ffi::egl::RED_SIZE as c_int); + out.push((color / 3) as c_int); + trace!( + log, + "Setting GREEN_SIZE to {}", + color / 3 + if color % 3 != 0 { 1 } else { 0 } + ); + out.push(ffi::egl::GREEN_SIZE as c_int); + out.push((color / 3 + if color % 3 != 0 { 1 } else { 0 }) as c_int); + trace!( + log, + "Setting BLUE_SIZE to {}", + color / 3 + if color % 3 == 2 { 1 } else { 0 } + ); + out.push(ffi::egl::BLUE_SIZE as c_int); + out.push((color / 3 + if color % 3 == 2 { 1 } else { 0 }) as c_int); + } + + if let Some(alpha) = reqs.alpha_bits { + trace!(log, "Setting ALPHA_SIZE to {}", alpha); + out.push(ffi::egl::ALPHA_SIZE as c_int); + out.push(alpha as c_int); + } + + if let Some(depth) = reqs.depth_bits { + trace!(log, "Setting DEPTH_SIZE to {}", depth); + out.push(ffi::egl::DEPTH_SIZE as c_int); + out.push(depth as c_int); + } + + if let Some(stencil) = reqs.stencil_bits { + trace!(log, "Setting STENCIL_SIZE to {}", stencil); + out.push(ffi::egl::STENCIL_SIZE as c_int); + out.push(stencil as c_int); + } + + if let Some(multisampling) = reqs.multisampling { + trace!(log, "Setting SAMPLES to {}", multisampling); + out.push(ffi::egl::SAMPLES as c_int); + out.push(multisampling as c_int); + } + + if reqs.stereoscopy { + error!(log, "Stereoscopy is currently unsupported (sorry!)"); + bail!(ErrorKind::NoAvailablePixelFormat); + } + + out.push(ffi::egl::NONE as c_int); + out + }; + + // calling `eglChooseConfig` + let mut config_id = mem::uninitialized(); + let mut num_configs = mem::uninitialized(); + if ffi::egl::ChooseConfig( + display, + descriptor.as_ptr(), + &mut config_id, + 1, + &mut num_configs, + ) == 0 + { + bail!(ErrorKind::ConfigFailed); + } + if num_configs == 0 { + error!(log, "No matching color format found"); + bail!(ErrorKind::NoAvailablePixelFormat); + } + + // analyzing each config + macro_rules! attrib { + ($display:expr, $config:expr, $attr:expr) => ( + { + let mut value = mem::uninitialized(); + let res = ffi::egl::GetConfigAttrib($display, $config, + $attr as ffi::egl::types::EGLint, &mut value); + if res == 0 { + bail!(ErrorKind::ConfigFailed); + } + value + } + ) + }; + + let desc = PixelFormat { + hardware_accelerated: attrib!(display, config_id, ffi::egl::CONFIG_CAVEAT) + != ffi::egl::SLOW_CONFIG as i32, + color_bits: attrib!(display, config_id, ffi::egl::RED_SIZE) as u8 + + attrib!(display, config_id, ffi::egl::BLUE_SIZE) as u8 + + attrib!(display, config_id, ffi::egl::GREEN_SIZE) as u8, + alpha_bits: attrib!(display, config_id, ffi::egl::ALPHA_SIZE) as u8, + depth_bits: attrib!(display, config_id, ffi::egl::DEPTH_SIZE) as u8, + stencil_bits: attrib!(display, config_id, ffi::egl::STENCIL_SIZE) as u8, + stereoscopy: false, + double_buffer: true, + multisampling: match attrib!(display, config_id, ffi::egl::SAMPLES) { + 0 | 1 => None, + a => Some(a as u16), + }, + srgb: false, // TODO: use EGL_KHR_gl_colorspace to know that + }; + + info!(log, "Selected color format: {:?}", desc); + + let mut context_attributes = Vec::with_capacity(10); + + if egl_version >= (1, 5) || extensions.iter().any(|s| *s == "EGL_KHR_create_context") { + trace!(log, "Setting CONTEXT_MAJOR_VERSION to {}", version.0); + context_attributes.push(ffi::egl::CONTEXT_MAJOR_VERSION as i32); + context_attributes.push(version.0 as i32); + trace!(log, "Setting CONTEXT_MINOR_VERSION to {}", version.1); + context_attributes.push(ffi::egl::CONTEXT_MINOR_VERSION as i32); + context_attributes.push(version.1 as i32); + + if attributes.debug && egl_version >= (1, 5) { + trace!(log, "Setting CONTEXT_OPENGL_DEBUG to TRUE"); + context_attributes.push(ffi::egl::CONTEXT_OPENGL_DEBUG as i32); + context_attributes.push(ffi::egl::TRUE as i32); + } + + context_attributes.push(ffi::egl::CONTEXT_FLAGS_KHR as i32); + context_attributes.push(0); + } else if egl_version >= (1, 3) { + trace!(log, "Setting CONTEXT_CLIENT_VERSION to {}", version.0); + context_attributes.push(ffi::egl::CONTEXT_CLIENT_VERSION as i32); + context_attributes.push(version.0 as i32); + } + + context_attributes.push(ffi::egl::NONE as i32); + + trace!(log, "Creating EGL context..."); + let context = ffi::egl::CreateContext(display, config_id, ptr::null(), context_attributes.as_ptr()); + + if context.is_null() { + match ffi::egl::GetError() as u32 { + ffi::egl::BAD_ATTRIBUTE => bail!(ErrorKind::CreationFailed), + err_no => bail!(ErrorKind::Unknown(err_no)), + } + } + debug!(log, "EGL context successfully created"); + + let surface_attributes = { + let mut out: Vec = Vec::with_capacity(3); + + match reqs.double_buffer { + Some(true) => { + trace!(log, "Setting RENDER_BUFFER to BACK_BUFFER"); + out.push(ffi::egl::RENDER_BUFFER as c_int); + out.push(ffi::egl::BACK_BUFFER as c_int); + } + Some(false) => { + trace!(log, "Setting RENDER_BUFFER to SINGLE_BUFFER"); + out.push(ffi::egl::RENDER_BUFFER as c_int); + out.push(ffi::egl::SINGLE_BUFFER as c_int); + } + None => {} + } + + out.push(ffi::egl::NONE as i32); + out + }; + + info!(log, "EGL context created"); + + Ok(( + Rc::new(context as *const _), + Rc::new(display as *const _), + config_id, + surface_attributes, + desc, + extensions.iter().any(|s| *s == "EGL_WL_bind_wayland_display"), + extensions.iter().any(|s| *s == "EGL_OES_image" || *s == "EGL_OES_image_base"), + )) + } + + /// Creates a surface for rendering + pub fn create_surface(&self, args: N::Arguments) -> Result> { + trace!(self.logger, "Creating EGL window surface."); + let res = EGLSurface::new(self, self.native.create_surface(args).chain_err(|| ErrorKind::SurfaceCreationFailed)?); + if res.is_ok() { + debug!(self.logger, "EGL surface successfully created"); + } + res + } + + /// Returns the address of an OpenGL function. + /// + /// Supposes that the context has been made current before this function is called. + pub unsafe fn get_proc_address(&self, symbol: &str) -> *const c_void { + let addr = CString::new(symbol.as_bytes()).unwrap(); + let addr = addr.as_ptr(); + ffi::egl::GetProcAddress(addr) as *const _ + } + + /// Returns true if the OpenGL context is the current one in the thread. + pub fn is_current(&self) -> bool { + unsafe { ffi::egl::GetCurrentContext() == (*self.context) as *const _ } + } + + /// Returns the pixel format of the main framebuffer of the context. + pub fn get_pixel_format(&self) -> PixelFormat { + self.pixel_format + } + + /// Binds this EGL context to the given Wayland display. + /// + /// This will allow clients to utilize EGL to create hardware-accelerated + /// surfaces. The server will need to be able to handle egl-wl_buffers. + /// See the `wayland::drm` module. + /// + /// ## Errors + /// + /// This might return `WlExtensionNotSupported` if binding is not supported + /// by the EGL implementation. + /// + /// This might return `OtherEGLDisplayAlreadyBound` if called for the same + /// `Display` multiple times, as only one context may be bound at any given time. + pub fn bind_wl_display(&self, display: &Display) -> Result<()> { + if !self.wl_drm_support { + bail!(ErrorKind::EglExtensionNotSupported(&["EGL_WL_bind_wayland_display"])); + } + let res = unsafe { ffi::egl::BindWaylandDisplayWL(*self.display, display.ptr() as *mut _) }; + if res == 0 { + bail!(ErrorKind::OtherEGLDisplayAlreadyBound); + } + Ok(()) + } + + /// Unbinds this EGL context from the given Wayland display. + /// + /// This will stop clients from using previously available extensions + /// to utilize hardware-accelerated surface via EGL. + /// + /// ## Errors + /// + /// This might return `WlExtensionNotSupported` if binding is not supported + /// by the EGL implementation. + /// + /// This might return `OtherEGLDisplayAlreadyBound` if called for the same + /// `Display` multiple times, as only one context may be bound at any given time. + pub fn unbind_wl_display(&self, display: &Display) -> Result<()> { + if !self.wl_drm_support { + bail!(ErrorKind::EglExtensionNotSupported(&["EGL_WL_bind_wayland_display"])); + } + let res = unsafe { ffi::egl::UnbindWaylandDisplayWL(*self.display, display.ptr() as *mut _) }; + if res == 0 { + bail!(ErrorKind::NoEGLDisplayBound); + } + Ok(()) + } + + /* + pub unsafe fn egl_image_to_texture(&self, image: ffi::egl::types::EGLImage, tex_id: c_uint) -> Result<()> { + if !self.egl_to_texture_support { + bail!(ErrorKind::EglExtensionNotSupported(&["EGL_OES_image", "EGL_OES_image_base"])); + } + ffi::gl::EGLImageTargetTexture2DOES(tex_id, image); + Ok(()) + } + + pub unsafe fn destroy_egl_image(&self, image: ffi::egl::types::EGLImage, tex_id: c_uint) -> Result<()> { + ffi::gl::DestroyImageKHR(self.display, image); + Ok(()) + } + */ +} + +unsafe impl + Send> Send for EGLContext {} +unsafe impl + Sync> Sync for EGLContext {} + +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 + ffi::egl::DestroyContext((*self.display) as *const _, (*self.context) as *const _); + ffi::egl::Terminate((*self.display) as *const _); + } + } +} + +#[cfg(feature = "backend_drm")] +impl AsRawFd for EGLContext, GbmDevice> { + fn as_raw_fd(&self) -> RawFd { + self.native.as_raw_fd() + } +} + +#[cfg(feature = "backend_drm")] +impl BasicDevice for EGLContext, GbmDevice> {} +#[cfg(feature = "backend_drm")] +impl ControlDevice for EGLContext, GbmDevice> {} + +/// Attributes to use when creating an OpenGL context. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct GlAttributes { + /// Describes the OpenGL API and version that are being requested when a context is created. + /// + /// `Some(3, 0)` will request a OpenGL ES 3.0 context for example. + /// `None` means "don't care" (minimum will be 2.0). + pub version: Option<(u8, u8)>, + /// OpenGL profile to use + pub profile: Option, + /// Whether to enable the debug flag of the context. + /// + /// Debug contexts are usually slower but give better error reporting. + pub debug: bool, + /// Whether to use vsync. If vsync is enabled, calling swap_buffers will block until the screen refreshes. + /// This is typically used to prevent screen tearing. + pub vsync: bool, +} + +/// Describes the requested OpenGL context profiles. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum GlProfile { + /// Include all the immediate more functions and definitions. + Compatibility, + /// Include all the future-compatible functions and definitions. + Core, +} + +/// Describes how the backend should choose a pixel format. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +pub struct PixelFormatRequirements { + /// If `true`, only hardware-accelerated formats will be conisdered. If `false`, only software renderers. + /// `None` means "don't care". Default is `None`. + pub hardware_accelerated: Option, + /// Minimum number of bits for the color buffer, excluding alpha. None means "don't care". The default is `None``. + pub color_bits: Option, + /// If `true`, the color buffer must be in a floating point format. Default is `false`. + /// + /// Using floating points allows you to write values outside of the `[0.0, 1.0]` range. + pub float_color_buffer: bool, + /// Minimum number of bits for the alpha in the color buffer. `None` means "don't care". The default is `None`. + pub alpha_bits: Option, + /// Minimum number of bits for the depth buffer. `None` means "don't care". The default value is `None`. + pub depth_bits: Option, + /// Minimum number of bits for the depth buffer. `None` means "don't care". The default value is `None`. + pub stencil_bits: Option, + /// If `true`, only double-buffered formats will be considered. If `false`, only single-buffer formats. + /// `None` means "don't care". The default is `None`. + pub double_buffer: Option, + /// Contains the minimum number of samples per pixel in the color, depth and stencil buffers. + /// `None` means "don't care". Default is `None`. A value of `Some(0)` indicates that multisampling must not be enabled. + pub multisampling: Option, + /// If `true`, only stereoscopic formats will be considered. If `false`, only non-stereoscopic formats. + /// The default is `false`. + pub stereoscopy: bool, +} diff --git a/src/backend/graphics/egl/error.rs b/src/backend/graphics/egl/error.rs new file mode 100644 index 0000000..756ae56 --- /dev/null +++ b/src/backend/graphics/egl/error.rs @@ -0,0 +1,70 @@ +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), + } + + #[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 = "Backend does not match the context type"] + NonMatchingBackend(expected: &'static str) { + description("The expected backend did not match the runtime."), + display("The expected backend '{:?}' does not match the runtime.", expected), + } + + #[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 required EGL extension is not supported by the underlying EGL implementation"] + EglExtensionNotSupported(extensions: &'static [&'static str]) { + description("The required EGL extension is not supported by the underlying EGL implementation"), + display("None of the following EGL extensions is supported by the underlying EGL implementation, + at least one is required: {:?}", extensions) + } + + #[doc = "Only one EGLDisplay may be bound to a given WlDisplay at any time"] + OtherEGLDisplayAlreadyBound { + description("Only one EGLDisplay may be bound to a given WlDisplay at any time") + } + + #[doc = "No EGLDisplay is currently bound to this WlDisplay"] + NoEGLDisplayBound { + description("No EGLDisplay is currently bound to this WlDisplay") + } + + #[doc = "The reason of failure could not be determined"] + Unknown(err_no: u32) + } +} diff --git a/src/backend/graphics/egl/ffi.rs b/src/backend/graphics/egl/ffi.rs new file mode 100644 index 0000000..733a2b7 --- /dev/null +++ b/src/backend/graphics/egl/ffi.rs @@ -0,0 +1,144 @@ +#![allow(missing_docs)] + +pub use nix::libc::{c_long, c_void, c_uint, int32_t, uint64_t}; + +pub type khronos_utime_nanoseconds_t = khronos_uint64_t; +pub type khronos_uint64_t = uint64_t; +pub type khronos_ssize_t = c_long; +pub type EGLint = int32_t; +pub type EGLNativeDisplayType = NativeDisplayType; +pub type EGLNativePixmapType = NativePixmapType; +pub type EGLNativeWindowType = NativeWindowType; +pub type NativeDisplayType = *const c_void; +pub type NativePixmapType = *const c_void; +pub type NativeWindowType = *const c_void; + +pub mod gl { + include!(concat!(env!("OUT_DIR"), "/gl_bindings.rs")); +} + +pub mod egl { + use super::*; + use libloading::Library; + use std::sync::{Once, ONCE_INIT}; + + lazy_static! { + pub static ref LIB: Library = { + Library::new("libEGL.so.1").expect("Failed to load LibEGL") + }; + } + + pub static LOAD: Once = ONCE_INIT; + + include!(concat!(env!("OUT_DIR"), "/egl_bindings.rs")); + + /* + * `gl_generator` cannot generate bindings for the `EGL_WL_bind_wayland_display` extension. + * Lets do it ourselves... + */ + + #[allow(non_snake_case, unused_variables, dead_code)] #[inline] + pub unsafe fn BindWaylandDisplayWL(dpy: types::EGLDisplay, display: *mut __gl_imports::raw::c_void) -> types::EGLBoolean { + __gl_imports::mem::transmute::<_, extern "system" fn(types::EGLDisplay, *mut __gl_imports::raw::c_void) -> types::EGLBoolean>(wayland_storage::BindWaylandDisplayWL.f)(dpy, display) + } + + #[allow(non_snake_case, unused_variables, dead_code)] #[inline] + pub unsafe fn UnbindWaylandDisplayWL(dpy: types::EGLDisplay, display: *mut __gl_imports::raw::c_void) -> types::EGLBoolean { + __gl_imports::mem::transmute::<_, extern "system" fn(types::EGLDisplay, *mut __gl_imports::raw::c_void) -> types::EGLBoolean>(wayland_storage::UnbindWaylandDisplayWL.f)(dpy, display) + } + + #[allow(non_snake_case, unused_variables, dead_code)] #[inline] + pub unsafe fn QueryWaylandBufferWL(dpy: types::EGLDisplay, buffer: *mut __gl_imports::raw::c_void, attribute: types::EGLint, value: *mut types::EGLint) -> types::EGLBoolean { + __gl_imports::mem::transmute::<_, extern "system" fn(types::EGLDisplay, *mut __gl_imports::raw::c_void, types::EGLint, *mut types::EGLint) -> types::EGLBoolean>(wayland_storage::QueryWaylandBufferWL.f)(dpy, buffer, attribute, value) + } + + mod wayland_storage { + use super::__gl_imports::raw; + use super::FnPtr; + pub static mut BindWaylandDisplayWL: FnPtr = FnPtr { + f: super::missing_fn_panic as *const raw::c_void, + is_loaded: false + }; + pub static mut UnbindWaylandDisplayWL: FnPtr = FnPtr { + f: super::missing_fn_panic as *const raw::c_void, + is_loaded: false + }; + pub static mut QueryWaylandBufferWL: FnPtr = FnPtr { + f: super::missing_fn_panic as *const raw::c_void, + is_loaded: false + }; + } + + #[allow(non_snake_case)] + pub mod BindWaylandDisplayWL { + use super::{wayland_storage, metaloadfn}; + use super::__gl_imports::raw; + use super::FnPtr; + + #[inline] + #[allow(dead_code)] + pub fn is_loaded() -> bool { + unsafe { wayland_storage::BindWaylandDisplayWL.is_loaded } + } + + #[allow(dead_code)] + pub fn load_with(mut loadfn: F) where F: FnMut(&str) -> *const raw::c_void { + unsafe { + wayland_storage::BindWaylandDisplayWL = FnPtr::new(metaloadfn(&mut loadfn, "eglBindWaylandDisplayWL", &[])) + } + } + } + + #[allow(non_snake_case)] + pub mod UnbindWaylandDisplayWL { + use super::{wayland_storage, metaloadfn}; + use super::__gl_imports::raw; + use super::FnPtr; + + #[inline] + #[allow(dead_code)] + pub fn is_loaded() -> bool { + unsafe { wayland_storage::UnbindWaylandDisplayWL.is_loaded } + } + + #[allow(dead_code)] + pub fn load_with(mut loadfn: F) where F: FnMut(&str) -> *const raw::c_void { + unsafe { + wayland_storage::UnbindWaylandDisplayWL = FnPtr::new(metaloadfn(&mut loadfn, "eglUnbindWaylandDisplayWL", &[])) + } + } + } + + #[allow(non_snake_case)] + pub mod QueryWaylandBufferWL { + use super::{wayland_storage, metaloadfn}; + use super::__gl_imports::raw; + use super::FnPtr; + + #[inline] + #[allow(dead_code)] + pub fn is_loaded() -> bool { + unsafe { wayland_storage::QueryWaylandBufferWL.is_loaded } + } + + #[allow(dead_code)] + pub fn load_with(mut loadfn: F) where F: FnMut(&str) -> *const raw::c_void { + unsafe { + wayland_storage::QueryWaylandBufferWL = FnPtr::new(metaloadfn(&mut loadfn, "eglQueryWaylandBufferWL", &[])) + } + } + } + + // Accepted as in eglCreateImageKHR + pub const WAYLAND_BUFFER_WL: c_uint = 0x31D5; + // Accepted in the parameter of eglCreateImageKHR: + pub const WAYLAND_PLANE_WL: c_uint = 0x31D6; + // Possible values for EGL_TEXTURE_FORMAT: + pub const TEXTURE_Y_U_V_WL: int32_t = 0x31D7; + pub const TEXTURE_Y_UV_WL: int32_t = 0x31D8; + pub const TEXTURE_Y_XUXV_WL: int32_t = 0x31D9; + pub const TEXTURE_EXTERNAL_WL: int32_t = 0x31DA; + // Accepted in the parameter of eglQueryWaylandBufferWL: + pub const EGL_TEXTURE_FORMAT: int32_t = 0x3080; + pub const WAYLAND_Y_INVERTED_WL: int32_t = 0x31DB; +} diff --git a/src/backend/graphics/egl/mod.rs b/src/backend/graphics/egl/mod.rs new file mode 100644 index 0000000..c12608f --- /dev/null +++ b/src/backend/graphics/egl/mod.rs @@ -0,0 +1,184 @@ +//! Common traits and types for egl context creation and rendering + +/// Large parts of this module are taken from +/// https://github.com/tomaka/glutin/tree/044e651edf67a2029eecc650dd42546af1501414/src/api/egl/ +/// +/// It therefore falls under glutin's Apache 2.0 license +/// (see https://github.com/tomaka/glutin/tree/044e651edf67a2029eecc650dd42546af1501414/LICENSE) +use super::GraphicsBackend; +use nix::libc::c_void; +use std::fmt; +use wayland_server::Display; + +pub use self::ffi::egl::types::EGLImage; +pub mod context; +pub use self::context::EGLContext; +pub mod error; +#[allow(non_camel_case_types, dead_code, unused_mut, non_upper_case_globals)] +pub mod ffi; +pub mod native; +pub mod surface; +pub use self::surface::EGLSurface; + +/// Error that can happen when swapping buffers. +#[derive(Debug, Clone, PartialEq)] +pub enum SwapBuffersError { + /// The OpenGL context has been lost and needs to be recreated. + /// + /// All the objects associated to it (textures, buffers, programs, etc.) + /// need to be recreated from scratch. + /// + /// Operations will have no effect. Functions that read textures, buffers, etc. + /// from OpenGL will return uninitialized data instead. + /// + /// A context loss usually happens on mobile devices when the user puts the + /// application on sleep and wakes it up later. However any OpenGL implementation + /// can theoretically lose the context at any time. + ContextLost, + /// The buffers have already been swapped. + /// + /// This error can be returned when `swap_buffers` has been called multiple times + /// without any modification in between. + AlreadySwapped, + /// Unknown GL error + Unknown(u32), +} + +impl fmt::Display for SwapBuffersError { + fn fmt(&self, formatter: &mut fmt::Formatter) -> ::std::result::Result<(), fmt::Error> { + use std::error::Error; + write!(formatter, "{}", self.description()) + } +} + +impl ::std::error::Error for SwapBuffersError { + fn description(&self) -> &str { + match *self { + SwapBuffersError::ContextLost => "The context has been lost, it needs to be recreated", + SwapBuffersError::AlreadySwapped => { + "Buffers are already swapped, swap_buffers was called too many times" + } + SwapBuffersError::Unknown(_) => "Unknown Open GL error occurred", + } + } + + fn cause(&self) -> Option<&::std::error::Error> { + None + } +} + +/// Error that can happen on optional EGL features +#[derive(Debug, Clone, PartialEq)] +pub struct EglExtensionNotSupportedError(&'static [&'static str]); + +impl fmt::Display for EglExtensionNotSupportedError { + fn fmt(&self, formatter: &mut fmt::Formatter) -> ::std::result::Result<(), fmt::Error> { + write!(formatter, "None of the following EGL extensions is supported by the underlying EGL implementation, + at least one is required: {:?}", self.0) + } +} + +impl ::std::error::Error for EglExtensionNotSupportedError { + fn description(&self) -> &str { + "The required EGL extension is not supported by the underlying EGL implementation" + } + + fn cause(&self) -> Option<&::std::error::Error> { + None + } +} + +impl From for EglExtensionNotSupportedError { + fn from(error: error::Error) -> Self { + match *error.kind() { + error::ErrorKind::EglExtensionNotSupported(extensions) => EglExtensionNotSupportedError(extensions), + _ => panic!("Error not convertible"), + } + } +} + +/// Describes the pixel format of the main framebuffer +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct PixelFormat { + /// is the format hardware accelerated + pub hardware_accelerated: bool, + /// number of bits used for colors + pub color_bits: u8, + /// number of bits used for alpha channel + pub alpha_bits: u8, + /// number of bits used for depth channel + pub depth_bits: u8, + /// number of bits used for stencil buffer + pub stencil_bits: u8, + /// is stereoscopy enabled + pub stereoscopy: bool, + /// is double buffering enabled + pub double_buffer: bool, + /// number of samples used for multisampling if enabled + pub multisampling: Option, + /// is srgb enabled + pub srgb: bool, +} + +/// Trait that describes objects that have an OpenGL context +/// and can be used to render upon +pub trait EGLGraphicsBackend: GraphicsBackend { + /// Swaps buffers at the end of a frame. + fn swap_buffers(&self) -> ::std::result::Result<(), SwapBuffersError>; + + /// Returns the address of an OpenGL function. + /// + /// Supposes that the context has been made current before this function is called. + unsafe fn get_proc_address(&self, symbol: &str) -> *const c_void; + + /// Returns the dimensions of the window, or screen, etc in points. + /// + /// That are the scaled pixels of the underlying graphics backend. + /// For nested compositors this will respect the scaling of the root compositor. + /// For drawing directly onto hardware this unit will be equal to actual pixels. + fn get_framebuffer_dimensions(&self) -> (u32, u32); + + /// Returns true if the OpenGL context is the current one in the thread. + fn is_current(&self) -> bool; + + /// Makes the OpenGL context the current context in the current thread. + /// + /// # Unsafety + /// + /// This function is marked unsafe, because the context cannot be made current + /// on multiple threads. + unsafe fn make_current(&self) -> ::std::result::Result<(), SwapBuffersError>; + + /// Returns the pixel format of the main framebuffer of the context. + fn get_pixel_format(&self) -> PixelFormat; + + /// Binds this EGL context to the given Wayland display. + /// + /// This will allow clients to utilize EGL to create hardware-accelerated + /// surfaces. The server will need to be able to handle egl-wl_buffers. + /// See the `wayland::drm` module. + /// + /// ## Errors + /// + /// This might return `WlExtensionNotSupported` if binding is not supported + /// by the EGL implementation. + /// + /// This might return `OtherEGLDisplayAlreadyBound` if called for the same + /// `Display` multiple times, as only one context may be bound at any given time. + fn bind_wl_display(&self, display: &Display) -> ::std::result::Result<(), EglExtensionNotSupportedError>; + /// Unbinds this EGL context from the given Wayland display. + /// + /// This will stop clients from using previously available extensions + /// to utilize hardware-accelerated surface via EGL. + /// + /// ## Errors + /// + /// This might return `WlExtensionNotSupported` if binding is not supported + /// by the EGL implementation. + /// + /// This might return `OtherEGLDisplayAlreadyBound` if called for the same + /// `Display` multiple times, as only one context may be bound at any given time. + fn unbind_wl_display(&self, display: &Display) -> ::std::result::Result<(), EglExtensionNotSupportedError>; + + // unsafe fn egl_image_to_texture(&self, image: ffi::egl::types::EGLImage, tex_id: c_uint) -> ::std::result::Result<(), EglExtensionNotSupportedError>; +} diff --git a/src/backend/graphics/egl/native.rs b/src/backend/graphics/egl/native.rs new file mode 100644 index 0000000..bd24b4a --- /dev/null +++ b/src/backend/graphics/egl/native.rs @@ -0,0 +1,254 @@ +use super::ffi; +use super::error::*; + +#[cfg(feature = "backend_drm")] +use ::backend::drm::error::{Error as DrmError, ErrorKind as DrmErrorKind, Result as DrmResult}; +#[cfg(feature = "backend_drm")] +use gbm::{AsRaw, Device as GbmDevice, Format as GbmFormat, BufferObjectFlags, Surface as GbmSurface}; +#[cfg(feature = "backend_drm")] +use std::marker::PhantomData; +#[cfg(any(feature = "backend_drm", feature = "backend_winit"))] +use std::ptr; +#[cfg(feature = "backend_drm")] +use std::os::unix::io::AsRawFd; +#[cfg(feature = "backend_winit")] +use winit::Window as WinitWindow; +#[cfg(feature = "backend_winit")] +use winit::os::unix::WindowExt; +#[cfg(feature = "backend_winit")] +use nix::libc::c_void; +#[cfg(feature = "backend_winit")] +use wayland_client::egl as wegl; + +pub trait Backend { + type Surface: NativeSurface; + + unsafe fn get_display bool>( + display: ffi::NativeDisplayType, + has_dp_extension: F, + log: ::slog::Logger, + ) -> ffi::egl::types::EGLDisplay; +} + +#[cfg(feature = "backend_winit")] +pub enum Wayland {} +#[cfg(feature = "backend_winit")] +impl Backend for Wayland { + type Surface = wegl::WlEglSurface; + + unsafe fn get_display(display: ffi::NativeDisplayType, has_dp_extension: F, log: ::slog::Logger) + -> ffi::egl::types::EGLDisplay + where + F: Fn(&str) -> bool + { + if has_dp_extension("EGL_KHR_platform_wayland") + && ffi::egl::GetPlatformDisplay::is_loaded() + { + trace!(log, "EGL Display Initialization via EGL_KHR_platform_wayland"); + ffi::egl::GetPlatformDisplay( + ffi::egl::PLATFORM_WAYLAND_KHR, + display as *mut _, + ptr::null(), + ) + } else if has_dp_extension("EGL_EXT_platform_wayland") + && ffi::egl::GetPlatformDisplayEXT::is_loaded() + { + trace!(log, "EGL Display Initialization via EGL_EXT_platform_wayland"); + ffi::egl::GetPlatformDisplayEXT( + ffi::egl::PLATFORM_WAYLAND_EXT, + display as *mut _, + ptr::null(), + ) + } else { + trace!(log, "Default EGL Display Initialization via GetDisplay"); + ffi::egl::GetDisplay(display as *mut _) + } + } +} + +pub struct XlibWindow(*const c_void); +#[cfg(feature = "backend_winit")] +pub enum X11 {} +#[cfg(feature = "backend_winit")] +impl Backend for X11 { + type Surface = XlibWindow; + + unsafe fn get_display(display: ffi::NativeDisplayType, has_dp_extension: F, log: ::slog::Logger) + -> ffi::egl::types::EGLDisplay + where + F: Fn(&str) -> bool + { + if has_dp_extension("EGL_KHR_platform_x11") + && ffi::egl::GetPlatformDisplay::is_loaded() + { + trace!(log, "EGL Display Initialization via EGL_KHR_platform_x11"); + ffi::egl::GetPlatformDisplay(ffi::egl::PLATFORM_X11_KHR, display as *mut _, ptr::null()) + } else if has_dp_extension("EGL_EXT_platform_x11") + && ffi::egl::GetPlatformDisplayEXT::is_loaded() + { + trace!(log, "EGL Display Initialization via EGL_EXT_platform_x11"); + ffi::egl::GetPlatformDisplayEXT(ffi::egl::PLATFORM_X11_EXT, display as *mut _, ptr::null()) + } else { + trace!(log, "Default EGL Display Initialization via GetDisplay"); + ffi::egl::GetDisplay(display as *mut _) + } + } +} +#[cfg(feature = "backend_drm")] +pub struct Gbm { + _userdata: PhantomData, +} +#[cfg(feature = "backend_drm")] +impl Backend for Gbm { + type Surface = GbmSurface; + + unsafe fn get_display(display: ffi::NativeDisplayType, has_dp_extension: F, log: ::slog::Logger) + -> ffi::egl::types::EGLDisplay + where + F: Fn(&str) -> bool + { + if has_dp_extension("EGL_KHR_platform_gbm") + && ffi::egl::GetPlatformDisplay::is_loaded() + { + trace!(log, "EGL Display Initialization via EGL_KHR_platform_gbm"); + ffi::egl::GetPlatformDisplay(ffi::egl::PLATFORM_GBM_KHR, display as *mut _, ptr::null()) + } else if has_dp_extension("EGL_MESA_platform_gbm") + && ffi::egl::GetPlatformDisplayEXT::is_loaded() + { + trace!(log, "EGL Display Initialization via EGL_MESA_platform_gbm"); + ffi::egl::GetPlatformDisplayEXT(ffi::egl::PLATFORM_GBM_MESA, display as *mut _, ptr::null()) + } else if has_dp_extension("EGL_MESA_platform_gbm") + && ffi::egl::GetPlatformDisplay::is_loaded() + { + trace!(log, "EGL Display Initialization via EGL_MESA_platform_gbm"); + ffi::egl::GetPlatformDisplay(ffi::egl::PLATFORM_GBM_MESA, display as *mut _, ptr::null()) + } else { + trace!(log, "Default EGL Display Initialization via GetDisplay"); + ffi::egl::GetDisplay(display as *mut _) + } + } +} + +/// Trait for types returning Surfaces which can be used to initialize `EGLSurface`s +/// +/// ## Unsafety +/// +/// The returned `NativeDisplayType` must be valid for egl and there is no way to test that. +pub unsafe trait NativeDisplay { + type Arguments; + type Error: ::std::error::Error + Send + 'static; + /// Because one typ might implement multiple `Backend` this function must be called to check + /// if the expected `Backend` is used at runtime. + fn is_backend(&self) -> bool; + /// Return a raw pointer egl will accept for context creation. + fn ptr(&self) -> Result; + /// Create a surface + fn create_surface(&self, args: Self::Arguments) -> ::std::result::Result; +} + +#[cfg(feature = "backend_winit")] +unsafe impl NativeDisplay for WinitWindow { + type Arguments = (); + type Error = Error; + + fn is_backend(&self) -> bool { + self.get_xlib_display().is_some() + } + + fn ptr(&self) -> Result { + self.get_xlib_display() + .map(|ptr| ptr as *const _) + .ok_or(ErrorKind::NonMatchingBackend("X11").into()) + } + + fn create_surface(&self, _args: ()) -> Result { + self.get_xlib_window() + .map(|ptr| XlibWindow(ptr)) + .ok_or(ErrorKind::NonMatchingBackend("X11").into()) + } +} + +#[cfg(feature = "backend_winit")] +unsafe impl NativeDisplay for WinitWindow { + type Arguments = (); + type Error = Error; + + fn is_backend(&self) -> bool { + self.get_wayland_display().is_some() + } + + fn ptr(&self) -> Result { + self.get_wayland_display() + .map(|ptr| ptr as *const _) + .ok_or(ErrorKind::NonMatchingBackend("Wayland").into()) + } + + fn create_surface(&self, _args: ()) -> Result { + if let Some(surface) = self.get_wayland_surface() { + let (w, h) = self.get_inner_size().unwrap(); + Ok(unsafe { wegl::WlEglSurface::new_from_raw(surface as *mut _, w as i32, h as i32) }) + } else { + bail!(ErrorKind::NonMatchingBackend("Wayland")) + } + } +} + +#[cfg(feature = "backend_drm")] +/// Arguments necessary to construct a `GbmSurface` +pub struct GbmSurfaceArguments { + /// Size of the surface + pub size: (u32, u32), + /// Pixel format of the surface + pub format: GbmFormat, + /// Flags for surface creation + pub flags: BufferObjectFlags, +} + +#[cfg(feature = "backend_drm")] +unsafe impl NativeDisplay> for GbmDevice { + type Arguments = GbmSurfaceArguments; + type Error = DrmError; + + fn is_backend(&self) -> bool { true } + + fn ptr(&self) -> Result { + Ok(self.as_raw() as *const _) + } + + fn create_surface(&self, args: GbmSurfaceArguments) -> DrmResult> { + use backend::drm::error::ResultExt as DrmResultExt; + + DrmResultExt::chain_err(GbmDevice::create_surface( + self, + args.size.0, + args.size.1, + args.format, + args.flags, + ), || DrmErrorKind::GbmInitFailed) + } +} + +/// Trait for types returning valid surface pointers for initializing egl +/// +/// ## Unsafety +/// +/// The returned `NativeWindowType` must be valid for egl and there is no way to test that. +pub unsafe trait NativeSurface { + /// Return a raw pointer egl will accept for surface creation. + fn ptr(&self) -> ffi::NativeWindowType; +} + +#[cfg(feature = "backend_winit")] +unsafe impl NativeSurface for XlibWindow { + fn ptr(&self) -> ffi::NativeWindowType { self.0 } +} + +#[cfg(feature = "backend_winit")] +unsafe impl NativeSurface for wegl::WlEglSurface { + fn ptr(&self) -> ffi::NativeWindowType { self.ptr() as *const _ } +} + +#[cfg(feature = "backend_drm")] +unsafe impl NativeSurface for GbmSurface { + fn ptr(&self) -> ffi::NativeWindowType { self.as_raw() as *const _ } +} diff --git a/src/backend/graphics/egl/surface.rs b/src/backend/graphics/egl/surface.rs new file mode 100644 index 0000000..e8f45f8 --- /dev/null +++ b/src/backend/graphics/egl/surface.rs @@ -0,0 +1,125 @@ +use super::{EGLContext, SwapBuffersError}; +use super::error::*; +use super::ffi; +use super::native; + +use std::ops::{Deref, DerefMut}; +use std::rc::{Rc, Weak}; + +/// EGL surface of a given egl context for rendering +pub struct EGLSurface { + context: Weak, + display: Weak, + native: N, + surface: ffi::egl::types::EGLSurface, +} + +impl Deref for EGLSurface { + type Target = N; + fn deref(&self) -> &N { + &self.native + } +} + +impl DerefMut for EGLSurface { + fn deref_mut(&mut self) -> &mut N { + &mut self.native + } +} + +impl EGLSurface { + pub(crate) fn new, D: native::NativeDisplay>( + context: &EGLContext, + native: N, + ) -> Result> { + let surface = unsafe { + ffi::egl::CreateWindowSurface( + *context.display, + context.config_id, + native.ptr(), + context.surface_attributes.as_ptr(), + ) + }; + + if surface.is_null() { + bail!(ErrorKind::SurfaceCreationFailed); + } + + Ok(EGLSurface { + context: Rc::downgrade(&context.context), + display: Rc::downgrade(&context.display), + native, + surface, + }) + } + + /// Swaps buffers at the end of a frame. + pub fn swap_buffers(&self) -> ::std::result::Result<(), SwapBuffersError> { + if let Some(display) = self.display.upgrade() { + let ret = unsafe { + ffi::egl::SwapBuffers((*display) as *const _, self.surface as *const _) + }; + + if ret == 0 { + match unsafe { ffi::egl::GetError() } as u32 { + ffi::egl::CONTEXT_LOST => Err(SwapBuffersError::ContextLost), + err => Err(SwapBuffersError::Unknown(err)), + } + } else { + Ok(()) + } + } else { + Err(SwapBuffersError::ContextLost) + } + } + + /// Makes the OpenGL context the current context in the current thread. + /// + /// # Unsafety + /// + /// This function is marked unsafe, because the context cannot be made current + /// on multiple threads. + pub unsafe fn make_current(&self) -> ::std::result::Result<(), SwapBuffersError> { + if let (Some(display), Some(context)) = (self.display.upgrade(), self.context.upgrade()) { + let ret = ffi::egl::MakeCurrent( + (*display) as *const _, + self.surface as *const _, + self.surface as *const _, + (*context) as *const _, + ); + + if ret == 0 { + match ffi::egl::GetError() as u32 { + ffi::egl::CONTEXT_LOST => Err(SwapBuffersError::ContextLost), + err => panic!("eglMakeCurrent failed (eglGetError returned 0x{:x})", err), + } + } else { + Ok(()) + } + } else { + Err(SwapBuffersError::ContextLost) + } + } + + /// Returns true if the OpenGL surface is the current one in the thread. + pub fn is_current(&self) -> bool { + if self.context.upgrade().is_some() { + unsafe { + ffi::egl::GetCurrentSurface(ffi::egl::DRAW as _) == self.surface as *const _ + && ffi::egl::GetCurrentSurface(ffi::egl::READ as _) == self.surface as *const _ + } + } else { + false + } + } +} + +impl Drop for EGLSurface { + fn drop(&mut self) { + if let Some(display) = self.display.upgrade() { + unsafe { + ffi::egl::DestroySurface((*display) as *const _, self.surface as *const _); + } + } + } +} diff --git a/src/backend/udev.rs b/src/backend/udev.rs index a04b670..b151dfc 100644 --- a/src/backend/udev.rs +++ b/src/backend/udev.rs @@ -9,6 +9,8 @@ //! See also `examples/udev.rs` for pure hardware backed example of a compositor utilizing this //! backend. +use drm::Device as BasicDevice; +use drm::control::Device as ControlDevice; use backend::drm::{drm_device_bind, DrmBackend, DrmDevice, DrmHandler}; use backend::session::{Session, SessionObserver}; use nix::fcntl; @@ -18,28 +20,38 @@ use std::collections::HashMap; use std::ffi::OsString; use std::io::{Error as IoError, Result as IoResult}; use std::mem::drop; -use std::os::unix::io::AsRawFd; +use std::os::unix::io::{AsRawFd, RawFd}; use std::path::{Path, PathBuf}; use udev::{Context, Enumerator, Event, EventType, MonitorBuilder, MonitorSocket, Result as UdevResult}; use wayland_server::{EventLoopHandle, StateProxy, StateToken}; use wayland_server::sources::{FdEventSource, FdEventSourceImpl, FdInterest}; +pub struct SessionFdDrmDevice(RawFd); + +impl AsRawFd for SessionFdDrmDevice { + fn as_raw_fd(&self) -> RawFd { + self.0 + } +} +impl BasicDevice for SessionFdDrmDevice {} +impl ControlDevice for SessionFdDrmDevice {} + /// Graphical backend that monitors available drm devices. /// /// Provides a way to automatically initialize a `DrmDevice` for available gpus and notifies the /// given handler of any changes. Can be used to provide hot-plug functionality for gpus and /// attached monitors. pub struct UdevBackend< - B: Borrow + 'static, - H: DrmHandler + 'static, + B: Borrow> + 'static, + H: DrmHandler + 'static, S: Session + 'static, T: UdevHandler + 'static, > { devices: HashMap< dev_t, ( - StateToken>, - FdEventSource<(StateToken>, H)>, + StateToken>, + FdEventSource<(StateToken>, H)>, ), >, monitor: MonitorSocket, @@ -49,8 +61,8 @@ pub struct UdevBackend< } impl< - B: From + Borrow + 'static, - H: DrmHandler + 'static, + B: From> + Borrow> + 'static, + H: DrmHandler + 'static, S: Session + 'static, T: UdevHandler + 'static, > UdevBackend { @@ -75,10 +87,10 @@ impl< .into_iter() // Create devices .flat_map(|path| { - match unsafe { DrmDevice::new_from_fd( + match unsafe { DrmDevice::new( { match session.open(&path, fcntl::O_RDWR | fcntl::O_CLOEXEC | fcntl::O_NOCTTY | fcntl::O_NONBLOCK) { - Ok(fd) => fd, + Ok(fd) => SessionFdDrmDevice(fd), Err(err) => { warn!(logger, "Unable to open drm device {:?}, Error: {:?}. Skipping", path, err); return None; @@ -134,7 +146,7 @@ impl< } } }) - .collect::>, FdEventSource<(StateToken>, H)>)>>(); + .collect::>, FdEventSource<(StateToken>, H)>)>>(); let mut builder = MonitorBuilder::new(context).chain_err(|| ErrorKind::FailedToInitMonitor)?; builder @@ -181,8 +193,8 @@ impl< } impl< - B: Borrow + 'static, - H: DrmHandler + 'static, + B: Borrow> + 'static, + H: DrmHandler + 'static, S: Session + 'static, T: UdevHandler + 'static, > SessionObserver for StateToken> { @@ -211,8 +223,8 @@ pub fn udev_backend_bind( evlh: &mut EventLoopHandle, udev: StateToken> ) -> IoResult>>> where - B: From + Borrow + 'static, - H: DrmHandler + 'static, + B: From> + Borrow> + 'static, + H: DrmHandler + 'static, T: UdevHandler + 'static, S: Session + 'static, { @@ -222,8 +234,8 @@ where fn fd_event_source_implementation() -> FdEventSourceImpl>> where - B: From + Borrow + 'static, - H: DrmHandler + 'static, + B: From> + Borrow> + 'static, + H: DrmHandler + 'static, T: UdevHandler + 'static, S: Session + 'static, { @@ -242,7 +254,7 @@ where if let (Some(path), Some(devnum)) = (event.devnode(), event.devnum()) { let mut device = { match unsafe { - DrmDevice::new_from_fd( + DrmDevice::new( { let logger = evlh.state().get(token).logger.clone(); match evlh.state().get_mut(token).session.open( @@ -250,7 +262,7 @@ where fcntl::O_RDWR | fcntl::O_CLOEXEC | fcntl::O_NOCTTY | fcntl::O_NONBLOCK, ) { - Ok(fd) => fd, + Ok(fd) => SessionFdDrmDevice(fd), Err(err) => { warn!(logger, "Unable to open drm device {:?}, Error: {:?}. Skipping", path, err); continue; @@ -365,7 +377,7 @@ where } /// Handler for the `UdevBackend`, allows to open, close and update drm devices as they change during runtime. -pub trait UdevHandler + 'static, H: DrmHandler + 'static> +pub trait UdevHandler> + 'static, H: DrmHandler + 'static> { /// Called on initialization for every known device and when a new device is detected. /// @@ -373,7 +385,7 @@ pub trait UdevHandler + 'static, H: DrmHandler + 'stati /// /// ## Panics /// Panics if you try to borrow the token of the belonging `UdevBackend` using this `StateProxy`. - fn device_added<'a, S: Into>>(&mut self, state: S, device: &mut DrmDevice) + fn device_added<'a, S: Into>>(&mut self, state: S, device: &mut DrmDevice) -> Option; /// Called when an open device is changed. /// @@ -382,7 +394,7 @@ pub trait UdevHandler + 'static, H: DrmHandler + 'stati /// /// ## Panics /// Panics if you try to borrow the token of the belonging `UdevBackend` using this `StateProxy`. - fn device_changed<'a, S: Into>>(&mut self, state: S, device: &StateToken>); + fn device_changed<'a, S: Into>>(&mut self, state: S, device: &StateToken>); /// Called when a device was removed. /// /// The device will not accept any operations anymore and its file descriptor will be closed once @@ -390,7 +402,7 @@ pub trait UdevHandler + 'static, H: DrmHandler + 'stati /// /// ## Panics /// Panics if you try to borrow the token of the belonging `UdevBackend` using this `StateProxy`. - fn device_removed<'a, S: Into>>(&mut self, state: S, device: &StateToken>); + fn device_removed<'a, S: Into>>(&mut self, state: S, device: &StateToken>); /// Called when the udev context has encountered and error. /// /// ## Panics diff --git a/src/backend/winit.rs b/src/backend/winit.rs index 428a8d4..a2d2f18 100644 --- a/src/backend/winit.rs +++ b/src/backend/winit.rs @@ -1,44 +1,23 @@ //! Implementation of backend traits for types provided by `winit` use backend::graphics::GraphicsBackend; -use backend::graphics::egl::{self, EGLContext, EGLGraphicsBackend, GlAttributes, PixelFormat, - PixelFormatRequirements, SwapBuffersError}; +use backend::graphics::egl::{EGLGraphicsBackend, EGLContext, EGLSurface, PixelFormat, SwapBuffersError, EglExtensionNotSupportedError}; +use backend::graphics::egl::error as egl_error; +use backend::graphics::egl::native; +use backend::graphics::egl::context::{GlAttributes, PixelFormatRequirements}; 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::libc::c_void; -use rental::TryNewError; -use std::cell::Cell; use std::cmp; use std::error; use std::fmt; use std::rc::Rc; use winit::{ElementState, Event, EventsLoop, KeyboardInput, MouseButton as WinitMouseButton, MouseCursor, - MouseScrollDelta, Touch, TouchPhase, WindowBuilder, WindowEvent}; -use winit::os::unix::WindowExt; - -rental! { - 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] - pub struct EGL<'a> { - context: Box>, - surface: EGLSurface<'context, 'a, WinitWindow>, - } - } -} - -use self::rental::{Window, EGL}; + MouseScrollDelta, Touch, TouchPhase, WindowBuilder, WindowEvent, Window as WinitWindow}; +use wayland_client::egl as wegl; +use wayland_server::Display; error_chain! { errors { @@ -46,23 +25,42 @@ error_chain! { InitFailed { description("Failed to initialize a window") } + + #[doc = "Context creation is not supported on the current window system"] + NotSupported { + description("Context creation is not supported on the current window system.") + } } links { - EGL(egl::Error, egl::ErrorKind) #[doc = "EGL error"]; + EGL(egl_error::Error, egl_error::ErrorKind) #[doc = "EGL error"]; } } -impl From> for Error { - fn from(err: TryNewError) -> Error { - err.0 +enum Window { + Wayland { + context: EGLContext, + surface: EGLSurface, + }, + X11 { + context: EGLContext, + surface: EGLSurface, + }, +} + +impl Window { + fn window(&self) -> &WinitWindow { + match self { + &Window::Wayland { ref context, .. } => &**context, + &Window::X11 { ref context, .. } => &**context, + } } } + /// Window with an active EGL Context created by `winit`. Implements the /// `EGLGraphicsBackend` graphics backend trait pub struct WinitGraphicsBackend { window: Rc, - ready: Cell, logger: ::slog::Logger, } @@ -136,31 +134,46 @@ where .chain_err(|| ErrorKind::InitFailed)?; debug!(log, "Window created"); - let window = Rc::new(Window::try_new(Box::new(winit_window), |window| { - EGL::try_new( - Box::new(match EGLContext::new_from_winit( - &*window, + let reqs = PixelFormatRequirements { + hardware_accelerated: Some(true), + color_bits: Some(24), + alpha_bits: Some(8), + ..Default::default() + }; + + let window = Rc::new( + if native::NativeDisplay::::is_backend(&winit_window) { + let context = EGLContext::::new( + winit_window, attributes, - PixelFormatRequirements { - hardware_accelerated: Some(true), - color_bits: Some(24), - alpha_bits: Some(8), - ..Default::default() - }, + reqs, log.clone(), - ) { - Ok(context) => context, - Err(err) => bail!(err), - }), - |context| context.create_surface(window), - ).map_err(egl::Error::from) - .map_err(Error::from) - })?); + )?; + let surface = context.create_surface(())?; + Window::Wayland { + context, + surface + } + } else if native::NativeDisplay::::is_backend(&winit_window) { + let context = EGLContext::::new( + winit_window, + attributes, + reqs, + log.clone(), + )?; + let surface = context.create_surface(())?; + Window::X11 { + context, + surface + } + } else { + bail!(ErrorKind::NotSupported); + } + ); Ok(( WinitGraphicsBackend { window: window.clone(), - ready: Cell::new(false), logger: log.new(o!("smithay_winit_component" => "graphics")), }, WinitInputBackend { @@ -189,7 +202,7 @@ impl GraphicsBackend for WinitGraphicsBackend { fn set_cursor_position(&self, x: u32, y: u32) -> ::std::result::Result<(), ()> { debug!(self.logger, "Setting cursor position to {:?}", (x, y)); - self.window.head().set_cursor_position(x as i32, y as i32) + self.window.window().set_cursor_position(x as i32, y as i32) } fn set_cursor_representation( @@ -197,7 +210,7 @@ impl GraphicsBackend for WinitGraphicsBackend { ) -> ::std::result::Result<(), ()> { // Cannot log this one, as `CursorFormat` is not `Debug` and should not be debug!(self.logger, "Changing cursor representation"); - self.window.head().set_cursor(*cursor); + self.window.window().set_cursor(*cursor); Ok(()) } } @@ -205,45 +218,71 @@ impl GraphicsBackend for WinitGraphicsBackend { impl EGLGraphicsBackend for WinitGraphicsBackend { fn swap_buffers(&self) -> ::std::result::Result<(), SwapBuffersError> { trace!(self.logger, "Swapping buffers"); - if !self.ready.get() { - if self.window.head().is_ready() { - // avoid locking the mutex every time once the window is ready - self.ready.set(true); - } else { - // Not yet ready, just silently ignore the swap-buffers call - return Ok(()); - } + match *self.window { + Window::Wayland { ref surface, .. } => surface.swap_buffers(), + Window::X11 { ref 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.window.rent(|egl| egl.head().get_proc_address(symbol)) + match *self.window { + Window::Wayland { ref context, .. } => context.get_proc_address(symbol), + Window::X11 { ref context, .. } => context.get_proc_address(symbol), + } } fn get_framebuffer_dimensions(&self) -> (u32, u32) { self.window - .head() - .get_inner_size_pixels() + .window() + .get_inner_size() .expect("Window does not exist anymore") } fn is_current(&self) -> bool { - self.window - .rent(|egl| egl.rent_all(|egl| egl.context.is_current() && egl.surface.is_current())) + match *self.window { + Window::Wayland { ref context, ref surface } => context.is_current() && surface.is_current(), + Window::X11 { ref context, ref surface } => context.is_current() && surface.is_current(), + } } unsafe fn make_current(&self) -> ::std::result::Result<(), SwapBuffersError> { - debug!(self.logger, "Setting EGL context to be the current context"); - self.window - .rent(|egl| egl.rent(|surface| surface.make_current())) + trace!(self.logger, "Setting EGL context to be the current context"); + match *self.window { + Window::Wayland { ref surface, .. } => surface.make_current(), + Window::X11 { ref surface, .. } => surface.make_current(), + } } fn get_pixel_format(&self) -> PixelFormat { - self.window.rent(|egl| egl.head().get_pixel_format()) + match *self.window { + Window::Wayland { ref context, .. } => context.get_pixel_format(), + Window::X11 { ref context, .. } => context.get_pixel_format(), + } } + + fn bind_wl_display(&self, display: &Display) -> ::std::result::Result<(), EglExtensionNotSupportedError> { + match *self.window { + Window::Wayland { ref context, .. } => context.bind_wl_display(display), + Window::X11 { ref context, .. } => context.bind_wl_display(display), + }?; + Ok(()) + } + + fn unbind_wl_display(&self, display: &Display) -> ::std::result::Result<(), EglExtensionNotSupportedError> { + match *self.window { + Window::Wayland { ref context, .. } => context.unbind_wl_display(display), + Window::X11 { ref context, .. } => context.unbind_wl_display(display), + }?; + Ok(()) + } + + /* + unsafe fn egl_image_to_texture(&self, image: EGLImage, tex_id: c_uint) -> ::std::result::Result<(), EglExtensionNotSupportedError> { + self.window.rent(|egl| egl.head().egl_image_to_texture(image, tex_id))?; + Ok(()) + } + */ } /// Errors that may happen when driving the event loop of `WinitInputBackend` @@ -326,8 +365,8 @@ impl PointerMotionAbsoluteEvent for WinitMouseMovedEvent { cmp::min( (self.x * width as f64 / self.window - .head() - .get_inner_size_points() + .window() + .get_inner_size() .unwrap_or((width, 0)) .0 as f64) as u32, 0, @@ -338,8 +377,8 @@ impl PointerMotionAbsoluteEvent for WinitMouseMovedEvent { cmp::min( (self.y * height as f64 / self.window - .head() - .get_inner_size_points() + .window() + .get_inner_size() .unwrap_or((0, height)) .1 as f64) as u32, 0, @@ -439,8 +478,8 @@ impl TouchDownEvent for WinitTouchStartedEvent { cmp::min( self.location.0 as i32 * width as i32 / self.window - .head() - .get_inner_size_points() + .window() + .get_inner_size() .unwrap_or((width, 0)) .0 as i32, 0, @@ -451,8 +490,8 @@ impl TouchDownEvent for WinitTouchStartedEvent { cmp::min( self.location.1 as i32 * height as i32 / self.window - .head() - .get_inner_size_points() + .window() + .get_inner_size() .unwrap_or((0, height)) .1 as i32, 0, @@ -491,8 +530,8 @@ impl TouchMotionEvent for WinitTouchMovedEvent { fn x_transformed(&self, width: u32) -> u32 { self.location.0 as u32 * width / self.window - .head() - .get_inner_size_points() + .window() + .get_inner_size() .unwrap_or((width, 0)) .0 } @@ -500,8 +539,8 @@ impl TouchMotionEvent for WinitTouchMovedEvent { fn y_transformed(&self, height: u32) -> u32 { self.location.1 as u32 * height / self.window - .head() - .get_inner_size_points() + .window() + .get_inner_size() .unwrap_or((0, height)) .1 } @@ -626,14 +665,11 @@ impl InputBackend for WinitInputBackend { match (event, handler.as_mut()) { (WindowEvent::Resized(x, y), _) => { trace!(logger, "Resizing window to {:?}", (x, y)); - 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) - } - }) - }); + window.window().set_inner_size(x, y); + match **window { + Window::Wayland { ref surface, .. } => surface.resize(x as i32, y as i32, 0, 0), + _ => {}, + }; } ( WindowEvent::KeyboardInput { @@ -667,7 +703,7 @@ impl InputBackend for WinitInputBackend { ) } ( - WindowEvent::MouseMoved { + WindowEvent::CursorMoved { position: (x, y), .. }, Some(handler), diff --git a/src/lib.rs b/src/lib.rs index 2e1efeb..d22fbff 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,10 +8,8 @@ #![recursion_limit = "1024"] extern crate image; -#[macro_use] +#[cfg_attr(feature = "backend_session", macro_use)] extern crate nix; -#[macro_use] -extern crate rental; extern crate tempfile; extern crate wayland_protocols; extern crate wayland_server; diff --git a/src/wayland/drm/mod.rs b/src/wayland/drm/mod.rs index e270e17..b01dcf8 100644 --- a/src/wayland/drm/mod.rs +++ b/src/wayland/drm/mod.rs @@ -1,8 +1,8 @@ use ::backend::graphics::egl::ffi; -use ::backend::graphics::egl::EGLContext; -pub use ::backend::graphics::egl::ffi::EGLImage; -use nix::libc::c_int; +use ::backend::graphics::egl::{EGLContext, NativeSurface}; +use ::backend::graphics::egl::EGLImage; use wayland_server::protocol::wl_buffer::WlBuffer; +use wayland_server::Resource; /// Error that can occur when accessing an EGL buffer #[derive(Debug)] @@ -13,14 +13,14 @@ pub enum BufferAccessError { FailedToCreateEGLImage, } -#[repr(u32)] +#[repr(i32)] pub enum Format { - RGB = ffi::TEXTURE_RGB, - RGBA = ffi::TEXTURE_RGBA, - External = ffi::TEXTURE_EXTERNAL_WL, - Y_UV = ffi::TEXTURE_Y_UV_WL, - Y_U_V = ffi::TEXTURE_Y_U_V_WL, - Y_XUXV = ffi::TEXTURE_Y_XUXV_WL, + RGB = ffi::egl::TEXTURE_RGB as i32, + RGBA = ffi::egl::TEXTURE_RGBA as i32, + External = ffi::egl::TEXTURE_EXTERNAL_WL, + Y_UV = ffi::egl::TEXTURE_Y_UV_WL, + Y_U_V = ffi::egl::TEXTURE_Y_U_V_WL, + Y_XUXV = ffi::egl::TEXTURE_Y_XUXV_WL, } impl Format { @@ -33,46 +33,40 @@ impl Format { } } -pub struct Attributes { - width: u32, - height: u32, - y_inverted: bool, - format: Format, +pub struct EGLImages { + pub width: u32, + pub height: u32, + pub y_inverted: bool, + pub format: Format, + images: Vec, + buffer: WlBuffer, } -pub fn with_buffer_contents(buffer: &WlBuffer, context: &EGLContext, f: F) -> Result<(), BufferAccessError> +pub fn buffer_contents(buffer: WlBuffer, context: &EGLContext) -> Result<(Vec, attributes: Attributes), BufferAccessError> where - F: FnOnce(Attributes, Vec) { - let mut format: u32 = 0; - if context.egl.QueryWaylandBufferWL(context.display, buffer.ptr(), ffi::egl::TEXTURE_FORMAT, &mut format as *mut _) == 0 { + let mut format: i32 = 0; + if unsafe { ffi::egl::QueryWaylandBufferWL(context.display, buffer.ptr() as *mut _, ffi::egl::EGL_TEXTURE_FORMAT, &mut format as *mut _) == 0 } { return Err(BufferAccessError::NotManaged); } - let mut width: u32 = 0; - if context.egl.QueryWaylandBufferWL(context.display, buffer.ptr(), ffi::egl::WIDTH, &mut width as *mut _) == 0 { + let mut width: i32 = 0; + if unsafe { ffi::egl::QueryWaylandBufferWL(context.display, buffer.ptr() as *mut _, ffi::egl::WIDTH as i32, &mut width as *mut _) == 0 } { return Err(BufferAccessError::NotManaged); } - let mut height: u32 = 0; - if context.egl.QueryWaylandBufferWL(context.display, buffer.ptr(), ffi::egl::HEIGHT, &mut height as *mut _) == 0 { + let mut height: i32 = 0; + if unsafe { ffi::egl::QueryWaylandBufferWL(context.display, buffer.ptr() as *mut _, ffi::egl::HEIGHT as i32, &mut height as *mut _) == 0 } { return Err(BufferAccessError::NotManaged); } - let mut inverted: u32 = 0; - if context.egl.QueryWaylandBufferWL(context.display, buffer.ptr(), ffi::egl::WAYLAND_Y_INVERTED_WL, &mut inverted as *mut _) == 0 { + let mut inverted: i32 = 0; + if unsafe { ffi::egl::QueryWaylandBufferWL(context.display, buffer.ptr() as *mut _, ffi::egl::WAYLAND_Y_INVERTED_WL, &mut inverted as *mut _) == 0 } { inverted = 1; } - let attributes = Attributes { - width, - height, - y_inverted = inverted != 0, - format: format as Format, - }; - - let mut images = Vec::with_capacity(attributes.format.num_planes()); - for _ in 0..attributes.format.num_planes() { + let mut images = Vec::with_capacity(attributes.format.num_planes() as usize); + for i in 0..attributes.format.num_planes() { let mut out = Vec::with_capacity(3); out.push(ffi::egl::WAYLAND_PLANE_WL as i32); out.push(i as i32); @@ -80,13 +74,13 @@ where images.push({ let image = - ffi::egl::CreateImageKHR( + unsafe { ffi::egl::CreateImageKHR( context.display, ffi::egl::NO_CONTEXT, ffi::egl::WAYLAND_BUFFER_WL, - buffer.ptr(), + buffer.ptr() as *mut _, out.as_ptr(), - ); + ) }; if image == ffi::egl::NO_IMAGE_KHR { return Err(BufferAccessError::FailedToCreateEGLImage); } else { @@ -95,5 +89,22 @@ where }); } - f(attributes, images) + let result = EGLImages { + width: width as u32, + height: height as u32, + y_inverted: inverted != 0, + format: match format { + x if x == ffi::egl::TEXTURE_RGB as i32 => Format::RGB, + x if x == ffi::egl::TEXTURE_RGBA as i32 => Format::RGBA, + ffi::egl::TEXTURE_EXTERNAL_WL => Format::External, + ffi::egl::TEXTURE_Y_UV_WL => Format::Y_UV, + ffi::egl::TEXTURE_Y_U_V_WL => Format::Y_U_V, + ffi::egl::TEXTURE_Y_XUXV_WL => Format::Y_XUXV, + _ => panic!("EGL returned invalid texture type"), + }, + images, + buffer, + }; + + Ok(result) } diff --git a/src/wayland/mod.rs b/src/wayland/mod.rs index ccdb2d9..31a43ba 100644 --- a/src/wayland/mod.rs +++ b/src/wayland/mod.rs @@ -19,6 +19,7 @@ //! quickly encounter a panic. pub mod compositor; +//pub mod drm; pub mod output; pub mod seat; pub mod shm;