drm: Introduce the Drm Backend
- new backend rendering via egl via gbm directly on a drm device - refine EGLContext and EGLSurface dependencies through lifetimes - fixup the old winit backend to work with these changes - add new example using the drm backend instead - change GliumDrawer to be static for the drm example
This commit is contained in:
parent
24ea2a066a
commit
0698775153
18
Cargo.toml
18
Cargo.toml
|
@ -13,12 +13,15 @@ slog = { version = "2.0.0" }
|
|||
slog-stdlog = "2.0.0-0.2"
|
||||
libloading = "0.4.0"
|
||||
wayland-client = { version = "0.9.9", optional = true }
|
||||
winit = { version = "0.7.0", optional = true }
|
||||
glium = { version = "0.16.0", optional = true, default-features = false }
|
||||
winit = { version = "0.7.5", optional = true }
|
||||
drm = { version = "0.2.1", optional = true }
|
||||
gbm = { version = "0.2.1", optional = true }
|
||||
glium = { version = "0.17.1", optional = true, default-features = false }
|
||||
input = { version = "0.2.0", optional = true }
|
||||
clippy = { version = "*", optional = true }
|
||||
rental = "0.4.11"
|
||||
wayland-protocols = { version = "0.10.1", features = ["unstable_protocols", "server"] }
|
||||
image = "0.15.0"
|
||||
|
||||
[build-dependencies]
|
||||
gl_generator = "0.5"
|
||||
|
@ -29,7 +32,14 @@ slog-async = "2.0"
|
|||
rand = "0.3"
|
||||
|
||||
[features]
|
||||
default = ["backend_winit", "backend_libinput", "renderer_glium"]
|
||||
default = ["backend_winit", "backend_drm", "backend_libinput", "renderer_glium"]
|
||||
backend_winit = ["winit", "wayland-server/dlopen", "wayland-client/dlopen"]
|
||||
renderer_glium = ["glium"]
|
||||
backend_drm = ["drm", "gbm"]
|
||||
backend_libinput = ["input"]
|
||||
renderer_glium = ["glium"]
|
||||
|
||||
[replace]
|
||||
"wayland-server:0.9.9" = { git = "https://github.com/Drakulix/wayland-rs", branch = "raw_handler_access"}
|
||||
"wayland-protocols:0.9.9" = { git = "https://github.com/Drakulix/wayland-rs", branch = "raw_handler_access"}
|
||||
"wayland-client:0.9.9" = { git = "https://github.com/Drakulix/wayland-rs", branch = "raw_handler_access"}
|
||||
"drm:0.2.1" = { git = "https://github.com/Drakulix/drm-rs", branch = "fix/userdata" }
|
||||
|
|
|
@ -0,0 +1,349 @@
|
|||
|
||||
extern crate drm;
|
||||
#[macro_use]
|
||||
extern crate glium;
|
||||
extern crate rand;
|
||||
#[macro_use(define_roles)]
|
||||
extern crate smithay;
|
||||
extern crate wayland_protocols;
|
||||
extern crate wayland_server;
|
||||
|
||||
#[macro_use]
|
||||
extern crate slog;
|
||||
extern crate slog_async;
|
||||
extern crate slog_term;
|
||||
|
||||
mod helpers;
|
||||
|
||||
use drm::control::{Device as ControlDevice, ResourceInfo};
|
||||
use drm::control::connector::{Info as ConnectorInfo, State as ConnectorState};
|
||||
|
||||
use glium::Surface;
|
||||
|
||||
use helpers::GliumDrawer;
|
||||
use slog::*;
|
||||
|
||||
use smithay::backend::drm::{DrmBackend, DrmDevice, DrmHandler, Id};
|
||||
use smithay::backend::graphics::egl::EGLGraphicsBackend;
|
||||
use smithay::backend::graphics::glium::{GliumGraphicsBackend, IntoGlium};
|
||||
use smithay::compositor::{self, CompositorHandler, CompositorToken, SubsurfaceRole, TraversalAction};
|
||||
use smithay::compositor::roles::Role;
|
||||
use smithay::shell::{self, PopupConfigure, PopupSurface, ShellClient, ShellHandler, ShellSurfaceRole,
|
||||
ToplevelConfigure, ToplevelSurface};
|
||||
use smithay::shm::{ShmGlobal, ShmToken};
|
||||
|
||||
use std::fs::OpenOptions;
|
||||
use std::io::Error as IoError;
|
||||
use std::time::Duration;
|
||||
use std::os::unix::io::AsRawFd;
|
||||
|
||||
use wayland_protocols::unstable::xdg_shell::server::{zxdg_shell_v6, zxdg_toplevel_v6};
|
||||
|
||||
use wayland_server::{Client, EventLoopHandle};
|
||||
use wayland_server::sources::READ;
|
||||
use wayland_server::protocol::{wl_callback, wl_compositor, wl_output, wl_seat, wl_shell, wl_shm,
|
||||
wl_subcompositor, wl_surface};
|
||||
|
||||
define_roles!(Roles => [ ShellSurface, ShellSurfaceRole ] );
|
||||
|
||||
struct SurfaceHandler {
|
||||
shm_token: ShmToken,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct SurfaceData {
|
||||
buffer: Option<(Vec<u8>, (u32, u32))>,
|
||||
location: Option<(i32, i32)>,
|
||||
}
|
||||
|
||||
impl compositor::Handler<SurfaceData, Roles> for SurfaceHandler {
|
||||
fn commit(&mut self, _evlh: &mut EventLoopHandle, _client: &Client, surface: &wl_surface::WlSurface,
|
||||
token: CompositorToken<SurfaceData, Roles, SurfaceHandler>) {
|
||||
// we retrieve the contents of the associated buffer and copy it
|
||||
token.with_surface_data(surface, |attributes| {
|
||||
match attributes.buffer.take() {
|
||||
Some(Some((buffer, (_x, _y)))) => {
|
||||
// we ignore hotspot coordinates in this simple example
|
||||
self.shm_token
|
||||
.with_buffer_contents(&buffer, |slice, data| {
|
||||
let offset = data.offset as usize;
|
||||
let stride = data.stride as usize;
|
||||
let width = data.width as usize;
|
||||
let height = data.height as usize;
|
||||
let mut new_vec = Vec::with_capacity(width * height * 4);
|
||||
for i in 0..height {
|
||||
new_vec.extend(
|
||||
&slice[(offset + i * stride)..(offset + i * stride + width * 4)],
|
||||
);
|
||||
}
|
||||
attributes.user_data.buffer =
|
||||
Some((new_vec, (data.width as u32, data.height as u32)));
|
||||
})
|
||||
.unwrap();
|
||||
buffer.release();
|
||||
}
|
||||
Some(None) => {
|
||||
// erase the contents
|
||||
attributes.user_data.buffer = None;
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn frame(&mut self, _evlh: &mut EventLoopHandle, _client: &Client, _surface: &wl_surface::WlSurface,
|
||||
callback: wl_callback::WlCallback,
|
||||
_token: CompositorToken<SurfaceData, Roles, SurfaceHandler>) {
|
||||
callback.done(0);
|
||||
}
|
||||
}
|
||||
|
||||
struct ShellSurfaceHandler {
|
||||
token: CompositorToken<SurfaceData, Roles, SurfaceHandler>,
|
||||
}
|
||||
|
||||
impl ShellSurfaceHandler {
|
||||
fn new(token: CompositorToken<SurfaceData, Roles, SurfaceHandler>) -> ShellSurfaceHandler {
|
||||
ShellSurfaceHandler { token }
|
||||
}
|
||||
}
|
||||
|
||||
impl shell::Handler<SurfaceData, Roles, SurfaceHandler, ()> for ShellSurfaceHandler {
|
||||
fn new_client(&mut self, _evlh: &mut EventLoopHandle, _client: ShellClient<()>) {}
|
||||
fn client_pong(&mut self, _evlh: &mut EventLoopHandle, _client: ShellClient<()>) {}
|
||||
fn new_toplevel(&mut self, _evlh: &mut EventLoopHandle,
|
||||
surface: ToplevelSurface<SurfaceData, Roles, SurfaceHandler, ()>)
|
||||
-> ToplevelConfigure {
|
||||
let wl_surface = surface.get_surface().unwrap();
|
||||
self.token.with_surface_data(wl_surface, |data| {
|
||||
// place the window at a random location in the [0;300]x[0;300] square
|
||||
use rand::distributions::{IndependentSample, Range};
|
||||
let range = Range::new(0, 300);
|
||||
let mut rng = rand::thread_rng();
|
||||
let x = range.ind_sample(&mut rng);
|
||||
let y = range.ind_sample(&mut rng);
|
||||
data.user_data.location = Some((x, y))
|
||||
});
|
||||
ToplevelConfigure {
|
||||
size: None,
|
||||
states: vec![],
|
||||
serial: 42,
|
||||
}
|
||||
}
|
||||
fn new_popup(&mut self, _evlh: &mut EventLoopHandle,
|
||||
_surface: PopupSurface<SurfaceData, Roles, SurfaceHandler, ()>)
|
||||
-> PopupConfigure {
|
||||
PopupConfigure {
|
||||
size: (10, 10),
|
||||
position: (10, 10),
|
||||
serial: 42,
|
||||
}
|
||||
}
|
||||
fn move_(&mut self, _evlh: &mut EventLoopHandle,
|
||||
_surface: ToplevelSurface<SurfaceData, Roles, SurfaceHandler, ()>, _seat: &wl_seat::WlSeat,
|
||||
_serial: u32) {
|
||||
}
|
||||
fn resize(&mut self, _evlh: &mut EventLoopHandle,
|
||||
_surface: ToplevelSurface<SurfaceData, Roles, SurfaceHandler, ()>, _seat: &wl_seat::WlSeat,
|
||||
_serial: u32, _edges: zxdg_toplevel_v6::ResizeEdge) {
|
||||
}
|
||||
fn grab(&mut self, _evlh: &mut EventLoopHandle,
|
||||
_surface: PopupSurface<SurfaceData, Roles, SurfaceHandler, ()>, _seat: &wl_seat::WlSeat,
|
||||
_serial: u32) {
|
||||
}
|
||||
fn change_display_state(&mut self, _evlh: &mut EventLoopHandle,
|
||||
_surface: ToplevelSurface<SurfaceData, Roles, SurfaceHandler, ()>,
|
||||
_maximized: Option<bool>, _minimized: Option<bool>, _fullscreen: Option<bool>,
|
||||
_output: Option<&wl_output::WlOutput>)
|
||||
-> ToplevelConfigure {
|
||||
ToplevelConfigure {
|
||||
size: None,
|
||||
states: vec![],
|
||||
serial: 42,
|
||||
}
|
||||
}
|
||||
fn show_window_menu(&mut self, _evlh: &mut EventLoopHandle,
|
||||
_surface: ToplevelSurface<SurfaceData, Roles, SurfaceHandler, ()>,
|
||||
_seat: &wl_seat::WlSeat, _serial: u32, _x: i32, _y: i32) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
type MyCompositorHandler = CompositorHandler<SurfaceData, Roles, SurfaceHandler>;
|
||||
type MyShellHandler = ShellHandler<SurfaceData, Roles, SurfaceHandler, ShellSurfaceHandler, ()>;
|
||||
|
||||
fn main() {
|
||||
// A logger facility, here we use the terminal for this example
|
||||
let log = Logger::root(
|
||||
slog_async::Async::default(slog_term::term_full().fuse()).fuse(),
|
||||
o!(),
|
||||
);
|
||||
|
||||
// Initialize the wayland server
|
||||
let (mut display, mut event_loop) = wayland_server::create_display();
|
||||
|
||||
// "Find" a suitable drm device
|
||||
let mut options = OpenOptions::new();
|
||||
options.read(true);
|
||||
options.write(true);
|
||||
let mut device =
|
||||
DrmDevice::new_from_file(options.clone().open("/dev/dri/card0").unwrap(), log.clone()).unwrap();
|
||||
|
||||
// Get a set of all modesetting resource handles (excluding planes):
|
||||
let res_handles = device.resource_handles().unwrap();
|
||||
|
||||
// Use first connected connector
|
||||
let connector_info = res_handles
|
||||
.connectors()
|
||||
.iter()
|
||||
.map(|conn| {
|
||||
ConnectorInfo::load_from_device(&device, *conn).unwrap()
|
||||
})
|
||||
.find(|conn| conn.connection_state() == ConnectorState::Connected)
|
||||
.unwrap();
|
||||
|
||||
// Use the first crtc (should be successful in most cases)
|
||||
let crtc = res_handles.crtcs()[0];
|
||||
|
||||
// Assuming we found a good connector and loaded the info into `connector_info`
|
||||
let mode = connector_info.modes()[0]; // Use first mode (usually highest resoltion, but in reality you should filter and sort and check and match with other connectors, if you use more then one.)
|
||||
|
||||
// Initialize the hardware backends
|
||||
let renderer = device
|
||||
.create_backend(crtc, mode, vec![connector_info.handle()])
|
||||
.unwrap();
|
||||
|
||||
/*
|
||||
* Initialize wl_shm global
|
||||
*/
|
||||
// Insert the ShmGlobal as a handler to your event loop
|
||||
// Here, we specify tha the standard Argb8888 and Xrgb8888 is the only supported.
|
||||
let shm_handler_id = event_loop.add_handler_with_init(ShmGlobal::new(vec![], log.clone()));
|
||||
// Register this handler to advertise a wl_shm global of version 1
|
||||
event_loop.register_global::<wl_shm::WlShm, ShmGlobal>(shm_handler_id, 1);
|
||||
// retreive the token
|
||||
let shm_token = {
|
||||
let state = event_loop.state();
|
||||
state.get_handler::<ShmGlobal>(shm_handler_id).get_token()
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
* Initialize the compositor global
|
||||
*/
|
||||
let compositor_handler_id = event_loop.add_handler_with_init(MyCompositorHandler::new(
|
||||
SurfaceHandler {
|
||||
shm_token: shm_token.clone(),
|
||||
},
|
||||
log.clone(),
|
||||
));
|
||||
// register it to handle wl_compositor and wl_subcompositor
|
||||
event_loop.register_global::<wl_compositor::WlCompositor, MyCompositorHandler>(compositor_handler_id, 4);
|
||||
event_loop
|
||||
.register_global::<wl_subcompositor::WlSubcompositor, MyCompositorHandler>(compositor_handler_id, 1);
|
||||
// retrieve the tokens
|
||||
let compositor_token = {
|
||||
let state = event_loop.state();
|
||||
state
|
||||
.get_handler::<MyCompositorHandler>(compositor_handler_id)
|
||||
.get_token()
|
||||
};
|
||||
|
||||
/*
|
||||
* Initialize the shell global
|
||||
*/
|
||||
let shell_handler_id = event_loop.add_handler_with_init(MyShellHandler::new(
|
||||
ShellSurfaceHandler::new(compositor_token),
|
||||
compositor_token,
|
||||
log.clone(),
|
||||
));
|
||||
event_loop.register_global::<wl_shell::WlShell, MyShellHandler>(shell_handler_id, 1);
|
||||
event_loop.register_global::<zxdg_shell_v6::ZxdgShellV6, MyShellHandler>(shell_handler_id, 1);
|
||||
|
||||
|
||||
/*
|
||||
* Initialize glium
|
||||
*/
|
||||
let drawer = GliumDrawer::new(renderer.into_glium());
|
||||
let mut frame = drawer.draw();
|
||||
frame.clear_color(0.8, 0.8, 0.9, 1.0);
|
||||
frame.finish().unwrap();
|
||||
|
||||
/*
|
||||
* Add a listening socket:
|
||||
*/
|
||||
let name = display.add_socket_auto().unwrap().into_string().unwrap();
|
||||
println!("Listening on socket: {}", name);
|
||||
|
||||
device.set_handler(DrmHandlerImpl {
|
||||
drawer: drawer,
|
||||
shell_handler_id,
|
||||
compositor_token,
|
||||
logger: log,
|
||||
});
|
||||
|
||||
let fd = device.as_raw_fd();
|
||||
let drm_device_id = event_loop.add_handler(device);
|
||||
let _drm_event_source = event_loop.add_fd_event_source::<DrmDevice<DrmHandlerImpl>>(fd, drm_device_id, READ);
|
||||
|
||||
event_loop.run().unwrap();
|
||||
}
|
||||
|
||||
pub struct DrmHandlerImpl {
|
||||
drawer: GliumDrawer<GliumGraphicsBackend<DrmBackend>>,
|
||||
shell_handler_id: usize,
|
||||
compositor_token: CompositorToken<SurfaceData, Roles, SurfaceHandler>,
|
||||
logger: ::slog::Logger,
|
||||
}
|
||||
|
||||
impl DrmHandler for DrmHandlerImpl {
|
||||
fn ready(&mut self, evlh: &mut EventLoopHandle, _id: Id, _frame: u32, _duration: Duration) {
|
||||
let mut frame = self.drawer.draw();
|
||||
frame.clear_color(0.8, 0.8, 0.9, 1.0);
|
||||
// redraw the frame, in a simple but inneficient way
|
||||
{
|
||||
let screen_dimensions = self.drawer.get_framebuffer_dimensions();
|
||||
for toplevel_surface in unsafe {
|
||||
evlh.get_handler_unchecked::<MyShellHandler>(self.shell_handler_id)
|
||||
.toplevel_surfaces()
|
||||
} {
|
||||
if let Some(wl_surface) = toplevel_surface.get_surface() {
|
||||
// this surface is a root of a subsurface tree that needs to be drawn
|
||||
let initial_place = self.compositor_token
|
||||
.with_surface_data(wl_surface, |data| data.user_data.location.unwrap_or((0, 0)));
|
||||
self.compositor_token
|
||||
.with_surface_tree(
|
||||
wl_surface,
|
||||
initial_place,
|
||||
|_surface, attributes, role, &(mut x, mut y)| {
|
||||
if let Some((ref contents, (w, h))) = attributes.user_data.buffer {
|
||||
// there is actually something to draw !
|
||||
if let Ok(subdata) = Role::<SubsurfaceRole>::data(role) {
|
||||
x += subdata.x;
|
||||
y += subdata.y;
|
||||
}
|
||||
self.drawer.render(
|
||||
&mut frame,
|
||||
contents,
|
||||
(w, h),
|
||||
(x, y),
|
||||
screen_dimensions,
|
||||
);
|
||||
TraversalAction::DoChildren((x, y))
|
||||
} else {
|
||||
// we are not display, so our children are neither
|
||||
TraversalAction::SkipChildren
|
||||
}
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
frame.finish().unwrap();
|
||||
}
|
||||
|
||||
fn error(&mut self, _evlh: &mut EventLoopHandle, error: IoError) {
|
||||
error!(self.logger, "{:?}", error);
|
||||
}
|
||||
}
|
|
@ -2,6 +2,8 @@ use glium;
|
|||
use glium::Surface;
|
||||
use glium::index::PrimitiveType;
|
||||
|
||||
use std::ops::Deref;
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
struct Vertex {
|
||||
position: [f32; 2],
|
||||
|
@ -10,18 +12,26 @@ struct Vertex {
|
|||
|
||||
implement_vertex!(Vertex, position, tex_coords);
|
||||
|
||||
pub struct GliumDrawer<'a, F: 'a> {
|
||||
display: &'a F,
|
||||
pub struct GliumDrawer<F> {
|
||||
display: F,
|
||||
vertex_buffer: glium::VertexBuffer<Vertex>,
|
||||
index_buffer: glium::IndexBuffer<u16>,
|
||||
program: glium::Program,
|
||||
}
|
||||
|
||||
impl<'a, F: glium::backend::Facade + 'a> GliumDrawer<'a, F> {
|
||||
pub fn new(display: &'a F) -> GliumDrawer<'a, F> {
|
||||
impl<F> Deref for GliumDrawer<F> {
|
||||
type Target = F;
|
||||
|
||||
fn deref(&self) -> &F {
|
||||
&self.display
|
||||
}
|
||||
}
|
||||
|
||||
impl<F: glium::backend::Facade> GliumDrawer<F> {
|
||||
pub fn new(display: F) -> GliumDrawer<F> {
|
||||
// building the vertex buffer, which contains all the vertices that we will draw
|
||||
let vertex_buffer = glium::VertexBuffer::new(
|
||||
display,
|
||||
&display,
|
||||
&[
|
||||
Vertex {
|
||||
position: [0.0, 0.0],
|
||||
|
@ -44,10 +54,10 @@ impl<'a, F: glium::backend::Facade + 'a> GliumDrawer<'a, F> {
|
|||
|
||||
// building the index buffer
|
||||
let index_buffer =
|
||||
glium::IndexBuffer::new(display, PrimitiveType::TriangleStrip, &[1 as u16, 2, 0, 3]).unwrap();
|
||||
glium::IndexBuffer::new(&display, PrimitiveType::TriangleStrip, &[1 as u16, 2, 0, 3]).unwrap();
|
||||
|
||||
// compiling shaders and linking them together
|
||||
let program = program!(display,
|
||||
let program = program!(&display,
|
||||
100 => {
|
||||
vertex: "
|
||||
#version 100
|
||||
|
@ -84,7 +94,7 @@ impl<'a, F: glium::backend::Facade + 'a> GliumDrawer<'a, F> {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn draw(&self, target: &mut glium::Frame, contents: &[u8], surface_dimensions: (u32, u32),
|
||||
pub fn render(&self, target: &mut glium::Frame, contents: &[u8], surface_dimensions: (u32, u32),
|
||||
surface_location: (i32, i32), screen_size: (u32, u32)) {
|
||||
let image = glium::texture::RawImage2d {
|
||||
data: contents.into(),
|
||||
|
@ -92,7 +102,7 @@ impl<'a, F: glium::backend::Facade + 'a> GliumDrawer<'a, F> {
|
|||
height: surface_dimensions.1,
|
||||
format: glium::texture::ClientFormat::U8U8U8U8,
|
||||
};
|
||||
let opengl_texture = glium::texture::Texture2d::new(self.display, image).unwrap();
|
||||
let opengl_texture = glium::texture::Texture2d::new(&self.display, image).unwrap();
|
||||
|
||||
let xscale = 2.0 * (surface_dimensions.0 as f32) / (screen_size.0 as f32);
|
||||
let yscale = -2.0 * (surface_dimensions.1 as f32) / (screen_size.1 as f32);
|
||||
|
|
|
@ -15,6 +15,8 @@ mod helpers;
|
|||
use glium::Surface;
|
||||
use helpers::{shell_implementation, surface_implementation, GliumDrawer};
|
||||
use slog::{Drain, Logger};
|
||||
use smithay::backend::graphics::egl::EGLGraphicsBackend;
|
||||
|
||||
use smithay::backend::graphics::glium::IntoGlium;
|
||||
use smithay::backend::input::InputBackend;
|
||||
use smithay::backend::winit;
|
||||
|
@ -55,9 +57,7 @@ fn main() {
|
|||
/*
|
||||
* Initialize glium
|
||||
*/
|
||||
let context = renderer.into_glium();
|
||||
|
||||
let drawer = GliumDrawer::new(&context);
|
||||
let drawer = GliumDrawer::new(renderer.into_glium());
|
||||
|
||||
/*
|
||||
* Add a listening socket:
|
||||
|
@ -68,11 +68,11 @@ fn main() {
|
|||
loop {
|
||||
input.dispatch_new_events().unwrap();
|
||||
|
||||
let mut frame = context.draw();
|
||||
let mut frame = drawer.draw();
|
||||
frame.clear(None, Some((0.8, 0.8, 0.9, 1.0)), false, None, None);
|
||||
// redraw the frame, in a simple but inneficient way
|
||||
{
|
||||
let screen_dimensions = context.get_framebuffer_dimensions();
|
||||
let screen_dimensions = drawer.get_framebuffer_dimensions();
|
||||
let state = event_loop.state();
|
||||
for toplevel_surface in state.get(&shell_state_token).toplevel_surfaces() {
|
||||
if let Some(wl_surface) = toplevel_surface.get_surface() {
|
||||
|
@ -90,7 +90,7 @@ fn main() {
|
|||
x += subdata.x;
|
||||
y += subdata.y;
|
||||
}
|
||||
drawer.draw(&mut frame, contents, (w, h), (x, y), screen_dimensions);
|
||||
drawer.render(&mut frame, contents, (w, h), (x, y), screen_dimensions);
|
||||
TraversalAction::DoChildren((x, y))
|
||||
} else {
|
||||
// we are not display, so our children are neither
|
|
@ -0,0 +1,419 @@
|
|||
|
||||
use super::{DrmError, ModeError};
|
||||
|
||||
use super::devices;
|
||||
|
||||
use backend::graphics::GraphicsBackend;
|
||||
use backend::graphics::egl::{EGLGraphicsBackend, EGLSurface, PixelFormat, SwapBuffersError};
|
||||
use drm::buffer::Buffer;
|
||||
use drm::control::{connector, crtc, framebuffer, Mode};
|
||||
use drm::control::ResourceInfo;
|
||||
|
||||
use gbm::{BufferObject, BufferObjectFlags, Format as GbmFormat, Surface as GbmSurface, SurfaceBufferHandle};
|
||||
|
||||
use image::{ImageBuffer, Rgba};
|
||||
|
||||
use nix::c_void;
|
||||
|
||||
use std::cell::{Cell, RefCell};
|
||||
use std::rc::Rc;
|
||||
|
||||
pub struct DrmBackend(Rc<RefCell<DrmBackendInternal>>);
|
||||
|
||||
impl DrmBackend {
|
||||
pub(crate) fn new(drm: Rc<RefCell<DrmBackendInternal>>) -> DrmBackend {
|
||||
DrmBackend(drm)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Dependency graph
|
||||
- drm
|
||||
- gbm
|
||||
- context
|
||||
- gbm_surface
|
||||
- egl_surface
|
||||
- gbm_buffers
|
||||
- cursor
|
||||
*/
|
||||
|
||||
pub(crate) struct GbmTypes<'dev, 'context> {
|
||||
cursor: BufferObject<'dev, ()>,
|
||||
surface: Surface<'context>,
|
||||
}
|
||||
|
||||
pub(crate) struct EGL<'gbm, 'context> {
|
||||
surface: EGLSurface<'context, 'gbm, GbmSurface<'context, framebuffer::Info>>,
|
||||
buffers: GbmBuffers<'gbm>,
|
||||
}
|
||||
|
||||
pub(crate) struct GbmBuffers<'gbm> {
|
||||
front_buffer: Cell<SurfaceBufferHandle<'gbm, framebuffer::Info>>,
|
||||
next_buffer: Cell<Option<SurfaceBufferHandle<'gbm, framebuffer::Info>>>,
|
||||
}
|
||||
|
||||
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<GbmSurface<'context, framebuffer::Info>>,
|
||||
egl: super::EGL<'gbm, 'context>,
|
||||
}
|
||||
|
||||
#[rental]
|
||||
pub(crate) struct Graphics {
|
||||
#[subrental(arity = 3)]
|
||||
context: Rc<Context>,
|
||||
gbm: GbmTypes<'context_1, 'context_2>,
|
||||
}
|
||||
}
|
||||
}
|
||||
use self::graphics::{Graphics, Surface};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct Id(usize);
|
||||
|
||||
impl Id {
|
||||
pub(crate) fn raw(&self) -> usize {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct DrmBackendInternal {
|
||||
graphics: Graphics,
|
||||
crtc: crtc::Handle,
|
||||
mode: Mode,
|
||||
connectors: Vec<connector::Handle>,
|
||||
own_id: Id,
|
||||
logger: ::slog::Logger,
|
||||
}
|
||||
|
||||
impl DrmBackendInternal {
|
||||
pub(crate) fn new<I, L>(context: Rc<devices::Context>, crtc: crtc::Handle, mode: Mode, connectors: I,
|
||||
own_id: usize, logger: L)
|
||||
-> Result<DrmBackendInternal, DrmError>
|
||||
where
|
||||
I: Into<Vec<connector::Handle>>,
|
||||
L: Into<Option<::slog::Logger>>,
|
||||
{
|
||||
let log = ::slog_or_stdlog(logger).new(o!("smithay_module" => "backend_drm"));
|
||||
info!(log, "Initializing a drm backend");
|
||||
|
||||
let connectors = connectors.into();
|
||||
|
||||
for connector in connectors.iter() {
|
||||
if !connector::Info::load_from_device(context.head().head(), *connector)?
|
||||
.modes()
|
||||
.contains(&mode)
|
||||
{
|
||||
return Err(DrmError::Mode(ModeError::ModeNotSuitable));
|
||||
}
|
||||
}
|
||||
|
||||
let (w, h) = mode.size();
|
||||
|
||||
info!(log, "Drm Backend initialized");
|
||||
|
||||
Ok(DrmBackendInternal {
|
||||
graphics: Graphics::try_new(context, |context| {
|
||||
debug!(log, "GBM EGLContext initialized");
|
||||
|
||||
Ok(GbmTypes {
|
||||
cursor: {
|
||||
// Create an unused cursor buffer (we don't want an Option here)
|
||||
context.devices.gbm.create_buffer_object(
|
||||
1,
|
||||
1,
|
||||
GbmFormat::ARGB8888,
|
||||
&[BufferObjectFlags::Cursor, BufferObjectFlags::Write],
|
||||
)?
|
||||
},
|
||||
surface: Surface::try_new(
|
||||
Box::new(context.devices.gbm.create_surface(
|
||||
w as u32,
|
||||
h as u32,
|
||||
GbmFormat::XRGB8888,
|
||||
&[BufferObjectFlags::Scanout, BufferObjectFlags::Rendering],
|
||||
)?),
|
||||
|surface| {
|
||||
let egl_surface = context.egl.create_surface(&surface)?;
|
||||
unsafe { egl_surface.make_current()? };
|
||||
egl_surface.swap_buffers()?;
|
||||
|
||||
let mut front_bo = surface.lock_front_buffer()?;
|
||||
debug!(log, "FrontBuffer color format: {:?}", front_bo.format());
|
||||
let fb = framebuffer::create(context.devices.drm, &*front_bo)?;
|
||||
crtc::set(
|
||||
context.devices.drm,
|
||||
crtc,
|
||||
fb.handle(),
|
||||
&connectors,
|
||||
(0, 0),
|
||||
Some(mode),
|
||||
)?;
|
||||
front_bo.set_userdata(fb);
|
||||
|
||||
Ok(EGL {
|
||||
surface: egl_surface,
|
||||
buffers: GbmBuffers {
|
||||
front_buffer: Cell::new(front_bo),
|
||||
next_buffer: Cell::new(None),
|
||||
},
|
||||
})
|
||||
},
|
||||
).map_err(DrmError::from)?,
|
||||
})
|
||||
})?,
|
||||
crtc,
|
||||
mode,
|
||||
connectors,
|
||||
own_id: Id(own_id),
|
||||
logger: log.clone(),
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn unlock_buffer(&self) {
|
||||
self.graphics.rent(|gbm| {
|
||||
gbm.surface.rent(|egl| {
|
||||
let next_bo = egl.buffers.next_buffer.replace(None);
|
||||
|
||||
if let Some(next_buffer) = next_bo {
|
||||
egl.buffers.front_buffer.set(next_buffer);
|
||||
// drop and release the old buffer
|
||||
} else {
|
||||
unreachable!();
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl DrmBackend {
|
||||
pub fn add_connector(&mut self, connector: connector::Handle) -> Result<(), ModeError> {
|
||||
let info =
|
||||
connector::Info::load_from_device(self.0.borrow().graphics.head().head().head(), connector)
|
||||
.map_err(|err| ModeError::FailedToLoad(err))?;
|
||||
let mut internal = self.0.borrow_mut();
|
||||
if info.modes().contains(&internal.mode) {
|
||||
internal.connectors.push(connector);
|
||||
Ok(())
|
||||
} else {
|
||||
Err(ModeError::ModeNotSuitable)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn used_connectors(&self) -> Vec<connector::Handle> {
|
||||
self.0.borrow().connectors.clone()
|
||||
}
|
||||
|
||||
pub fn remove_connector(&mut self, connector: connector::Handle) {
|
||||
self.0.borrow_mut().connectors.retain(|x| *x != connector);
|
||||
}
|
||||
|
||||
pub fn use_mode(&mut self, mode: Mode) -> Result<(), DrmError> {
|
||||
for connector in self.0.borrow().connectors.iter() {
|
||||
if !connector::Info::load_from_device(self.0.borrow().graphics.head().head().head(), *connector)?
|
||||
.modes()
|
||||
.contains(&mode)
|
||||
{
|
||||
return Err(DrmError::Mode(ModeError::ModeNotSuitable));
|
||||
}
|
||||
}
|
||||
|
||||
let crtc = self.0.borrow().crtc;
|
||||
let mut internal = self.0.borrow_mut();
|
||||
let connectors = internal.connectors.clone();
|
||||
let logger = internal.logger.clone();
|
||||
|
||||
let (w, h) = mode.size();
|
||||
|
||||
internal
|
||||
.graphics
|
||||
.rent_all_mut(|graphics| -> Result<(), DrmError> {
|
||||
graphics.gbm.surface = Surface::try_new(
|
||||
Box::new(graphics.context.devices.gbm.create_surface(
|
||||
w as u32,
|
||||
h as u32,
|
||||
GbmFormat::XRGB8888,
|
||||
&[BufferObjectFlags::Scanout, BufferObjectFlags::Rendering],
|
||||
)?),
|
||||
|surface| {
|
||||
let egl_surface = graphics.context.egl.create_surface(&surface)?;
|
||||
unsafe { egl_surface.make_current()? };
|
||||
egl_surface.swap_buffers()?;
|
||||
|
||||
let mut front_bo = surface.lock_front_buffer()?;
|
||||
debug!(logger, "FrontBuffer color format: {:?}", front_bo.format());
|
||||
let fb = framebuffer::create(graphics.context.devices.drm, &*front_bo)?;
|
||||
crtc::set(
|
||||
graphics.context.devices.drm,
|
||||
crtc,
|
||||
fb.handle(),
|
||||
&connectors,
|
||||
(0, 0),
|
||||
Some(mode),
|
||||
)?;
|
||||
front_bo.set_userdata(fb);
|
||||
|
||||
Ok(EGL {
|
||||
surface: egl_surface,
|
||||
buffers: GbmBuffers {
|
||||
front_buffer: Cell::new(front_bo),
|
||||
next_buffer: Cell::new(None),
|
||||
},
|
||||
})
|
||||
},
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
internal.mode = mode;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn is(&self, id: Id) -> bool {
|
||||
self.0.borrow().own_id == id
|
||||
}
|
||||
}
|
||||
|
||||
impl GraphicsBackend for DrmBackend {
|
||||
type CursorFormat = ImageBuffer<Rgba<u8>, Vec<u8>>;
|
||||
type Error = DrmError;
|
||||
|
||||
fn set_cursor_position(&self, x: u32, y: u32) -> Result<(), DrmError> {
|
||||
crtc::move_cursor(
|
||||
self.0.borrow().graphics.head().head().head(),
|
||||
self.0.borrow().crtc,
|
||||
(x as i32, y as i32),
|
||||
).map_err(DrmError::from)
|
||||
}
|
||||
|
||||
fn set_cursor_representation(&self, buffer: ImageBuffer<Rgba<u8>, Vec<u8>>, hotspot: (u32, u32))
|
||||
-> Result<(), DrmError> {
|
||||
let (w, h) = buffer.dimensions();
|
||||
self.0
|
||||
.borrow_mut()
|
||||
.graphics
|
||||
.rent_all_mut(|graphics| -> Result<(), DrmError> {
|
||||
graphics.gbm.cursor = {
|
||||
let mut cursor = graphics.context.devices.gbm.create_buffer_object(
|
||||
w,
|
||||
h,
|
||||
GbmFormat::ARGB8888,
|
||||
&[BufferObjectFlags::Cursor, BufferObjectFlags::Write],
|
||||
)?;
|
||||
cursor.write(&*buffer.into_raw())?;
|
||||
cursor
|
||||
};
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
if crtc::set_cursor2(
|
||||
self.0.borrow().graphics.head().head().head(),
|
||||
self.0.borrow().crtc,
|
||||
self.0
|
||||
.borrow()
|
||||
.graphics
|
||||
.rent(|gbm| Buffer::handle(&gbm.cursor)),
|
||||
(w, h),
|
||||
(hotspot.0 as i32, hotspot.1 as i32),
|
||||
).is_err()
|
||||
{
|
||||
crtc::set_cursor(
|
||||
self.0.borrow().graphics.head().head().head(),
|
||||
self.0.borrow().crtc,
|
||||
self.0
|
||||
.borrow()
|
||||
.graphics
|
||||
.rent(|gbm| Buffer::handle(&gbm.cursor)),
|
||||
(w, h),
|
||||
).map_err(DrmError::from)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl EGLGraphicsBackend for DrmBackend {
|
||||
fn swap_buffers(&self) -> Result<(), SwapBuffersError> {
|
||||
self.0.borrow().graphics.rent_all(|graphics| {
|
||||
// We cannot call lock_front_buffer anymore without releasing the previous buffer, which will happen when the page flip is done
|
||||
if graphics.gbm.surface.rent(|egl| {
|
||||
let next = egl.buffers.next_buffer.take();
|
||||
let res = next.is_some();
|
||||
egl.buffers.next_buffer.set(next);
|
||||
res
|
||||
}) {
|
||||
return Err(SwapBuffersError::AlreadySwapped);
|
||||
}
|
||||
|
||||
graphics.gbm.surface.rent(|egl| egl.surface.swap_buffers())?;
|
||||
|
||||
graphics.gbm.surface.rent_all(|surface| {
|
||||
// supporting this would cause a lot of inconvinience and
|
||||
// would most likely result in a lot of flickering.
|
||||
// neither weston, wlc or wlroots bother with that as well.
|
||||
let mut next_bo = surface.gbm.lock_front_buffer().expect("Surface only has one front buffer. Not supported by smithay");
|
||||
|
||||
let maybe_fb = next_bo.userdata().cloned();
|
||||
let fb = if let Some(info) = maybe_fb {
|
||||
info
|
||||
} else {
|
||||
let fb = framebuffer::create(graphics.context.devices.drm, &*next_bo).map_err(|_| SwapBuffersError::ContextLost)?;
|
||||
next_bo.set_userdata(fb);
|
||||
fb
|
||||
};
|
||||
surface.egl.buffers.next_buffer.set(Some(next_bo));
|
||||
|
||||
trace!(self.0.borrow().logger, "Page flip queued");
|
||||
|
||||
let id: Id = self.0.borrow().own_id;
|
||||
|
||||
crtc::page_flip(graphics.context.devices.drm, self.0.borrow().crtc, fb.handle(), &[crtc::PageFlipFlags::PageFlipEvent], id).map_err(|_| SwapBuffersError::ContextLost)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
unsafe fn get_proc_address(&self, symbol: &str) -> *const c_void {
|
||||
self.0
|
||||
.borrow()
|
||||
.graphics
|
||||
.head()
|
||||
.rent(|context| context.get_proc_address(symbol))
|
||||
}
|
||||
|
||||
fn get_framebuffer_dimensions(&self) -> (u32, u32) {
|
||||
let (w, h) = self.0.borrow().mode.size();
|
||||
(w as u32, h as u32)
|
||||
}
|
||||
|
||||
fn is_current(&self) -> bool {
|
||||
self.0
|
||||
.borrow()
|
||||
.graphics
|
||||
.head()
|
||||
.rent(|context| context.is_current())
|
||||
}
|
||||
|
||||
unsafe fn make_current(&self) -> Result<(), SwapBuffersError> {
|
||||
self.0
|
||||
.borrow()
|
||||
.graphics
|
||||
.rent(|gbm| gbm.surface.rent(|egl| egl.surface.make_current()))
|
||||
}
|
||||
|
||||
fn get_pixel_format(&self) -> PixelFormat {
|
||||
self.0
|
||||
.borrow()
|
||||
.graphics
|
||||
.head()
|
||||
.rent(|context| context.get_pixel_format())
|
||||
}
|
||||
}
|
|
@ -0,0 +1,131 @@
|
|||
|
||||
|
||||
use backend::graphics::egl::{CreationError, SwapBuffersError};
|
||||
use drm::result::Error as DrmError;
|
||||
use gbm::FrontBufferError;
|
||||
use rental::TryNewError;
|
||||
|
||||
use std::error::{self, Error as ErrorTrait};
|
||||
use std::fmt;
|
||||
use std::io::Error as IoError;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
Drm(DrmError),
|
||||
EGLCreation(CreationError),
|
||||
EGLSwap(SwapBuffersError),
|
||||
Gbm(FrontBufferError),
|
||||
Io(IoError),
|
||||
Mode(ModeError),
|
||||
}
|
||||
|
||||
impl fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"DrmBackend error: {}",
|
||||
match self {
|
||||
&Error::Drm(ref x) => x as &error::Error,
|
||||
&Error::EGLCreation(ref x) => x as &error::Error,
|
||||
&Error::EGLSwap(ref x) => x as &error::Error,
|
||||
&Error::Gbm(ref x) => x as &error::Error,
|
||||
&Error::Io(ref x) => x as &error::Error,
|
||||
&Error::Mode(ref x) => x as &error::Error,
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl error::Error for Error {
|
||||
fn description(&self) -> &str {
|
||||
"DrmBackend error"
|
||||
}
|
||||
|
||||
fn cause(&self) -> Option<&error::Error> {
|
||||
match self {
|
||||
&Error::Drm(ref x) => Some(x as &error::Error),
|
||||
&Error::EGLCreation(ref x) => Some(x as &error::Error),
|
||||
&Error::EGLSwap(ref x) => Some(x as &error::Error),
|
||||
&Error::Gbm(ref x) => Some(x as &error::Error),
|
||||
&Error::Io(ref x) => Some(x as &error::Error),
|
||||
&Error::Mode(ref x) => Some(x as &error::Error),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<DrmError> for Error {
|
||||
fn from(err: DrmError) -> Error {
|
||||
Error::Drm(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<CreationError> for Error {
|
||||
fn from(err: CreationError) -> Error {
|
||||
Error::EGLCreation(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SwapBuffersError> for Error {
|
||||
fn from(err: SwapBuffersError) -> Error {
|
||||
Error::EGLSwap(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<FrontBufferError> for Error {
|
||||
fn from(err: FrontBufferError) -> Error {
|
||||
Error::Gbm(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<IoError> for Error {
|
||||
fn from(err: IoError) -> Error {
|
||||
Error::Io(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ModeError> for Error {
|
||||
fn from(err: ModeError) -> Error {
|
||||
match err {
|
||||
err @ ModeError::ModeNotSuitable => Error::Mode(err),
|
||||
ModeError::FailedToLoad(err) => Error::Drm(err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<H> From<TryNewError<Error, H>> for Error {
|
||||
fn from(err: TryNewError<Error, H>) -> Error {
|
||||
err.0
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ModeError {
|
||||
ModeNotSuitable,
|
||||
FailedToLoad(DrmError),
|
||||
}
|
||||
|
||||
impl fmt::Display for ModeError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{}", self.description())?;
|
||||
if let Some(cause) = self.cause() {
|
||||
write!(f, "\tCause: {}", cause)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl error::Error for ModeError {
|
||||
fn description(&self) -> &str {
|
||||
match self {
|
||||
&ModeError::ModeNotSuitable => "Mode does not match all attached connectors",
|
||||
&ModeError::FailedToLoad(_) => "Failed to load mode information from device",
|
||||
}
|
||||
}
|
||||
|
||||
fn cause(&self) -> Option<&error::Error> {
|
||||
match self {
|
||||
&ModeError::FailedToLoad(ref err) => Some(err as &error::Error),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,273 @@
|
|||
|
||||
|
||||
use backend::graphics::egl::{EGLContext, GlAttributes, PixelFormatRequirements};
|
||||
use drm::Device as BasicDevice;
|
||||
use drm::control::{connector, crtc, Mode};
|
||||
use drm::control::Device as ControlDevice;
|
||||
|
||||
use gbm::Device as GbmDevice;
|
||||
|
||||
use nix;
|
||||
|
||||
use std::cell::RefCell;
|
||||
use std::fs::File;
|
||||
use std::io::Error as IoError;
|
||||
use std::os::unix::io::{AsRawFd, RawFd};
|
||||
use std::rc::{Rc, Weak};
|
||||
use std::time::Duration;
|
||||
|
||||
use wayland_server::{EventLoopHandle};
|
||||
use wayland_server::sources::{FdEventSourceHandler, FdInterest};
|
||||
|
||||
mod backend;
|
||||
mod error;
|
||||
|
||||
pub use self::backend::{DrmBackend, Id};
|
||||
use self::backend::DrmBackendInternal;
|
||||
pub use self::error::{Error as DrmError, ModeError};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct DrmDev(File);
|
||||
|
||||
impl AsRawFd for DrmDev {
|
||||
fn as_raw_fd(&self) -> RawFd {
|
||||
self.0.as_raw_fd()
|
||||
}
|
||||
}
|
||||
impl BasicDevice for DrmDev {}
|
||||
impl ControlDevice for DrmDev {}
|
||||
|
||||
impl DrmDev {
|
||||
unsafe fn new_from_fd(fd: RawFd) -> Self {
|
||||
use std::os::unix::io::FromRawFd;
|
||||
DrmDev(File::from_raw_fd(fd))
|
||||
}
|
||||
|
||||
fn new_from_file(file: File) -> Self {
|
||||
DrmDev(file)
|
||||
}
|
||||
}
|
||||
|
||||
rental! {
|
||||
mod devices {
|
||||
use drm::control::framebuffer;
|
||||
use gbm::{Device as GbmDevice, Surface as GbmSurface};
|
||||
|
||||
use ::backend::graphics::egl::EGLContext;
|
||||
use super::DrmDev;
|
||||
|
||||
#[rental]
|
||||
pub(crate) struct Context {
|
||||
#[subrental(arity = 2)]
|
||||
devices: Box<Devices>,
|
||||
egl: EGLContext<'devices_1, GbmSurface<'devices_1, framebuffer::Info>>,
|
||||
}
|
||||
|
||||
#[rental]
|
||||
pub(crate) struct Devices {
|
||||
drm: Box<DrmDev>,
|
||||
gbm: GbmDevice<'drm>,
|
||||
}
|
||||
}
|
||||
}
|
||||
use self::devices::{Context, Devices};
|
||||
|
||||
|
||||
// odd naming, but makes sense for the user
|
||||
pub struct DrmDevice<H: DrmHandler + 'static> {
|
||||
context: Rc<Context>,
|
||||
backends: Vec<Weak<RefCell<DrmBackendInternal>>>,
|
||||
handler: Option<H>,
|
||||
logger: ::slog::Logger,
|
||||
}
|
||||
|
||||
impl<H: DrmHandler + 'static> DrmDevice<H> {
|
||||
pub unsafe fn new_from_fd<L>(fd: RawFd, logger: L) -> Result<Self, DrmError>
|
||||
where
|
||||
L: Into<Option<::slog::Logger>>,
|
||||
{
|
||||
DrmDevice::new(
|
||||
DrmDev::new_from_fd(fd),
|
||||
GlAttributes {
|
||||
version: None,
|
||||
profile: None,
|
||||
debug: cfg!(debug_assertions),
|
||||
vsync: true,
|
||||
},
|
||||
logger,
|
||||
)
|
||||
}
|
||||
|
||||
pub unsafe fn new_from_fd_with_gl_attr<L>(fd: RawFd, attributes: GlAttributes, logger: L)
|
||||
-> Result<Self, DrmError>
|
||||
where
|
||||
L: Into<Option<::slog::Logger>>,
|
||||
{
|
||||
DrmDevice::new(DrmDev::new_from_fd(fd), attributes, logger)
|
||||
}
|
||||
|
||||
pub fn new_from_file<L>(file: File, logger: L) -> Result<Self, DrmError>
|
||||
where
|
||||
L: Into<Option<::slog::Logger>>,
|
||||
{
|
||||
DrmDevice::new(
|
||||
DrmDev::new_from_file(file),
|
||||
GlAttributes {
|
||||
version: None,
|
||||
profile: None,
|
||||
debug: cfg!(debug_assertions),
|
||||
vsync: true,
|
||||
},
|
||||
logger,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn new_from_file_with_gl_attr<L>(file: File, attributes: GlAttributes, logger: L)
|
||||
-> Result<Self, DrmError>
|
||||
where
|
||||
L: Into<Option<::slog::Logger>>,
|
||||
{
|
||||
DrmDevice::new(DrmDev::new_from_file(file), attributes, logger)
|
||||
}
|
||||
|
||||
fn new<L>(drm: DrmDev, attributes: GlAttributes, logger: L) -> Result<Self, DrmError>
|
||||
where
|
||||
L: Into<Option<::slog::Logger>>,
|
||||
{
|
||||
let log = ::slog_or_stdlog(logger).new(o!("smithay_module" => "backend_drm", "drm" => "device"));
|
||||
|
||||
/* GBM will load a dri driver, but even though they need symbols from
|
||||
* libglapi, in some version of Mesa they are not linked to it. Since
|
||||
* only the gl-renderer module links to it, these symbols won't be globally available,
|
||||
* and loading the DRI driver fails.
|
||||
* Workaround this by dlopen()'ing libglapi with RTLD_GLOBAL.
|
||||
*/
|
||||
unsafe {
|
||||
nix::libc::dlopen(
|
||||
"libglapi.so.0".as_ptr() as *const _,
|
||||
nix::libc::RTLD_LAZY | nix::libc::RTLD_GLOBAL,
|
||||
);
|
||||
}
|
||||
|
||||
Ok(DrmDevice {
|
||||
context: Rc::new(Context::try_new(
|
||||
Box::new(Devices::try_new(Box::new(drm), |drm| {
|
||||
GbmDevice::new_from_drm::<DrmDevice<H>>(drm).map_err(DrmError::from)
|
||||
})?),
|
||||
|devices| {
|
||||
EGLContext::new_from_gbm(
|
||||
devices.gbm,
|
||||
attributes,
|
||||
PixelFormatRequirements {
|
||||
hardware_accelerated: Some(true),
|
||||
color_bits: Some(24),
|
||||
alpha_bits: Some(8),
|
||||
..Default::default()
|
||||
},
|
||||
log.clone(),
|
||||
).map_err(DrmError::from)
|
||||
},
|
||||
)?),
|
||||
backends: Vec::new(),
|
||||
handler: None,
|
||||
logger: log,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn create_backend<I>(&mut self, crtc: crtc::Handle, mode: Mode, connectors: I)
|
||||
-> Result<DrmBackend, DrmError>
|
||||
where
|
||||
I: Into<Vec<connector::Handle>>,
|
||||
{
|
||||
let logger = self.logger
|
||||
.new(o!("drm" => "backend", "crtc" => format!("{:?}", crtc)));
|
||||
let own_id = self.backends.len();
|
||||
|
||||
let backend = Rc::new(RefCell::new(DrmBackendInternal::new(
|
||||
self.context.clone(),
|
||||
crtc,
|
||||
mode,
|
||||
connectors,
|
||||
own_id,
|
||||
logger,
|
||||
)?));
|
||||
|
||||
self.backends.push(Rc::downgrade(&backend));
|
||||
|
||||
Ok(DrmBackend::new(backend))
|
||||
}
|
||||
|
||||
pub fn set_handler(&mut self, handler: H) -> Option<H> {
|
||||
let res = self.handler.take();
|
||||
self.handler = Some(handler);
|
||||
res
|
||||
}
|
||||
|
||||
pub fn clear_handler(&mut self) -> Option<H> {
|
||||
self.handler.take()
|
||||
}
|
||||
}
|
||||
|
||||
// for users convinience
|
||||
impl<H: DrmHandler + 'static> AsRawFd for DrmDevice<H> {
|
||||
fn as_raw_fd(&self) -> RawFd {
|
||||
self.context.head().head().as_raw_fd()
|
||||
}
|
||||
}
|
||||
impl<H: DrmHandler + 'static> BasicDevice for DrmDevice<H> {}
|
||||
impl<H: DrmHandler + 'static> ControlDevice for DrmDevice<H> {}
|
||||
|
||||
pub trait DrmHandler {
|
||||
fn ready(&mut self, evlh: &mut EventLoopHandle, id: Id, frame: u32, duration: Duration);
|
||||
fn error(&mut self, evlh: &mut EventLoopHandle, error: IoError);
|
||||
}
|
||||
|
||||
impl<H: DrmHandler + 'static> FdEventSourceHandler for DrmDevice<H> {
|
||||
fn ready(&mut self, evlh: &mut EventLoopHandle, fd: RawFd, _mask: FdInterest) {
|
||||
use std::any::Any;
|
||||
|
||||
struct DrmDeviceRef(RawFd);
|
||||
impl AsRawFd for DrmDeviceRef {
|
||||
fn as_raw_fd(&self) -> RawFd {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
impl BasicDevice for DrmDeviceRef {}
|
||||
impl ControlDevice for DrmDeviceRef {}
|
||||
|
||||
struct PageFlipHandler<'a, 'b, H: DrmHandler + 'static>(
|
||||
&'a mut DrmDevice<H>,
|
||||
&'b mut EventLoopHandle,
|
||||
);
|
||||
|
||||
impl<'a, 'b, H: DrmHandler + 'static> crtc::PageFlipHandler<DrmDeviceRef> for PageFlipHandler<'a, 'b, H> {
|
||||
fn handle_event(&mut self, _device: &DrmDeviceRef, frame: u32, duration: Duration,
|
||||
userdata: Box<Any>) {
|
||||
let id: Id = *userdata.downcast().unwrap();
|
||||
if let Some(backend) = self.0.backends[id.raw()].upgrade() {
|
||||
backend.borrow().unlock_buffer();
|
||||
if let Some(handler) = self.0.handler.as_mut() {
|
||||
handler.ready(self.1, id, frame, duration);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
crtc::handle_event(
|
||||
&DrmDeviceRef(fd),
|
||||
2,
|
||||
None::<&mut ()>,
|
||||
Some(&mut PageFlipHandler(
|
||||
self,
|
||||
evlh,
|
||||
)),
|
||||
None::<&mut ()>,
|
||||
).unwrap();
|
||||
}
|
||||
|
||||
fn error(&mut self, evlh: &mut EventLoopHandle, _fd: RawFd, error: IoError) {
|
||||
if let Some(handler) = self.handler.as_mut() {
|
||||
handler.error(evlh, error)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -7,19 +7,29 @@
|
|||
/// (see https://github.com/tomaka/glutin/tree/044e651edf67a2029eecc650dd42546af1501414/LICENSE)
|
||||
|
||||
use super::GraphicsBackend;
|
||||
#[cfg(feature = "backend_drm")]
|
||||
use gbm::{AsRaw, Device as GbmDevice, Surface as GbmSurface};
|
||||
|
||||
use libloading::Library;
|
||||
use nix::{c_int, c_void};
|
||||
use slog;
|
||||
use std::error::{self, Error};
|
||||
|
||||
use std::error::{self, Error};
|
||||
use std::ffi::{CStr, CString};
|
||||
use std::fmt;
|
||||
use std::io;
|
||||
use std::marker::PhantomData;
|
||||
use std::mem;
|
||||
use std::ops::Deref;
|
||||
use std::ops::{Deref, DerefMut};
|
||||
use std::ptr;
|
||||
|
||||
#[cfg(feature = "backend_winit")]
|
||||
use wayland_client::egl as wegl;
|
||||
#[cfg(feature = "backend_winit")]
|
||||
use winit::Window as WinitWindow;
|
||||
#[cfg(feature = "backend_winit")]
|
||||
use winit::os::unix::WindowExt;
|
||||
|
||||
#[allow(non_camel_case_types, dead_code)]
|
||||
mod ffi {
|
||||
use nix::c_void;
|
||||
|
@ -46,7 +56,7 @@ mod ffi {
|
|||
/// Native types to create an `EGLContext` from.
|
||||
/// Currently supported providers are X11, Wayland and GBM.
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum NativeDisplay {
|
||||
enum NativeDisplayPtr {
|
||||
/// X11 Display to create an `EGLContext` upon.
|
||||
X11(ffi::NativeDisplayType),
|
||||
/// Wayland Display to create an `EGLContext` upon.
|
||||
|
@ -58,7 +68,7 @@ pub enum NativeDisplay {
|
|||
/// Native types to create an `EGLSurface` from.
|
||||
/// Currently supported providers are X11, Wayland and GBM.
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum NativeSurface {
|
||||
pub enum NativeSurfacePtr {
|
||||
/// X11 Window to create an `EGLSurface` upon.
|
||||
X11(ffi::NativeWindowType),
|
||||
/// Wayland Surface to create an `EGLSurface` upon.
|
||||
|
@ -68,7 +78,7 @@ pub enum NativeSurface {
|
|||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
enum NativeType {
|
||||
pub enum NativeType {
|
||||
X11,
|
||||
Wayland,
|
||||
Gbm,
|
||||
|
@ -135,8 +145,61 @@ impl error::Error for CreationError {
|
|||
}
|
||||
}
|
||||
|
||||
pub unsafe trait NativeSurface {
|
||||
type Keep: 'static;
|
||||
|
||||
fn surface(&self, backend: NativeType) -> Result<(NativeSurfacePtr, Self::Keep), CreationError>;
|
||||
}
|
||||
|
||||
#[cfg(feature = "backend_winit")]
|
||||
unsafe impl NativeSurface for WinitWindow {
|
||||
type Keep = Option<wegl::WlEglSurface>;
|
||||
|
||||
fn surface(&self, backend_type: NativeType)
|
||||
-> Result<(NativeSurfacePtr, Option<wegl::WlEglSurface>), CreationError> {
|
||||
match backend_type {
|
||||
NativeType::X11 => if let Some(window) = self.get_xlib_window() {
|
||||
Ok((NativeSurfacePtr::X11(window), None))
|
||||
} else {
|
||||
return Err(CreationError::NonMatchingSurfaceType);
|
||||
},
|
||||
NativeType::Wayland => if let Some(surface) = self.get_wayland_surface() {
|
||||
let (w, h) = self.get_inner_size().unwrap();
|
||||
let egl_surface =
|
||||
unsafe { wegl::WlEglSurface::new_from_raw(surface as *mut _, w as i32, h as i32) };
|
||||
Ok((
|
||||
NativeSurfacePtr::Wayland(egl_surface.ptr() as *const _),
|
||||
Some(egl_surface),
|
||||
))
|
||||
} else {
|
||||
return Err(CreationError::NonMatchingSurfaceType);
|
||||
},
|
||||
_ => Err(CreationError::NonMatchingSurfaceType),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "backend_drm")]
|
||||
unsafe impl<'a, T: 'static> NativeSurface for GbmSurface<'a, T> {
|
||||
type Keep = ();
|
||||
|
||||
fn surface(&self, backend: NativeType) -> Result<(NativeSurfacePtr, Self::Keep), CreationError> {
|
||||
match backend {
|
||||
NativeType::Gbm => Ok((NativeSurfacePtr::Gbm(self.as_raw() as *const _), ())),
|
||||
_ => Err(CreationError::NonMatchingSurfaceType),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl NativeSurface for () {
|
||||
type Keep = ();
|
||||
fn surface(&self, _backend: NativeType) -> Result<(NativeSurfacePtr, ()), CreationError> {
|
||||
Err(CreationError::NotSupported)
|
||||
}
|
||||
}
|
||||
|
||||
/// EGL context for rendering
|
||||
pub struct EGLContext {
|
||||
pub struct EGLContext<'a, T: NativeSurface> {
|
||||
_lib: Library,
|
||||
context: ffi::egl::types::EGLContext,
|
||||
display: ffi::egl::types::EGLDisplay,
|
||||
|
@ -146,38 +209,67 @@ pub struct EGLContext {
|
|||
pixel_format: PixelFormat,
|
||||
backend_type: NativeType,
|
||||
logger: slog::Logger,
|
||||
_lifetime: PhantomData<&'a ()>,
|
||||
_type: PhantomData<T>,
|
||||
}
|
||||
|
||||
/// EGL surface of a given egl context for rendering
|
||||
pub struct EGLSurface<'a> {
|
||||
context: &'a EGLContext,
|
||||
surface: ffi::egl::types::EGLSurface,
|
||||
}
|
||||
|
||||
impl<'a> Deref for EGLSurface<'a> {
|
||||
type Target = EGLContext;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
self.context
|
||||
}
|
||||
}
|
||||
|
||||
impl EGLContext {
|
||||
/// Create a new EGL context
|
||||
///
|
||||
/// # Unsafety
|
||||
///
|
||||
/// This method is marked unsafe, because the contents of `NativeDisplay` cannot be verified and may
|
||||
/// contain dangling pointers or similar unsafe content
|
||||
pub unsafe fn new<L>(native: NativeDisplay, mut attributes: GlAttributes, reqs: PixelFormatRequirements,
|
||||
logger: L)
|
||||
-> Result<EGLContext, CreationError>
|
||||
impl<'a> EGLContext<'a, ()> {
|
||||
#[cfg(feature = "backend_winit")]
|
||||
pub fn new_from_winit<L>(window: &'a WinitWindow, attributes: GlAttributes,
|
||||
reqs: PixelFormatRequirements, logger: L)
|
||||
-> Result<EGLContext<'a, WinitWindow>, CreationError>
|
||||
where
|
||||
L: Into<Option<::slog::Logger>>,
|
||||
{
|
||||
let logger = logger.into();
|
||||
let log = ::slog_or_stdlog(logger.clone()).new(o!("smithay_module" => "renderer_egl"));
|
||||
trace!(log, "Loading libEGL");
|
||||
let log = ::slog_or_stdlog(logger.into()).new(o!("smithay_module" => "renderer_egl"));
|
||||
info!(log, "Initializing from winit window");
|
||||
|
||||
unsafe {
|
||||
EGLContext::new(
|
||||
if let Some(display) = window.get_xlib_display() {
|
||||
debug!(log, "Window is backed by X11");
|
||||
NativeDisplayPtr::X11(display)
|
||||
} else if let Some(display) = window.get_wayland_display() {
|
||||
debug!(log, "Window is backed by Wayland");
|
||||
NativeDisplayPtr::Wayland(display)
|
||||
} else {
|
||||
error!(log, "Window is backed by an unsupported graphics framework");
|
||||
return Err(CreationError::NotSupported);
|
||||
},
|
||||
attributes,
|
||||
reqs,
|
||||
log,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "backend_drm")]
|
||||
pub fn new_from_gbm<L, U: 'static>(gbm: &'a GbmDevice<'a>, attributes: GlAttributes,
|
||||
reqs: PixelFormatRequirements, logger: L)
|
||||
-> Result<EGLContext<'a, GbmSurface<'a, U>>, CreationError>
|
||||
where
|
||||
L: Into<Option<::slog::Logger>>,
|
||||
{
|
||||
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<EGLContext<'a, T>, CreationError>
|
||||
where
|
||||
T: NativeSurface,
|
||||
{
|
||||
// If no version is given, try OpenGLES 3.0, if available,
|
||||
// fallback to 2.0 otherwise
|
||||
let version = match attributes.version {
|
||||
|
@ -186,13 +278,13 @@ impl EGLContext {
|
|||
None => {
|
||||
debug!(log, "Trying to initialize EGL with OpenGLES 3.0");
|
||||
attributes.version = Some((3, 0));
|
||||
match EGLContext::new(native, attributes, reqs, logger.clone()) {
|
||||
match EGLContext::new(native, attributes, reqs, log.clone()) {
|
||||
Ok(x) => return Ok(x),
|
||||
Err(err) => {
|
||||
warn!(log, "EGL OpenGLES 3.0 Initialization failed with {}", err);
|
||||
debug!(log, "Trying to initialize EGL with OpenGLES 2.0");
|
||||
attributes.version = Some((2, 0));
|
||||
return EGLContext::new(native, attributes, reqs, logger);
|
||||
return EGLContext::new(native, attributes, reqs, log);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -213,6 +305,7 @@ impl EGLContext {
|
|||
}
|
||||
};
|
||||
|
||||
trace!(log, "Loading libEGL");
|
||||
let lib = Library::new("libEGL.so.1")?;
|
||||
let egl = ffi::egl::Egl::load_with(|sym| {
|
||||
let name = CString::new(sym).unwrap();
|
||||
|
@ -243,35 +336,42 @@ impl EGLContext {
|
|||
let has_dp_extension = |e: &str| dp_extensions.iter().any(|s| s == e);
|
||||
|
||||
let display = match native {
|
||||
NativeDisplay::X11(display)
|
||||
NativeDisplayPtr::X11(display)
|
||||
if has_dp_extension("EGL_KHR_platform_x11") && egl.GetPlatformDisplay.is_loaded() =>
|
||||
{
|
||||
trace!(log, "EGL Display Initialization via EGL_KHR_platform_x11");
|
||||
egl.GetPlatformDisplay(ffi::egl::PLATFORM_X11_KHR, display as *mut _, ptr::null())
|
||||
}
|
||||
|
||||
NativeDisplay::X11(display)
|
||||
NativeDisplayPtr::X11(display)
|
||||
if has_dp_extension("EGL_EXT_platform_x11") && egl.GetPlatformDisplayEXT.is_loaded() =>
|
||||
{
|
||||
trace!(log, "EGL Display Initialization via EGL_EXT_platform_x11");
|
||||
egl.GetPlatformDisplayEXT(ffi::egl::PLATFORM_X11_EXT, display as *mut _, ptr::null())
|
||||
}
|
||||
|
||||
NativeDisplay::Gbm(display)
|
||||
NativeDisplayPtr::Gbm(display)
|
||||
if has_dp_extension("EGL_KHR_platform_gbm") && egl.GetPlatformDisplay.is_loaded() =>
|
||||
{
|
||||
trace!(log, "EGL Display Initialization via EGL_KHR_platform_gbm");
|
||||
egl.GetPlatformDisplay(ffi::egl::PLATFORM_GBM_KHR, display as *mut _, ptr::null())
|
||||
}
|
||||
|
||||
NativeDisplay::Gbm(display)
|
||||
NativeDisplayPtr::Gbm(display)
|
||||
if has_dp_extension("EGL_MESA_platform_gbm") && egl.GetPlatformDisplayEXT.is_loaded() =>
|
||||
{
|
||||
trace!(log, "EGL Display Initialization via EGL_MESA_platform_gbm");
|
||||
egl.GetPlatformDisplayEXT(ffi::egl::PLATFORM_GBM_KHR, display as *mut _, ptr::null())
|
||||
egl.GetPlatformDisplayEXT(ffi::egl::PLATFORM_GBM_MESA, display as *mut _, ptr::null())
|
||||
}
|
||||
|
||||
NativeDisplay::Wayland(display)
|
||||
NativeDisplayPtr::Gbm(display)
|
||||
if has_dp_extension("EGL_MESA_platform_gbm") && egl.GetPlatformDisplay.is_loaded() =>
|
||||
{
|
||||
trace!(log, "EGL Display Initialization via EGL_MESA_platform_gbm");
|
||||
egl.GetPlatformDisplay(ffi::egl::PLATFORM_GBM_MESA, display as *mut _, ptr::null())
|
||||
}
|
||||
|
||||
NativeDisplayPtr::Wayland(display)
|
||||
if has_dp_extension("EGL_KHR_platform_wayland") && egl.GetPlatformDisplay.is_loaded() =>
|
||||
{
|
||||
trace!(
|
||||
|
@ -285,7 +385,7 @@ impl EGLContext {
|
|||
)
|
||||
}
|
||||
|
||||
NativeDisplay::Wayland(display)
|
||||
NativeDisplayPtr::Wayland(display)
|
||||
if has_dp_extension("EGL_EXT_platform_wayland") && egl.GetPlatformDisplayEXT.is_loaded() =>
|
||||
{
|
||||
trace!(
|
||||
|
@ -299,12 +399,18 @@ impl EGLContext {
|
|||
)
|
||||
}
|
||||
|
||||
NativeDisplay::X11(display) | NativeDisplay::Gbm(display) | NativeDisplay::Wayland(display) => {
|
||||
NativeDisplayPtr::X11(display) |
|
||||
NativeDisplayPtr::Gbm(display) |
|
||||
NativeDisplayPtr::Wayland(display) => {
|
||||
trace!(log, "Default EGL Display Initialization via GetDisplay");
|
||||
egl.GetDisplay(display as *mut _)
|
||||
}
|
||||
};
|
||||
|
||||
if display == ffi::egl::NO_DISPLAY {
|
||||
error!(log, "EGL Display is not valid");
|
||||
}
|
||||
|
||||
let egl_version = {
|
||||
let mut major: ffi::egl::types::EGLint = mem::uninitialized();
|
||||
let mut minor: ffi::egl::types::EGLint = mem::uninitialized();
|
||||
|
@ -583,11 +689,13 @@ impl EGLContext {
|
|||
surface_attributes: surface_attributes,
|
||||
pixel_format: desc,
|
||||
backend_type: match native {
|
||||
NativeDisplay::X11(_) => NativeType::X11,
|
||||
NativeDisplay::Wayland(_) => NativeType::Wayland,
|
||||
NativeDisplay::Gbm(_) => NativeType::Gbm,
|
||||
NativeDisplayPtr::X11(_) => NativeType::X11,
|
||||
NativeDisplayPtr::Wayland(_) => NativeType::Wayland,
|
||||
NativeDisplayPtr::Gbm(_) => NativeType::Gbm,
|
||||
},
|
||||
logger: log,
|
||||
_lifetime: PhantomData,
|
||||
_type: PhantomData,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -597,37 +705,37 @@ impl EGLContext {
|
|||
///
|
||||
/// This method is marked unsafe, because the contents of `NativeSurface` cannot be verified and may
|
||||
/// contain dangling pointers or similar unsafe content
|
||||
pub unsafe fn create_surface<'a>(&'a self, native: NativeSurface)
|
||||
-> Result<EGLSurface<'a>, CreationError> {
|
||||
pub fn create_surface<'b>(&'a self, native: &'b T) -> Result<EGLSurface<'a, 'b, T>, CreationError> {
|
||||
trace!(self.logger, "Creating EGL window surface...");
|
||||
|
||||
let surface = {
|
||||
let surface = match (native, self.backend_type) {
|
||||
(NativeSurface::X11(window), NativeType::X11) |
|
||||
(NativeSurface::Wayland(window), NativeType::Wayland) |
|
||||
(NativeSurface::Gbm(window), NativeType::Gbm) => self.egl.CreateWindowSurface(
|
||||
let (surface, keep) = native.surface(self.backend_type)?;
|
||||
|
||||
let egl_surface = unsafe {
|
||||
self.egl.CreateWindowSurface(
|
||||
self.display,
|
||||
self.config_id,
|
||||
window,
|
||||
match surface {
|
||||
NativeSurfacePtr::X11(ptr) => ptr,
|
||||
NativeSurfacePtr::Wayland(ptr) => ptr,
|
||||
NativeSurfacePtr::Gbm(ptr) => ptr,
|
||||
},
|
||||
self.surface_attributes.as_ptr(),
|
||||
),
|
||||
_ => return Err(CreationError::NonMatchingSurfaceType),
|
||||
)
|
||||
};
|
||||
|
||||
if surface.is_null() {
|
||||
if egl_surface.is_null() {
|
||||
return Err(CreationError::OsError(
|
||||
String::from("eglCreateWindowSurface failed"),
|
||||
));
|
||||
}
|
||||
|
||||
surface
|
||||
};
|
||||
|
||||
debug!(self.logger, "EGL window surface successfully created");
|
||||
debug!(self.logger, "EGL surface successfully created");
|
||||
|
||||
Ok(EGLSurface {
|
||||
context: &self,
|
||||
surface: surface,
|
||||
surface: egl_surface,
|
||||
keep,
|
||||
_lifetime_surface: PhantomData,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -651,7 +759,43 @@ impl EGLContext {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'a> EGLSurface<'a> {
|
||||
unsafe impl<'a, T: NativeSurface> Send for EGLContext<'a, T> {}
|
||||
unsafe impl<'a, T: NativeSurface> Sync for EGLContext<'a, T> {}
|
||||
|
||||
impl<'a, T: NativeSurface> Drop for EGLContext<'a, T> {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
// we don't call MakeCurrent(0, 0) because we are not sure that the context
|
||||
// is still the current one
|
||||
self.egl
|
||||
.DestroyContext(self.display as *const _, self.context as *const _);
|
||||
self.egl.Terminate(self.display as *const _);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// EGL surface of a given egl context for rendering
|
||||
pub struct EGLSurface<'context, 'surface, T: NativeSurface + 'context> {
|
||||
context: &'context EGLContext<'context, T>,
|
||||
surface: ffi::egl::types::EGLSurface,
|
||||
keep: T::Keep,
|
||||
_lifetime_surface: PhantomData<&'surface ()>,
|
||||
}
|
||||
|
||||
impl<'a, 'b, T: NativeSurface> Deref for EGLSurface<'a, 'b, T> {
|
||||
type Target = T::Keep;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.keep
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b, T: NativeSurface> DerefMut for EGLSurface<'a, 'b, T> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.keep
|
||||
}
|
||||
}
|
||||
|
||||
impl<'context, 'surface, T: NativeSurface> EGLSurface<'context, 'surface, T> {
|
||||
/// Swaps buffers at the end of a frame.
|
||||
pub fn swap_buffers(&self) -> Result<(), SwapBuffersError> {
|
||||
let ret = unsafe {
|
||||
|
@ -695,24 +839,10 @@ impl<'a> EGLSurface<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
unsafe impl Send for EGLContext {}
|
||||
unsafe impl Sync for EGLContext {}
|
||||
unsafe impl<'a> Send for EGLSurface<'a> {}
|
||||
unsafe impl<'a> Sync for EGLSurface<'a> {}
|
||||
unsafe impl<'a, 'b, T: NativeSurface> Send for EGLSurface<'a, 'b, T> {}
|
||||
unsafe impl<'a, 'b, T: NativeSurface> Sync for EGLSurface<'a, 'b, T> {}
|
||||
|
||||
impl Drop for EGLContext {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
// we don't call MakeCurrent(0, 0) because we are not sure that the context
|
||||
// is still the current one
|
||||
self.egl
|
||||
.DestroyContext(self.display as *const _, self.context as *const _);
|
||||
self.egl.Terminate(self.display as *const _);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Drop for EGLSurface<'a> {
|
||||
impl<'a, 'b, T: NativeSurface> Drop for EGLSurface<'a, 'b, T> {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
self.context
|
||||
|
@ -744,6 +874,27 @@ pub enum SwapBuffersError {
|
|||
AlreadySwapped,
|
||||
}
|
||||
|
||||
impl fmt::Display for SwapBuffersError {
|
||||
fn fmt(&self, formatter: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
||||
write!(formatter, "{}", self.description())
|
||||
}
|
||||
}
|
||||
|
||||
impl error::Error for SwapBuffersError {
|
||||
fn description(&self) -> &str {
|
||||
match self {
|
||||
&SwapBuffersError::ContextLost => "The context has been lost, it needs to be recreated",
|
||||
&SwapBuffersError::AlreadySwapped => {
|
||||
"Buffers are already swapped, swap_buffers was called too many times"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn cause(&self) -> Option<&error::Error> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Attributes to use when creating an OpenGL context.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct GlAttributes {
|
||||
|
|
|
@ -34,7 +34,7 @@ impl<T: EGLGraphicsBackend + 'static> GliumGraphicsBackend<T> {
|
|||
GliumGraphicsBackend {
|
||||
// cannot fail
|
||||
context: unsafe {
|
||||
Context::new::<_, ()>(internal.clone(), false, DebugCallbackBehavior::default()).unwrap()
|
||||
Context::new(internal.clone(), true, DebugCallbackBehavior::default()).unwrap()
|
||||
},
|
||||
backend: internal,
|
||||
}
|
||||
|
@ -56,10 +56,10 @@ impl<T: EGLGraphicsBackend + 'static> GliumGraphicsBackend<T> {
|
|||
}
|
||||
|
||||
impl<T: EGLGraphicsBackend> Deref for GliumGraphicsBackend<T> {
|
||||
type Target = Context;
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &Context {
|
||||
&self.context
|
||||
fn deref(&self) -> &T {
|
||||
&self.backend.0
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -8,6 +8,8 @@ pub trait GraphicsBackend {
|
|||
/// Format representing the image drawn for the cursor.
|
||||
type CursorFormat;
|
||||
|
||||
type Error;
|
||||
|
||||
/// Sets the cursor position and therefor updates the drawn cursors position.
|
||||
/// Useful as well for e.g. pointer wrapping.
|
||||
///
|
||||
|
@ -19,14 +21,15 @@ pub trait GraphicsBackend {
|
|||
/// by the higher compositor and not by the backend. It is still good practice to update
|
||||
/// the position after every recieved event, but don't rely on pointer wrapping working.
|
||||
///
|
||||
fn set_cursor_position(&mut self, x: u32, y: u32) -> Result<(), ()>;
|
||||
fn set_cursor_position(&self, x: u32, y: u32) -> Result<(), Self::Error>;
|
||||
|
||||
/// Set the cursor drawn on the `GraphicsBackend`.
|
||||
///
|
||||
/// The format is entirely dictated by the concrete implementation and might range
|
||||
/// from raw image buffers over a fixed list of possible cursor types to simply the
|
||||
/// void type () to represent no possible customization of the cursor itself.
|
||||
fn set_cursor_representation(&mut self, cursor: Self::CursorFormat);
|
||||
fn set_cursor_representation(&self, cursor: Self::CursorFormat, hotspot: (u32, u32))
|
||||
-> Result<(), Self::Error>;
|
||||
}
|
||||
|
||||
pub mod software;
|
||||
|
|
|
@ -18,6 +18,8 @@ pub mod graphics;
|
|||
|
||||
#[cfg(feature = "backend_winit")]
|
||||
pub mod winit;
|
||||
#[cfg(feature = "backend_drm")]
|
||||
pub mod drm;
|
||||
#[cfg(feature = "backend_libinput")]
|
||||
pub mod libinput;
|
||||
|
||||
|
|
|
@ -2,8 +2,8 @@
|
|||
|
||||
use backend::{SeatInternal, TouchSlotInternal};
|
||||
use backend::graphics::GraphicsBackend;
|
||||
use backend::graphics::egl::{CreationError, EGLContext, EGLGraphicsBackend, GlAttributes, NativeDisplay,
|
||||
NativeSurface, PixelFormat, PixelFormatRequirements, SwapBuffersError};
|
||||
use backend::graphics::egl::{CreationError, EGLContext, EGLGraphicsBackend, GlAttributes, PixelFormat,
|
||||
PixelFormatRequirements, SwapBuffersError};
|
||||
use backend::input::{Axis, AxisSource, Event as BackendEvent, InputBackend, InputHandler, KeyState,
|
||||
KeyboardKeyEvent, MouseButton, MouseButtonState, PointerAxisEvent, PointerButtonEvent,
|
||||
PointerMotionAbsoluteEvent, Seat, SeatCapabilities, TouchCancelEvent, TouchDownEvent,
|
||||
|
@ -14,31 +14,36 @@ use std::cmp;
|
|||
use std::error::Error;
|
||||
use std::fmt;
|
||||
use std::rc::Rc;
|
||||
use wayland_client::egl as wegl;
|
||||
use winit::{CreationError as WinitCreationError, ElementState, Event, EventsLoop, KeyboardInput,
|
||||
MouseButton as WinitMouseButton, MouseCursor, MouseScrollDelta, Touch, TouchPhase, Window,
|
||||
MouseButton as WinitMouseButton, MouseCursor, MouseScrollDelta, Touch, TouchPhase,
|
||||
WindowBuilder, WindowEvent};
|
||||
use winit::os::unix::{WindowExt, get_x11_xconnection};
|
||||
|
||||
rental! {
|
||||
mod egl {
|
||||
mod rental {
|
||||
use std::boxed::Box;
|
||||
use ::winit::{Window as WinitWindow};
|
||||
use ::backend::graphics::egl::{EGLContext, EGLSurface};
|
||||
|
||||
#[rental]
|
||||
pub struct Window {
|
||||
window: Box<WinitWindow>,
|
||||
egl: EGL<'window>,
|
||||
}
|
||||
|
||||
#[rental(deref_suffix)]
|
||||
pub struct RentEGL {
|
||||
context: Box<EGLContext>,
|
||||
surface: EGLSurface<'context>,
|
||||
#[rental]
|
||||
pub struct EGL<'a> {
|
||||
context: Box<EGLContext<'a, WinitWindow>>,
|
||||
surface: EGLSurface<'context, 'a, WinitWindow>,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
use self::rental::{Window, EGL};
|
||||
|
||||
/// Window with an active EGL Context created by `winit`. Implements the
|
||||
/// `EGLGraphicsBackend` graphics backend trait
|
||||
pub struct WinitGraphicsBackend {
|
||||
window: Rc<Window>,
|
||||
context: egl::RentEGL,
|
||||
logger: ::slog::Logger,
|
||||
}
|
||||
|
||||
|
@ -48,7 +53,6 @@ pub struct WinitGraphicsBackend {
|
|||
pub struct WinitInputBackend {
|
||||
events_loop: EventsLoop,
|
||||
window: Rc<Window>,
|
||||
surface: Option<wegl::WlEglSurface>,
|
||||
time_counter: u32,
|
||||
key_counter: u32,
|
||||
seat: Seat,
|
||||
|
@ -107,37 +111,13 @@ where
|
|||
info!(log, "Initializing a winit backend");
|
||||
|
||||
let events_loop = EventsLoop::new();
|
||||
let window = Rc::new(builder.build(&events_loop)?);
|
||||
let winit_window = builder.build(&events_loop)?;
|
||||
debug!(log, "Window created");
|
||||
|
||||
let (native_display, native_surface, surface) = if let (Some(conn), Some(window)) =
|
||||
(get_x11_xconnection(), window.get_xlib_window())
|
||||
{
|
||||
debug!(log, "Window is backed by X11");
|
||||
(
|
||||
NativeDisplay::X11(conn.display as *const _),
|
||||
NativeSurface::X11(window),
|
||||
None,
|
||||
)
|
||||
} else if let (Some(display), Some(surface)) =
|
||||
(window.get_wayland_display(), window.get_wayland_surface())
|
||||
{
|
||||
debug!(log, "Window is backed by Wayland");
|
||||
let (w, h) = window.get_inner_size().unwrap();
|
||||
let egl_surface = unsafe { wegl::WlEglSurface::new_from_raw(surface as *mut _, w as i32, h as i32) };
|
||||
(
|
||||
NativeDisplay::Wayland(display),
|
||||
NativeSurface::Wayland(egl_surface.ptr() as *const _),
|
||||
Some(egl_surface),
|
||||
)
|
||||
} else {
|
||||
error!(log, "Window is backed by an unsupported graphics framework");
|
||||
return Err(CreationError::NotSupported);
|
||||
};
|
||||
|
||||
let context = unsafe {
|
||||
match EGLContext::new(
|
||||
native_display,
|
||||
let window = match Window::try_new(Box::new(winit_window), |window| {
|
||||
match EGL::try_new(
|
||||
Box::new(match EGLContext::new_from_winit(
|
||||
&*window,
|
||||
attributes,
|
||||
PixelFormatRequirements {
|
||||
hardware_accelerated: Some(true),
|
||||
|
@ -152,24 +132,25 @@ where
|
|||
error!(log, "EGLContext creation failed:\n {}", err);
|
||||
return Err(err);
|
||||
}
|
||||
}),
|
||||
|context| context.create_surface(window),
|
||||
) {
|
||||
Ok(x) => Ok(x),
|
||||
Err(::rental::TryNewError(err, _)) => return Err(err),
|
||||
}
|
||||
}) {
|
||||
Ok(x) => Rc::new(x),
|
||||
Err(::rental::TryNewError(err, _)) => return Err(err),
|
||||
};
|
||||
|
||||
Ok((
|
||||
WinitGraphicsBackend {
|
||||
window: window.clone(),
|
||||
context: match egl::RentEGL::try_new(Box::new(context), move |context| unsafe {
|
||||
context.create_surface(native_surface)
|
||||
}) {
|
||||
Ok(x) => x,
|
||||
Err(::rental::TryNewError(err, _)) => return Err(err),
|
||||
},
|
||||
logger: log.new(o!("smithay_winit_component" => "graphics")),
|
||||
},
|
||||
WinitInputBackend {
|
||||
events_loop: events_loop,
|
||||
window: window,
|
||||
surface: surface,
|
||||
events_loop,
|
||||
window,
|
||||
time_counter: 0,
|
||||
key_counter: 0,
|
||||
seat: Seat::new(
|
||||
|
@ -189,47 +170,52 @@ where
|
|||
|
||||
impl GraphicsBackend for WinitGraphicsBackend {
|
||||
type CursorFormat = MouseCursor;
|
||||
type Error = ();
|
||||
|
||||
fn set_cursor_position(&mut self, x: u32, y: u32) -> Result<(), ()> {
|
||||
fn set_cursor_position(&self, x: u32, y: u32) -> Result<(), ()> {
|
||||
debug!(self.logger, "Setting cursor position to {:?}", (x, y));
|
||||
self.window.set_cursor_position(x as i32, y as i32)
|
||||
self.window.head().set_cursor_position(x as i32, y as i32)
|
||||
}
|
||||
|
||||
fn set_cursor_representation(&mut self, cursor: Self::CursorFormat) {
|
||||
fn set_cursor_representation(&self, cursor: Self::CursorFormat, _hotspot: (u32, u32)) -> Result<(), ()> {
|
||||
// Cannot log this one, as `CursorFormat` is not `Debug` and should not be
|
||||
debug!(self.logger, "Changing cursor representation");
|
||||
self.window.set_cursor(cursor)
|
||||
self.window.head().set_cursor(cursor);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl EGLGraphicsBackend for WinitGraphicsBackend {
|
||||
fn swap_buffers(&self) -> Result<(), SwapBuffersError> {
|
||||
trace!(self.logger, "Swapping buffers");
|
||||
self.context.rent(|surface| surface.swap_buffers())
|
||||
self.window
|
||||
.rent(|egl| egl.rent(|surface| surface.swap_buffers()))
|
||||
}
|
||||
|
||||
unsafe fn get_proc_address(&self, symbol: &str) -> *const c_void {
|
||||
trace!(self.logger, "Getting symbol for {:?}", symbol);
|
||||
self.context.get_proc_address(symbol)
|
||||
self.window.rent(|egl| egl.head().get_proc_address(symbol))
|
||||
}
|
||||
|
||||
fn get_framebuffer_dimensions(&self) -> (u32, u32) {
|
||||
self.window
|
||||
.head()
|
||||
.get_inner_size_pixels()
|
||||
.expect("Window does not exist anymore")
|
||||
}
|
||||
|
||||
fn is_current(&self) -> bool {
|
||||
self.context.is_current()
|
||||
self.window.rent(|egl| egl.head().is_current())
|
||||
}
|
||||
|
||||
unsafe fn make_current(&self) -> Result<(), SwapBuffersError> {
|
||||
debug!(self.logger, "Setting EGL context to be the current context");
|
||||
self.context.rent(|surface| surface.make_current())
|
||||
self.window
|
||||
.rent(|egl| egl.rent(|surface| surface.make_current()))
|
||||
}
|
||||
|
||||
fn get_pixel_format(&self) -> PixelFormat {
|
||||
self.context.get_pixel_format()
|
||||
self.window.rent(|egl| egl.head().get_pixel_format())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -310,16 +296,24 @@ impl PointerMotionAbsoluteEvent for WinitMouseMovedEvent {
|
|||
|
||||
fn x_transformed(&self, width: u32) -> u32 {
|
||||
cmp::min(
|
||||
(self.x * width as f64 / self.window.get_inner_size_points().unwrap_or((width, 0)).0 as f64) as
|
||||
u32,
|
||||
(self.x * width as f64 /
|
||||
self.window
|
||||
.head()
|
||||
.get_inner_size_points()
|
||||
.unwrap_or((width, 0))
|
||||
.0 as f64) as u32,
|
||||
0,
|
||||
)
|
||||
}
|
||||
|
||||
fn y_transformed(&self, height: u32) -> u32 {
|
||||
cmp::min(
|
||||
(self.y * height as f64 / self.window.get_inner_size_points().unwrap_or((0, height)).1 as f64) as
|
||||
u32,
|
||||
(self.y * height as f64 /
|
||||
self.window
|
||||
.head()
|
||||
.get_inner_size_points()
|
||||
.unwrap_or((0, height))
|
||||
.1 as f64) as u32,
|
||||
0,
|
||||
)
|
||||
}
|
||||
|
@ -416,7 +410,11 @@ impl TouchDownEvent for WinitTouchStartedEvent {
|
|||
fn x_transformed(&self, width: u32) -> u32 {
|
||||
cmp::min(
|
||||
self.location.0 as i32 * width as i32 /
|
||||
self.window.get_inner_size_points().unwrap_or((width, 0)).0 as i32,
|
||||
self.window
|
||||
.head()
|
||||
.get_inner_size_points()
|
||||
.unwrap_or((width, 0))
|
||||
.0 as i32,
|
||||
0,
|
||||
) as u32
|
||||
}
|
||||
|
@ -424,7 +422,11 @@ impl TouchDownEvent for WinitTouchStartedEvent {
|
|||
fn y_transformed(&self, height: u32) -> u32 {
|
||||
cmp::min(
|
||||
self.location.1 as i32 * height as i32 /
|
||||
self.window.get_inner_size_points().unwrap_or((0, height)).1 as i32,
|
||||
self.window
|
||||
.head()
|
||||
.get_inner_size_points()
|
||||
.unwrap_or((0, height))
|
||||
.1 as i32,
|
||||
0,
|
||||
) as u32
|
||||
}
|
||||
|
@ -459,11 +461,21 @@ impl TouchMotionEvent for WinitTouchMovedEvent {
|
|||
}
|
||||
|
||||
fn x_transformed(&self, width: u32) -> u32 {
|
||||
self.location.0 as u32 * width / self.window.get_inner_size_points().unwrap_or((width, 0)).0
|
||||
self.location.0 as u32 * width /
|
||||
self.window
|
||||
.head()
|
||||
.get_inner_size_points()
|
||||
.unwrap_or((width, 0))
|
||||
.0
|
||||
}
|
||||
|
||||
fn y_transformed(&self, height: u32) -> u32 {
|
||||
self.location.1 as u32 * height / self.window.get_inner_size_points().unwrap_or((0, height)).1
|
||||
self.location.1 as u32 * height /
|
||||
self.window
|
||||
.head()
|
||||
.get_inner_size_points()
|
||||
.unwrap_or((0, height))
|
||||
.1
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -578,7 +590,6 @@ impl InputBackend for WinitInputBackend {
|
|||
let mut time_counter = &mut self.time_counter;
|
||||
let seat = &self.seat;
|
||||
let window = &self.window;
|
||||
let surface = &self.surface;
|
||||
let mut handler = self.handler.as_mut();
|
||||
let logger = &self.logger;
|
||||
|
||||
|
@ -587,10 +598,12 @@ impl InputBackend for WinitInputBackend {
|
|||
match (event, handler.as_mut()) {
|
||||
(WindowEvent::Resized(x, y), _) => {
|
||||
trace!(logger, "Resizing window to {:?}", (x, y));
|
||||
window.set_inner_size(x, y);
|
||||
if let Some(wl_egl_surface) = surface.as_ref() {
|
||||
wl_egl_surface.resize(x as i32, y as i32, 0, 0);
|
||||
}
|
||||
window.head().set_inner_size(x, y);
|
||||
window.rent(|egl| {
|
||||
egl.rent(|surface| if let Some(wegl_surface) = (**surface).as_ref() {
|
||||
wegl_surface.resize(x as i32, y as i32, 0, 0)
|
||||
})
|
||||
});
|
||||
}
|
||||
(
|
||||
WindowEvent::KeyboardInput {
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
#![cfg_attr(feature = "clippy", feature(plugin))]
|
||||
#![cfg_attr(feature = "clippy", plugin(clippy))]
|
||||
|
||||
extern crate image;
|
||||
extern crate nix;
|
||||
#[macro_use]
|
||||
extern crate rental;
|
||||
|
@ -15,6 +16,10 @@ extern crate wayland_protocols;
|
|||
extern crate wayland_server;
|
||||
extern crate xkbcommon;
|
||||
|
||||
#[cfg(feature = "backend_drm")]
|
||||
extern crate drm;
|
||||
#[cfg(feature = "backend_drm")]
|
||||
extern crate gbm;
|
||||
#[cfg(feature = "backend_libinput")]
|
||||
extern crate input;
|
||||
#[cfg(feature = "backend_winit")]
|
||||
|
|
Loading…
Reference in New Issue