Refactor drm backend
- Split `DrmDevice` into `LegacyDrmDevice`, `GbmDevice` and `EglDevice` - Provide common `Device` and `RawDevice` traits - Change `DrmBackend` into `Surface` (and `RawSurface`) implementations of each `Device` (or `RawDevice`)
This commit is contained in:
parent
b537237a74
commit
bd5690bd77
|
@ -21,7 +21,7 @@ slog-stdlog = "3.0.2"
|
||||||
libloading = "0.4.0"
|
libloading = "0.4.0"
|
||||||
wayland-client = { version = "0.21.1", features = ["egl"], optional = true }
|
wayland-client = { version = "0.21.1", features = ["egl"], optional = true }
|
||||||
winit = { version = "0.18.0", optional = true }
|
winit = { version = "0.18.0", optional = true }
|
||||||
drm = { version = "^0.3.1", optional = true }
|
drm = { version = "^0.3.4", optional = true }
|
||||||
gbm = { version = "^0.4.0", optional = true, default-features = false, features = ["drm-support"] }
|
gbm = { version = "^0.4.0", optional = true, default-features = false, features = ["drm-support"] }
|
||||||
glium = { version = "0.19.0", optional = true, default-features = false }
|
glium = { version = "0.19.0", optional = true, default-features = false }
|
||||||
input = { version = "0.4.0", optional = true }
|
input = { version = "0.4.0", optional = true }
|
||||||
|
@ -37,9 +37,12 @@ lazy_static = "1.0.0"
|
||||||
gl_generator = { version = "0.9", optional = true }
|
gl_generator = { version = "0.9", optional = true }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["backend_winit", "backend_drm", "backend_libinput", "backend_udev", "renderer_glium", "xwayland"]
|
default = ["backend_winit", "backend_drm_legacy", "backend_drm_gbm", "backend_drm_egl", "backend_libinput", "backend_udev", "renderer_glium", "xwayland"]
|
||||||
backend_winit = ["winit", "wayland-server/dlopen", "wayland-client/dlopen", "backend_egl"]
|
backend_winit = ["winit", "wayland-server/dlopen", "wayland-client/dlopen", "backend_egl"]
|
||||||
backend_drm = ["drm", "backend_egl"]
|
backend_drm = ["drm"]
|
||||||
|
backend_drm_legacy = ["backend_drm"]
|
||||||
|
backend_drm_gbm = ["backend_drm", "gbm"]
|
||||||
|
backend_drm_egl = ["backend_drm", "backend_egl"]
|
||||||
backend_egl = ["gl_generator"]
|
backend_egl = ["gl_generator"]
|
||||||
backend_libinput = ["input"]
|
backend_libinput = ["input"]
|
||||||
backend_session = []
|
backend_session = []
|
||||||
|
|
|
@ -1,537 +0,0 @@
|
||||||
use super::{error::*, DevPath};
|
|
||||||
use backend::graphics::{
|
|
||||||
egl::{
|
|
||||||
error::Result as EGLResult,
|
|
||||||
native::{Gbm, GbmSurfaceArguments},
|
|
||||||
wayland::{EGLDisplay, EGLWaylandExtensions},
|
|
||||||
EGLContext, EGLGraphicsBackend, EGLSurface, PixelFormat, SwapBuffersError,
|
|
||||||
},
|
|
||||||
GraphicsBackend,
|
|
||||||
};
|
|
||||||
use drm::{
|
|
||||||
control::{connector, crtc, encoder, framebuffer, Device, Mode, ResourceInfo},
|
|
||||||
Device as BasicDevice,
|
|
||||||
};
|
|
||||||
use gbm::{
|
|
||||||
BufferObject, BufferObjectFlags, Device as GbmDevice, Format as GbmFormat, Surface as GbmSurface,
|
|
||||||
SurfaceBufferHandle,
|
|
||||||
};
|
|
||||||
use image::{ImageBuffer, Rgba};
|
|
||||||
use nix::libc::c_void;
|
|
||||||
use std::{
|
|
||||||
cell::Cell,
|
|
||||||
os::unix::io::{AsRawFd, RawFd},
|
|
||||||
rc::{Rc, Weak},
|
|
||||||
};
|
|
||||||
use wayland_server::Display;
|
|
||||||
|
|
||||||
/// Backend based on a `DrmDevice` and a given crtc
|
|
||||||
pub struct DrmBackend<A: Device + 'static> {
|
|
||||||
backend: Rc<DrmBackendInternal<A>>,
|
|
||||||
surface: EGLSurface<GbmSurface<framebuffer::Info>>,
|
|
||||||
mode: Mode,
|
|
||||||
connectors: Vec<connector::Handle>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) struct DrmBackendInternal<A: Device + 'static> {
|
|
||||||
pub(crate) context: Rc<EGLContext<Gbm<framebuffer::Info>, GbmDevice<A>>>,
|
|
||||||
pub(crate) cursor: Cell<(BufferObject<()>, (u32, u32))>,
|
|
||||||
current_frame_buffer: Cell<framebuffer::Info>,
|
|
||||||
front_buffer: Cell<SurfaceBufferHandle<framebuffer::Info>>,
|
|
||||||
next_buffer: Cell<Option<SurfaceBufferHandle<framebuffer::Info>>>,
|
|
||||||
crtc: crtc::Handle,
|
|
||||||
logger: ::slog::Logger,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<A: Device + 'static> DrmBackend<A> {
|
|
||||||
pub(crate) fn new(
|
|
||||||
context: Rc<EGLContext<Gbm<framebuffer::Info>, GbmDevice<A>>>,
|
|
||||||
crtc: crtc::Handle,
|
|
||||||
mode: Mode,
|
|
||||||
connectors: Vec<connector::Handle>,
|
|
||||||
log: ::slog::Logger,
|
|
||||||
) -> Result<Self> {
|
|
||||||
// logger already initialized by the DrmDevice
|
|
||||||
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)?,
|
|
||||||
(0, 0),
|
|
||||||
));
|
|
||||||
|
|
||||||
Ok(DrmBackend {
|
|
||||||
backend: Rc::new(DrmBackendInternal {
|
|
||||||
context,
|
|
||||||
cursor,
|
|
||||||
current_frame_buffer: Cell::new(fb),
|
|
||||||
front_buffer: Cell::new(front_bo),
|
|
||||||
next_buffer: Cell::new(None),
|
|
||||||
crtc,
|
|
||||||
logger: log,
|
|
||||||
}),
|
|
||||||
surface,
|
|
||||||
mode,
|
|
||||||
connectors,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn weak(&self) -> Weak<DrmBackendInternal<A>> {
|
|
||||||
Rc::downgrade(&self.backend)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Add a connector to backend
|
|
||||||
///
|
|
||||||
/// # Errors
|
|
||||||
///
|
|
||||||
/// Errors if the new connector does not support the currently set `Mode`
|
|
||||||
pub fn add_connector(&mut self, connector: connector::Handle) -> Result<()> {
|
|
||||||
let info = connector::Info::load_from_device(&*self.backend.context, connector).chain_err(|| {
|
|
||||||
ErrorKind::DrmDev(format!(
|
|
||||||
"Error loading connector info on {:?}",
|
|
||||||
self.backend.context.dev_path()
|
|
||||||
))
|
|
||||||
})?;
|
|
||||||
|
|
||||||
// check if the connector can handle the current mode
|
|
||||||
if info.modes().contains(&self.mode) {
|
|
||||||
// check if there is a valid encoder
|
|
||||||
let encoders = info
|
|
||||||
.encoders()
|
|
||||||
.iter()
|
|
||||||
.map(|encoder| {
|
|
||||||
encoder::Info::load_from_device(&*self.backend.context, *encoder).chain_err(|| {
|
|
||||||
ErrorKind::DrmDev(format!(
|
|
||||||
"Error loading encoder info on {:?}",
|
|
||||||
self.backend.context.dev_path()
|
|
||||||
))
|
|
||||||
})
|
|
||||||
}).collect::<Result<Vec<encoder::Info>>>()?;
|
|
||||||
|
|
||||||
// and if any encoder supports the selected crtc
|
|
||||||
let resource_handles = self.backend.context.resource_handles().chain_err(|| {
|
|
||||||
ErrorKind::DrmDev(format!(
|
|
||||||
"Error loading resources on {:?}",
|
|
||||||
self.backend.context.dev_path()
|
|
||||||
))
|
|
||||||
})?;
|
|
||||||
if !encoders
|
|
||||||
.iter()
|
|
||||||
.map(|encoder| encoder.possible_crtcs())
|
|
||||||
.all(|crtc_list| {
|
|
||||||
resource_handles
|
|
||||||
.filter_crtcs(crtc_list)
|
|
||||||
.contains(&self.backend.crtc)
|
|
||||||
}) {
|
|
||||||
bail!(ErrorKind::NoSuitableEncoder(info, self.backend.crtc));
|
|
||||||
}
|
|
||||||
|
|
||||||
info!(
|
|
||||||
self.backend.logger,
|
|
||||||
"Adding new connector: {:?}",
|
|
||||||
info.connector_type()
|
|
||||||
);
|
|
||||||
self.connectors.push(connector);
|
|
||||||
Ok(())
|
|
||||||
} else {
|
|
||||||
bail!(ErrorKind::ModeNotSuitable(self.mode))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the currently set connectors
|
|
||||||
pub fn used_connectors(&self) -> &[connector::Handle] {
|
|
||||||
&*self.connectors
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Removes a currently set connector
|
|
||||||
pub fn remove_connector(&mut self, connector: connector::Handle) {
|
|
||||||
if let Ok(info) = connector::Info::load_from_device(&*self.backend.context, connector) {
|
|
||||||
info!(
|
|
||||||
self.backend.logger,
|
|
||||||
"Removing connector: {:?}",
|
|
||||||
info.connector_type()
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
info!(self.backend.logger, "Removing unknown connector");
|
|
||||||
}
|
|
||||||
|
|
||||||
self.connectors.retain(|x| *x != connector);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Gets the currently used mode
|
|
||||||
pub fn current_mode(&self) -> Mode {
|
|
||||||
self.mode
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Changes the currently set mode
|
|
||||||
///
|
|
||||||
/// # Errors
|
|
||||||
///
|
|
||||||
/// This will fail if not all set connectors support the new `Mode`.
|
|
||||||
/// Several internal resources will need to be recreated to fit the new `Mode`.
|
|
||||||
/// Other errors might occur.
|
|
||||||
pub fn use_mode(&mut self, mode: Mode) -> Result<()> {
|
|
||||||
// check the connectors
|
|
||||||
for connector in &self.connectors {
|
|
||||||
if !connector::Info::load_from_device(&*self.backend.context, *connector)
|
|
||||||
.chain_err(|| {
|
|
||||||
ErrorKind::DrmDev(format!(
|
|
||||||
"Error loading connector info on {:?}",
|
|
||||||
self.backend.context.dev_path()
|
|
||||||
))
|
|
||||||
})?.modes()
|
|
||||||
.contains(&mode)
|
|
||||||
{
|
|
||||||
bail!(ErrorKind::ModeNotSuitable(mode));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
info!(self.backend.logger, "Setting new mode: {:?}", mode.name());
|
|
||||||
let (w, h) = mode.size();
|
|
||||||
|
|
||||||
// Recreate the surface and the related resources to match the new
|
|
||||||
// resolution.
|
|
||||||
debug!(
|
|
||||||
self.backend.logger,
|
|
||||||
"Reinitializing surface for new mode: {}:{}", w, h
|
|
||||||
);
|
|
||||||
let surface = self
|
|
||||||
.backend
|
|
||||||
.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.backend.next_buffer.take() {
|
|
||||||
if let Ok(Some(fb)) = old_bo.take_userdata() {
|
|
||||||
if let Err(err) = framebuffer::destroy(&*self.backend.context, fb.handle()) {
|
|
||||||
warn!(
|
|
||||||
self.backend.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 mut old_front_bo = self.backend.front_buffer.replace({
|
|
||||||
let mut front_bo = surface
|
|
||||||
.lock_front_buffer()
|
|
||||||
.chain_err(|| ErrorKind::FailedToSwap)?;
|
|
||||||
|
|
||||||
debug!(
|
|
||||||
self.backend.logger,
|
|
||||||
"FrontBuffer color format: {:?}",
|
|
||||||
front_bo.format()
|
|
||||||
);
|
|
||||||
|
|
||||||
// we also need a new framebuffer for the front buffer
|
|
||||||
let dev_path = self.backend.context.dev_path();
|
|
||||||
let fb = framebuffer::create(&*self.backend.context, &*front_bo)
|
|
||||||
.chain_err(|| ErrorKind::DrmDev(format!("Error creating framebuffer on {:?}", dev_path)))?;
|
|
||||||
|
|
||||||
front_bo.set_userdata(fb).unwrap();
|
|
||||||
|
|
||||||
debug!(self.backend.logger, "Setting screen");
|
|
||||||
crtc::set(
|
|
||||||
&*self.backend.context,
|
|
||||||
self.backend.crtc,
|
|
||||||
fb.handle(),
|
|
||||||
&self.connectors,
|
|
||||||
(0, 0),
|
|
||||||
Some(mode),
|
|
||||||
).chain_err(|| {
|
|
||||||
ErrorKind::DrmDev(format!(
|
|
||||||
"Error setting crtc {:?} on {:?}",
|
|
||||||
self.backend.crtc,
|
|
||||||
self.backend.context.dev_path()
|
|
||||||
))
|
|
||||||
})?;
|
|
||||||
|
|
||||||
front_bo
|
|
||||||
});
|
|
||||||
if let Ok(Some(fb)) = old_front_bo.take_userdata() {
|
|
||||||
if let Err(err) = framebuffer::destroy(&*self.backend.context, fb.handle()) {
|
|
||||||
warn!(
|
|
||||||
self.backend.logger,
|
|
||||||
"Error releasing old front_buffer framebuffer: {:?}", err
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Drop the old surface after cleanup
|
|
||||||
self.surface = surface;
|
|
||||||
self.mode = mode;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the crtc id used by this backend
|
|
||||||
pub fn crtc(&self) -> crtc::Handle {
|
|
||||||
self.backend.crtc
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<A: Device + 'static> DrmBackendInternal<A> {
|
|
||||||
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
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn page_flip(
|
|
||||||
&self,
|
|
||||||
fb: Option<&framebuffer::Info>,
|
|
||||||
) -> ::std::result::Result<(), SwapBuffersError> {
|
|
||||||
trace!(self.logger, "Queueing Page flip");
|
|
||||||
|
|
||||||
let fb = *fb.unwrap_or(&self.current_frame_buffer.get());
|
|
||||||
|
|
||||||
// and flip
|
|
||||||
crtc::page_flip(
|
|
||||||
&*self.context,
|
|
||||||
self.crtc,
|
|
||||||
fb.handle(),
|
|
||||||
&[crtc::PageFlipFlags::PageFlipEvent],
|
|
||||||
).map_err(|_| SwapBuffersError::ContextLost)?;
|
|
||||||
|
|
||||||
self.current_frame_buffer.set(fb);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<A: Device + 'static> Drop for DrmBackend<A> {
|
|
||||||
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)
|
|
||||||
if let Ok(Some(fb)) = {
|
|
||||||
if let Some(mut next) = self.backend.next_buffer.take() {
|
|
||||||
next.take_userdata()
|
|
||||||
} else if let Ok(mut next) = self.surface.lock_front_buffer() {
|
|
||||||
next.take_userdata()
|
|
||||||
} else {
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
} {
|
|
||||||
// ignore failure at this point
|
|
||||||
let _ = framebuffer::destroy(&*self.backend.context, fb.handle());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<A: Device + 'static> Drop for DrmBackendInternal<A> {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
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<A: Device + 'static> GraphicsBackend for DrmBackend<A> {
|
|
||||||
type CursorFormat = ImageBuffer<Rgba<u8>, Vec<u8>>;
|
|
||||||
type Error = Error;
|
|
||||||
|
|
||||||
fn set_cursor_position(&self, x: u32, y: u32) -> Result<()> {
|
|
||||||
trace!(self.backend.logger, "Move the cursor to {},{}", x, y);
|
|
||||||
crtc::move_cursor(&*self.backend.context, self.backend.crtc, (x as i32, y as i32)).chain_err(|| {
|
|
||||||
ErrorKind::DrmDev(format!(
|
|
||||||
"Error moving cursor on {:?}",
|
|
||||||
self.backend.context.dev_path()
|
|
||||||
))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_cursor_representation(
|
|
||||||
&self,
|
|
||||||
buffer: &ImageBuffer<Rgba<u8>, Vec<u8>>,
|
|
||||||
hotspot: (u32, u32),
|
|
||||||
) -> Result<()> {
|
|
||||||
let (w, h) = buffer.dimensions();
|
|
||||||
debug!(self.backend.logger, "Importing cursor");
|
|
||||||
|
|
||||||
// import the cursor into a buffer we can render
|
|
||||||
let mut cursor = self
|
|
||||||
.backend
|
|
||||||
.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.backend.logger, "Setting the new imported cursor");
|
|
||||||
|
|
||||||
// and set it
|
|
||||||
if crtc::set_cursor2(
|
|
||||||
&*self.backend.context,
|
|
||||||
self.backend.crtc,
|
|
||||||
&cursor,
|
|
||||||
(hotspot.0 as i32, hotspot.1 as i32),
|
|
||||||
).is_err()
|
|
||||||
{
|
|
||||||
crtc::set_cursor(&*self.backend.context, self.backend.crtc, &cursor).chain_err(|| {
|
|
||||||
ErrorKind::DrmDev(format!(
|
|
||||||
"Failed to set cursor on {:?}",
|
|
||||||
self.backend.context.dev_path()
|
|
||||||
))
|
|
||||||
})?;
|
|
||||||
}
|
|
||||||
|
|
||||||
// and store it
|
|
||||||
self.backend.cursor.set((cursor, hotspot));
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<A: Device + 'static> EGLGraphicsBackend for DrmBackend<A> {
|
|
||||||
fn swap_buffers(&self) -> ::std::result::Result<(), SwapBuffersError> {
|
|
||||||
let res = {
|
|
||||||
let nb = self.backend.next_buffer.take();
|
|
||||||
let res = nb.is_some();
|
|
||||||
self.backend.next_buffer.set(nb);
|
|
||||||
res
|
|
||||||
};
|
|
||||||
if res {
|
|
||||||
// We cannot call lock_front_buffer anymore without releasing the previous buffer, which will happen when the page flip is done
|
|
||||||
warn!(
|
|
||||||
self.backend.logger,
|
|
||||||
"Tried to swap a DrmBackend with a queued flip"
|
|
||||||
);
|
|
||||||
return Err(SwapBuffersError::AlreadySwapped);
|
|
||||||
}
|
|
||||||
|
|
||||||
// flip normally
|
|
||||||
self.surface.swap_buffers()?;
|
|
||||||
|
|
||||||
// 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()
|
|
||||||
.map_err(|_| SwapBuffersError::ContextLost)?
|
|
||||||
.cloned();
|
|
||||||
let fb = if let Some(info) = maybe_fb {
|
|
||||||
info
|
|
||||||
} else {
|
|
||||||
let fb = framebuffer::create(&*self.backend.context, &*next_bo)
|
|
||||||
.map_err(|_| SwapBuffersError::ContextLost)?;
|
|
||||||
next_bo.set_userdata(fb).unwrap();
|
|
||||||
fb
|
|
||||||
};
|
|
||||||
self.backend.next_buffer.set(Some(next_bo));
|
|
||||||
|
|
||||||
self.backend.page_flip(Some(&fb))
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe fn get_proc_address(&self, symbol: &str) -> *const c_void {
|
|
||||||
self.backend.context.get_proc_address(symbol)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_framebuffer_dimensions(&self) -> (u32, u32) {
|
|
||||||
let (w, h) = self.mode.size();
|
|
||||||
(w as u32, h as u32)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_current(&self) -> bool {
|
|
||||||
self.backend.context.is_current() && self.surface.is_current()
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe fn make_current(&self) -> ::std::result::Result<(), SwapBuffersError> {
|
|
||||||
self.surface.make_current()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_pixel_format(&self) -> PixelFormat {
|
|
||||||
self.backend.context.get_pixel_format()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// for users convenience
|
|
||||||
impl<A: Device + 'static> AsRawFd for DrmBackend<A> {
|
|
||||||
fn as_raw_fd(&self) -> RawFd {
|
|
||||||
self.backend.context.as_raw_fd()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<A: Device + 'static> BasicDevice for DrmBackend<A> {}
|
|
||||||
impl<A: Device + 'static> Device for DrmBackend<A> {}
|
|
||||||
|
|
||||||
impl<A: Device + 'static> EGLWaylandExtensions for DrmBackend<A> {
|
|
||||||
fn bind_wl_display(&self, display: &Display) -> EGLResult<EGLDisplay> {
|
|
||||||
self.backend.context.bind_wl_display(display)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
//!
|
||||||
|
//! Errors thrown by the `DrmDevice` and `DrmBackend`
|
||||||
|
//!
|
||||||
|
|
||||||
|
use backend::egl::error as egl;
|
||||||
|
|
||||||
|
error_chain! {
|
||||||
|
errors {
|
||||||
|
#[doc = "Underlying backend failed"]
|
||||||
|
UnderlyingBackendError {
|
||||||
|
description("The underlying backend reported an error"),
|
||||||
|
display("The underlying backend reported an error"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
links {
|
||||||
|
EGL(egl::Error, egl::ErrorKind) #[doc = "EGL error"];
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,172 @@
|
||||||
|
use drm::control::{crtc, Mode};
|
||||||
|
use std::cell::RefCell;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::rc::{Rc, Weak};
|
||||||
|
use std::os::unix::io::{AsRawFd, RawFd};
|
||||||
|
use wayland_server::Display;
|
||||||
|
|
||||||
|
use backend::egl::{EGLContext, EGLGraphicsBackend, EGLDisplay};
|
||||||
|
use backend::egl::context::GlAttributes;
|
||||||
|
use backend::egl::native::{Backend, NativeDisplay, NativeSurface};
|
||||||
|
use backend::egl::error::Result as EGLResult;
|
||||||
|
use super::{Device, Surface, DeviceHandler};
|
||||||
|
|
||||||
|
pub mod error;
|
||||||
|
use self::error::*;
|
||||||
|
|
||||||
|
mod surface;
|
||||||
|
pub use self::surface::*;
|
||||||
|
|
||||||
|
#[cfg(feature = "backend_session")]
|
||||||
|
pub mod session;
|
||||||
|
|
||||||
|
/// Representation of an open gbm device to create rendering backends
|
||||||
|
pub struct EglDevice<B: Backend<Surface=<D as Device>::Surface> + 'static, D: Device + NativeDisplay<B> + 'static>
|
||||||
|
where <D as Device>::Surface: NativeSurface
|
||||||
|
{
|
||||||
|
dev: Rc<RefCell<EGLContext<B, D>>>,
|
||||||
|
backends: Rc<RefCell<HashMap<crtc::Handle, Weak<EglSurface<B, D>>>>>,
|
||||||
|
logger: ::slog::Logger,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<B: Backend<Surface=<D as Device>::Surface> + 'static, D: Device + NativeDisplay<B> + 'static> AsRawFd for EglDevice<B, D>
|
||||||
|
where <D as Device>::Surface: NativeSurface
|
||||||
|
{
|
||||||
|
fn as_raw_fd(&self) -> RawFd {
|
||||||
|
self.dev.borrow().as_raw_fd()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<B: Backend<Surface=<D as Device>::Surface> + 'static, D: Device + NativeDisplay<B> + 'static> EglDevice<B, D>
|
||||||
|
where <D as Device>::Surface: NativeSurface
|
||||||
|
{
|
||||||
|
/// Create a new `EglGbmDrmDevice` from an open drm node
|
||||||
|
///
|
||||||
|
/// Returns an error if the file is no valid drm node or context creation was not
|
||||||
|
/// successful.
|
||||||
|
pub fn new<L>(dev: D, logger: L) -> Result<Self>
|
||||||
|
where
|
||||||
|
L: Into<Option<::slog::Logger>>,
|
||||||
|
{
|
||||||
|
EglDevice::new_with_gl_attr(
|
||||||
|
dev,
|
||||||
|
GlAttributes {
|
||||||
|
version: None,
|
||||||
|
profile: None,
|
||||||
|
debug: cfg!(debug_assertions),
|
||||||
|
vsync: true,
|
||||||
|
},
|
||||||
|
logger,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new `EglGbmDrmDevice` from an open `RawDevice` and given `GlAttributes`
|
||||||
|
///
|
||||||
|
/// Returns an error if the file is no valid drm node or context creation was not
|
||||||
|
/// successful.
|
||||||
|
pub fn new_with_gl_attr<L>(mut dev: D, attributes: GlAttributes, logger: L) -> Result<Self>
|
||||||
|
where
|
||||||
|
L: Into<Option<::slog::Logger>>,
|
||||||
|
{
|
||||||
|
let log = ::slog_or_stdlog(logger).new(o!("smithay_module" => "backend_egl"));
|
||||||
|
|
||||||
|
dev.clear_handler();
|
||||||
|
|
||||||
|
debug!(log, "Creating egl context from device");
|
||||||
|
Ok(EglDevice {
|
||||||
|
// Open the gbm device from the drm device and create a context based on that
|
||||||
|
dev: Rc::new(RefCell::new(EGLContext::new(
|
||||||
|
dev,
|
||||||
|
attributes,
|
||||||
|
Default::default(),
|
||||||
|
log.clone(),
|
||||||
|
).map_err(Error::from)?
|
||||||
|
)),
|
||||||
|
backends: Rc::new(RefCell::new(HashMap::new())),
|
||||||
|
logger: log,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct InternalDeviceHandler<B: Backend<Surface=<D as Device>::Surface> + 'static, D: Device + NativeDisplay<B> + 'static>
|
||||||
|
where <D as Device>::Surface: NativeSurface
|
||||||
|
{
|
||||||
|
handler: Box<DeviceHandler<Device=EglDevice<B, D>> + 'static>,
|
||||||
|
backends: Weak<RefCell<HashMap<crtc::Handle, Weak<EglSurface<B, D>>>>>,
|
||||||
|
logger: ::slog::Logger,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<B: Backend<Surface=<D as Device>::Surface> + 'static, D: Device + NativeDisplay<B> + 'static> DeviceHandler for InternalDeviceHandler<B, D>
|
||||||
|
where
|
||||||
|
<D as NativeDisplay<B>>::Arguments: From<(crtc::Handle, Mode, <<D as Device>::Surface as Surface>::Connectors)>,
|
||||||
|
<D as Device>::Surface: NativeSurface,
|
||||||
|
{
|
||||||
|
type Device=D;
|
||||||
|
|
||||||
|
fn vblank(&mut self, surface: &<D as Device>::Surface) {
|
||||||
|
if let Some(backends) = self.backends.upgrade() {
|
||||||
|
if let Some(surface) = backends.borrow().get(&surface.crtc()) {
|
||||||
|
if let Some(surface) = surface.upgrade() {
|
||||||
|
self.handler.vblank(&*surface);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
warn!(self.logger, "Surface ({:?}) not managed by egl, event not handled.", surface.crtc());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn error(&mut self, error: <<D as Device>::Surface as Surface>::Error) {
|
||||||
|
self.handler.error(ResultExt::<()>::chain_err(Err(error), || ErrorKind::UnderlyingBackendError).unwrap_err())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<B: Backend<Surface=<D as Device>::Surface> + 'static, D: Device + NativeDisplay<B> + 'static> Device for EglDevice<B, D>
|
||||||
|
where
|
||||||
|
<D as NativeDisplay<B>>::Arguments: From<(crtc::Handle, Mode, <<D as Device>::Surface as Surface>::Connectors)>,
|
||||||
|
<D as Device>::Surface: NativeSurface,
|
||||||
|
{
|
||||||
|
type Surface = EglSurface<B, D>;
|
||||||
|
type Return = Rc<EglSurface<B, D>>;
|
||||||
|
|
||||||
|
fn set_handler(&mut self, handler: impl DeviceHandler<Device=Self> + 'static) {
|
||||||
|
self.dev.borrow_mut().set_handler(InternalDeviceHandler {
|
||||||
|
handler: Box::new(handler),
|
||||||
|
backends: Rc::downgrade(&self.backends),
|
||||||
|
logger: self.logger.clone(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn clear_handler(&mut self) {
|
||||||
|
self.dev.borrow_mut().clear_handler()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_surface(
|
||||||
|
&mut self,
|
||||||
|
crtc: crtc::Handle,
|
||||||
|
mode: Mode,
|
||||||
|
connectors: impl Into<<<Self as Device>::Surface as Surface>::Connectors>,
|
||||||
|
) -> Result<Rc<EglSurface<B, D>>> {
|
||||||
|
info!(self.logger, "Initializing EglSurface");
|
||||||
|
|
||||||
|
let surface = self.dev.borrow_mut().create_surface((crtc, mode, connectors.into()).into())?;
|
||||||
|
|
||||||
|
let backend = Rc::new(EglSurface {
|
||||||
|
dev: self.dev.clone(),
|
||||||
|
surface,
|
||||||
|
});
|
||||||
|
self.backends.borrow_mut().insert(crtc, Rc::downgrade(&backend));
|
||||||
|
Ok(backend)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn process_events(&mut self) {
|
||||||
|
self.dev.borrow_mut().process_events()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<B: Backend<Surface=<D as Device>::Surface> + 'static, D: Device + NativeDisplay<B> + 'static> EGLGraphicsBackend for EglDevice<B, D>
|
||||||
|
where <D as Device>::Surface: NativeSurface
|
||||||
|
{
|
||||||
|
fn bind_wl_display(&self, display: &Display) -> EGLResult<EGLDisplay> {
|
||||||
|
self.dev.borrow().bind_wl_display(display)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
use std::os::unix::io::RawFd;
|
||||||
|
|
||||||
|
use backend::egl::native::{Backend, NativeDisplay, NativeSurface};
|
||||||
|
use backend::session::{AsSessionObserver, SessionObserver};
|
||||||
|
use backend::drm::Device;
|
||||||
|
use super::{EglDevice};
|
||||||
|
|
||||||
|
/// `SessionObserver` linked to the `DrmDevice` it was created from.
|
||||||
|
pub struct EglDeviceObserver<S: SessionObserver + 'static> {
|
||||||
|
observer: S,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<
|
||||||
|
S: SessionObserver + 'static,
|
||||||
|
B: Backend<Surface=<D as Device>::Surface> + 'static,
|
||||||
|
D: Device + NativeDisplay<B> + AsSessionObserver<S> + 'static,
|
||||||
|
> AsSessionObserver<EglDeviceObserver<S>> for EglDevice<B, D>
|
||||||
|
where <D as Device>::Surface: NativeSurface
|
||||||
|
{
|
||||||
|
fn observer(&mut self) -> EglDeviceObserver<S> {
|
||||||
|
EglDeviceObserver {
|
||||||
|
observer: (**self.dev.borrow_mut()).observer(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S: SessionObserver + 'static> SessionObserver for EglDeviceObserver<S> {
|
||||||
|
fn pause(&mut self, devnum: Option<(u32, u32)>) {
|
||||||
|
self.observer.pause(devnum);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn activate(&mut self, devnum: Option<(u32, u32, Option<RawFd>)>) {
|
||||||
|
self.observer.activate(devnum);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,110 @@
|
||||||
|
use drm::control::{connector, crtc, Mode};
|
||||||
|
use nix::libc::c_void;
|
||||||
|
use std::cell::RefCell;
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
use backend::drm::{Device, Surface};
|
||||||
|
use backend::egl::{EGLContext, EGLSurface};
|
||||||
|
use backend::egl::native::{Backend, NativeDisplay, NativeSurface};
|
||||||
|
use backend::graphics::{CursorBackend, SwapBuffersError};
|
||||||
|
use backend::graphics::gl::{GLGraphicsBackend, PixelFormat};
|
||||||
|
use super::error::*;
|
||||||
|
|
||||||
|
pub struct EglSurface<B: Backend<Surface=<D as Device>::Surface> + 'static, D: Device + NativeDisplay<B> + 'static>
|
||||||
|
where <D as Device>::Surface: NativeSurface
|
||||||
|
{
|
||||||
|
pub(in super) dev: Rc<RefCell<EGLContext<B, D>>>,
|
||||||
|
pub(in super) surface: EGLSurface<B::Surface>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<B: Backend<Surface=<D as Device>::Surface> + 'static, D: Device + NativeDisplay<B> + 'static> Surface for EglSurface<B, D>
|
||||||
|
where <D as Device>::Surface: NativeSurface
|
||||||
|
{
|
||||||
|
type Error = Error;
|
||||||
|
type Connectors = <<D as Device>::Surface as Surface>::Connectors;
|
||||||
|
|
||||||
|
fn crtc(&self) -> crtc::Handle {
|
||||||
|
(*self.surface).crtc()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn current_connectors(&self) -> Self::Connectors {
|
||||||
|
self.surface.current_connectors()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pending_connectors(&self) -> Self::Connectors {
|
||||||
|
self.surface.pending_connectors()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_connector(&self, connector: connector::Handle) -> Result<()> {
|
||||||
|
self.surface.add_connector(connector).chain_err(|| ErrorKind::UnderlyingBackendError)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove_connector(&self, connector: connector::Handle) -> Result<()> {
|
||||||
|
self.surface.remove_connector(connector).chain_err(|| ErrorKind::UnderlyingBackendError)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn current_mode(&self) -> Mode {
|
||||||
|
self.surface.current_mode()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pending_mode(&self) -> Mode {
|
||||||
|
self.surface.pending_mode()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn use_mode(&self, mode: Mode) -> Result<()> {
|
||||||
|
self.surface.use_mode(mode).chain_err(|| ErrorKind::UnderlyingBackendError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, B: Backend<Surface=<D as Device>::Surface> + 'static, D: Device + NativeDisplay<B> + 'static> CursorBackend<'a> for EglSurface<B, D>
|
||||||
|
where
|
||||||
|
D: CursorBackend<'a>,
|
||||||
|
<D as Device>::Surface: NativeSurface
|
||||||
|
{
|
||||||
|
type CursorFormat = <D as CursorBackend<'a>>::CursorFormat;
|
||||||
|
type Error = <D as CursorBackend<'a>>::Error;
|
||||||
|
|
||||||
|
fn set_cursor_position(&self, x: u32, y: u32) -> ::std::result::Result<(), Self::Error> {
|
||||||
|
self.dev.borrow().set_cursor_position(x, y)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_cursor_representation<'b>(
|
||||||
|
&'b self,
|
||||||
|
buffer: Self::CursorFormat,
|
||||||
|
hotspot: (u32, u32),
|
||||||
|
) -> ::std::result::Result<(), Self::Error>
|
||||||
|
where 'a: 'b
|
||||||
|
{
|
||||||
|
let dev = self.dev.borrow();
|
||||||
|
dev.set_cursor_representation(buffer, hotspot)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<B: Backend<Surface=<D as Device>::Surface> + 'static, D: Device + NativeDisplay<B> + 'static> GLGraphicsBackend for EglSurface<B, D>
|
||||||
|
where <D as Device>::Surface: NativeSurface
|
||||||
|
{
|
||||||
|
fn swap_buffers(&self) -> ::std::result::Result<(), SwapBuffersError> {
|
||||||
|
self.surface.swap_buffers()
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn get_proc_address(&self, symbol: &str) -> *const c_void {
|
||||||
|
self.dev.borrow().get_proc_address(symbol)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_framebuffer_dimensions(&self) -> (u32, u32) {
|
||||||
|
let (w, h) = self.pending_mode().size();
|
||||||
|
(w as u32, h as u32)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_current(&self) -> bool {
|
||||||
|
self.dev.borrow().is_current() && self.surface.is_current()
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn make_current(&self) -> ::std::result::Result<(), SwapBuffersError> {
|
||||||
|
self.surface.make_current()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_pixel_format(&self) -> PixelFormat {
|
||||||
|
self.dev.borrow().get_pixel_format()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,118 @@
|
||||||
|
use backend::drm::{RawDevice, Device, RawSurface, Surface};
|
||||||
|
use backend::egl::native::{Backend, NativeDisplay, NativeSurface};
|
||||||
|
use backend::egl::error::{Result as EglResult};
|
||||||
|
use backend::egl::ffi;
|
||||||
|
use backend::graphics::SwapBuffersError;
|
||||||
|
|
||||||
|
use super::{GbmDevice, GbmSurface};
|
||||||
|
use super::error::{Error, Result};
|
||||||
|
|
||||||
|
use drm::control::{crtc, Device as ControlDevice, Mode};
|
||||||
|
use gbm::AsRaw;
|
||||||
|
use std::marker::PhantomData;
|
||||||
|
use std::rc::Rc;
|
||||||
|
use std::ptr;
|
||||||
|
|
||||||
|
/// Gbm backend type
|
||||||
|
pub struct Gbm<D: RawDevice + 'static>
|
||||||
|
where
|
||||||
|
<D as Device>::Return: ::std::borrow::Borrow<<D as RawDevice>::Surface>
|
||||||
|
{
|
||||||
|
_userdata: PhantomData<D>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<D: RawDevice + 'static> Backend for Gbm<D>
|
||||||
|
where
|
||||||
|
<D as Device>::Return: ::std::borrow::Borrow<<D as RawDevice>::Surface>
|
||||||
|
{
|
||||||
|
type Surface = Rc<GbmSurface<D>>;
|
||||||
|
|
||||||
|
unsafe fn get_display<F>(
|
||||||
|
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 _)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Arguments necessary to construct a `GbmSurface`
|
||||||
|
pub struct SurfaceArguments<D: RawDevice + 'static>
|
||||||
|
where
|
||||||
|
<D as Device>::Return: ::std::borrow::Borrow<<D as RawDevice>::Surface>
|
||||||
|
{
|
||||||
|
/// Crtc
|
||||||
|
pub crtc: crtc::Handle,
|
||||||
|
/// Mode
|
||||||
|
pub mode: Mode,
|
||||||
|
/// Connectors
|
||||||
|
pub connectors: <GbmSurface<D> as Surface>::Connectors,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<D: RawDevice + 'static> From<(crtc::Handle, Mode, <GbmSurface<D> as Surface>::Connectors)> for SurfaceArguments<D>
|
||||||
|
where
|
||||||
|
<D as Device>::Return: ::std::borrow::Borrow<<D as RawDevice>::Surface>
|
||||||
|
{
|
||||||
|
fn from((crtc, mode, connectors): (crtc::Handle, Mode, <GbmSurface<D> as Surface>::Connectors)) -> Self {
|
||||||
|
SurfaceArguments {
|
||||||
|
crtc,
|
||||||
|
mode,
|
||||||
|
connectors,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl<D: RawDevice + ControlDevice + 'static> NativeDisplay<Gbm<D>> for GbmDevice<D>
|
||||||
|
where
|
||||||
|
<D as Device>::Return: ::std::borrow::Borrow<<D as RawDevice>::Surface>
|
||||||
|
{
|
||||||
|
type Arguments = SurfaceArguments<D>;
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
fn is_backend(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ptr(&self) -> EglResult<ffi::NativeDisplayType> {
|
||||||
|
Ok(self.dev.borrow().as_raw() as *const _)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_surface(&mut self, args: SurfaceArguments<D>) -> Result<Rc<GbmSurface<D>>> {
|
||||||
|
Device::create_surface(self, args.crtc, args.mode, args.connectors)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl<D: RawDevice + 'static> NativeSurface for Rc<GbmSurface<D>>
|
||||||
|
where
|
||||||
|
<D as Device>::Return: ::std::borrow::Borrow<<D as RawDevice>::Surface>
|
||||||
|
{
|
||||||
|
fn ptr(&self) -> ffi::NativeWindowType {
|
||||||
|
self.surface.borrow().as_raw() as *const _
|
||||||
|
}
|
||||||
|
|
||||||
|
fn swap_buffers<F>(&self, flip: F) -> ::std::result::Result<(), SwapBuffersError>
|
||||||
|
where
|
||||||
|
F: FnOnce() -> ::std::result::Result<(), SwapBuffersError>
|
||||||
|
{
|
||||||
|
if ::std::borrow::Borrow::borrow(&self.crtc).commit_pending() {
|
||||||
|
self.recreate(flip).map_err(|_| SwapBuffersError::ContextLost)
|
||||||
|
} else {
|
||||||
|
self.page_flip(flip)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,47 @@
|
||||||
|
//!
|
||||||
|
//! Errors thrown by the `DrmDevice` and `DrmBackend`
|
||||||
|
//!
|
||||||
|
|
||||||
|
error_chain! {
|
||||||
|
errors {
|
||||||
|
#[doc = "Creation of gbm device failed"]
|
||||||
|
InitFailed {
|
||||||
|
description("Creation of gbm device failed"),
|
||||||
|
display("Creation of gbm device failed"),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[doc = "Creation of gbm surface failed"]
|
||||||
|
SurfaceCreationFailed {
|
||||||
|
description("Creation of gbm surface failed"),
|
||||||
|
display("Creation of gbm surface failed"),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[doc = "Creation of gbm buffer object failed"]
|
||||||
|
BufferCreationFailed {
|
||||||
|
description("Creation of gbm buffer object failed"),
|
||||||
|
display("Creation of gbm buffer object failed"),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[doc = "Writing to gbm buffer failed"]
|
||||||
|
BufferWriteFailed {
|
||||||
|
description("Writing to gbm buffer failed"),
|
||||||
|
display("Writing to gbm buffer failed"),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[doc = "Lock of gbm surface front buffer failed"]
|
||||||
|
FrontBufferLockFailed {
|
||||||
|
description("Lock of gbm surface front buffer failed"),
|
||||||
|
display("Lock of gbm surface front buffer failed"),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[doc = "Underlying backend failed"]
|
||||||
|
UnderlyingBackendError {
|
||||||
|
description("The underlying backend reported an error"),
|
||||||
|
display("The underlying backend reported an error"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreign_links {
|
||||||
|
FailedToSwap(::backend::graphics::SwapBuffersError);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,191 @@
|
||||||
|
use super::{Device, RawDevice, Surface, DeviceHandler};
|
||||||
|
|
||||||
|
use drm::control::{crtc, framebuffer, Device as ControlDevice, Mode};
|
||||||
|
use gbm::{self, Format as GbmFormat, BufferObjectFlags};
|
||||||
|
|
||||||
|
use std::cell::{Cell, RefCell};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::rc::{Rc, Weak};
|
||||||
|
use std::sync::{Once, ONCE_INIT};
|
||||||
|
use std::os::unix::io::{AsRawFd, RawFd};
|
||||||
|
|
||||||
|
pub mod error;
|
||||||
|
use self::error::*;
|
||||||
|
|
||||||
|
mod surface;
|
||||||
|
pub use self::surface::GbmSurface;
|
||||||
|
|
||||||
|
pub mod egl;
|
||||||
|
|
||||||
|
#[cfg(feature = "backend_session")]
|
||||||
|
pub mod session;
|
||||||
|
|
||||||
|
static LOAD: Once = ONCE_INIT;
|
||||||
|
|
||||||
|
/// Representation of an open gbm device to create rendering backends
|
||||||
|
pub struct GbmDevice<D: RawDevice + ControlDevice + 'static>
|
||||||
|
where
|
||||||
|
<D as Device>::Return: ::std::borrow::Borrow<<D as RawDevice>::Surface>
|
||||||
|
{
|
||||||
|
pub(in self) dev: Rc<RefCell<gbm::Device<D>>>,
|
||||||
|
backends: Rc<RefCell<HashMap<crtc::Handle, Weak<GbmSurface<D>>>>>,
|
||||||
|
logger: ::slog::Logger,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<D: RawDevice + ControlDevice + 'static> GbmDevice<D>
|
||||||
|
where
|
||||||
|
<D as Device>::Return: ::std::borrow::Borrow<<D as RawDevice>::Surface>
|
||||||
|
{
|
||||||
|
/// Create a new `GbmDevice` from an open drm node
|
||||||
|
///
|
||||||
|
/// Returns an error if the file is no valid drm node or context creation was not
|
||||||
|
/// successful.
|
||||||
|
pub fn new<L>(mut dev: D, logger: L) -> Result<Self>
|
||||||
|
where
|
||||||
|
L: Into<Option<::slog::Logger>>,
|
||||||
|
{
|
||||||
|
/* 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.
|
||||||
|
*/
|
||||||
|
LOAD.call_once(|| unsafe {
|
||||||
|
nix::libc::dlopen(
|
||||||
|
"libglapi.so.0".as_ptr() as *const _,
|
||||||
|
nix::libc::RTLD_LAZY | nix::libc::RTLD_GLOBAL,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
let log = ::slog_or_stdlog(logger).new(o!("smithay_module" => "backend_gbm"));
|
||||||
|
|
||||||
|
dev.clear_handler();
|
||||||
|
|
||||||
|
debug!(log, "Creating gbm device");
|
||||||
|
Ok(GbmDevice {
|
||||||
|
// Open the gbm device from the drm device
|
||||||
|
dev: Rc::new(RefCell::new(gbm::Device::new(dev).chain_err(|| ErrorKind::InitFailed)?)),
|
||||||
|
backends: Rc::new(RefCell::new(HashMap::new())),
|
||||||
|
logger: log,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct InternalDeviceHandler<D: RawDevice + ControlDevice + 'static>
|
||||||
|
where
|
||||||
|
<D as Device>::Return: ::std::borrow::Borrow<<D as RawDevice>::Surface>
|
||||||
|
{
|
||||||
|
handler: Box<DeviceHandler<Device=GbmDevice<D>> + 'static>,
|
||||||
|
backends: Weak<RefCell<HashMap<crtc::Handle, Weak<GbmSurface<D>>>>>,
|
||||||
|
logger: ::slog::Logger,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<D: RawDevice + ControlDevice + 'static> DeviceHandler for InternalDeviceHandler<D>
|
||||||
|
where
|
||||||
|
<D as Device>::Return: ::std::borrow::Borrow<<D as RawDevice>::Surface>
|
||||||
|
{
|
||||||
|
type Device = D;
|
||||||
|
|
||||||
|
fn vblank(&mut self, surface: &<D as Device>::Surface) {
|
||||||
|
if let Some(backends) = self.backends.upgrade() {
|
||||||
|
if let Some(surface) = backends.borrow().get(&surface.crtc()) {
|
||||||
|
if let Some(surface) = surface.upgrade() {
|
||||||
|
surface.unlock_buffer();
|
||||||
|
self.handler.vblank(&*surface);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
warn!(self.logger, "Surface ({:?}) not managed by gbm, event not handled.", surface.crtc());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn error(&mut self, error: <<D as Device>::Surface as Surface>::Error) {
|
||||||
|
self.handler.error(ResultExt::<()>::chain_err(Err(error), || ErrorKind::UnderlyingBackendError).unwrap_err())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<D: RawDevice + ControlDevice + 'static> Device for GbmDevice<D>
|
||||||
|
where
|
||||||
|
<D as Device>::Return: ::std::borrow::Borrow<<D as RawDevice>::Surface>
|
||||||
|
{
|
||||||
|
type Surface = GbmSurface<D>;
|
||||||
|
type Return = Rc<GbmSurface<D>>;
|
||||||
|
|
||||||
|
fn set_handler(&mut self, handler: impl DeviceHandler<Device=Self> + 'static) {
|
||||||
|
self.dev.borrow_mut().set_handler(InternalDeviceHandler {
|
||||||
|
handler: Box::new(handler),
|
||||||
|
backends: Rc::downgrade(&self.backends),
|
||||||
|
logger: self.logger.clone(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn clear_handler(&mut self) {
|
||||||
|
self.dev.borrow_mut().clear_handler();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_surface(
|
||||||
|
&mut self,
|
||||||
|
crtc: crtc::Handle,
|
||||||
|
mode: Mode,
|
||||||
|
connectors: impl Into<<Self::Surface as Surface>::Connectors>
|
||||||
|
) -> Result<Rc<GbmSurface<D>>> {
|
||||||
|
info!(self.logger, "Initializing GbmSurface");
|
||||||
|
|
||||||
|
let (w, h) = mode.size();
|
||||||
|
let surface = self.dev.borrow().create_surface(
|
||||||
|
w as u32,
|
||||||
|
h as u32,
|
||||||
|
GbmFormat::XRGB8888,
|
||||||
|
BufferObjectFlags::SCANOUT | BufferObjectFlags::RENDERING,
|
||||||
|
).chain_err(|| ErrorKind::SurfaceCreationFailed)?;
|
||||||
|
|
||||||
|
// 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::FrontBufferLockFailed)?;
|
||||||
|
|
||||||
|
debug!(self.logger, "FrontBuffer color format: {:?}", front_bo.format());
|
||||||
|
|
||||||
|
// we need a framebuffer for the front buffer
|
||||||
|
let fb = framebuffer::create(&*self.dev.borrow(), &*front_bo).chain_err(|| ErrorKind::UnderlyingBackendError)?;
|
||||||
|
front_bo.set_userdata(fb).unwrap();
|
||||||
|
|
||||||
|
let cursor = Cell::new((
|
||||||
|
self.dev.borrow()
|
||||||
|
.create_buffer_object(
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
GbmFormat::ARGB8888,
|
||||||
|
BufferObjectFlags::CURSOR | BufferObjectFlags::WRITE,
|
||||||
|
).chain_err(|| ErrorKind::BufferCreationFailed)?,
|
||||||
|
(0, 0),
|
||||||
|
));
|
||||||
|
|
||||||
|
let backend = Rc::new(GbmSurface {
|
||||||
|
dev: self.dev.clone(),
|
||||||
|
surface: RefCell::new(surface),
|
||||||
|
crtc: Device::create_surface(&mut **self.dev.borrow_mut(), crtc, mode, connectors)
|
||||||
|
.chain_err(|| ErrorKind::UnderlyingBackendError)?,
|
||||||
|
cursor,
|
||||||
|
current_frame_buffer: Cell::new(fb),
|
||||||
|
front_buffer: Cell::new(front_bo),
|
||||||
|
next_buffer: Cell::new(None),
|
||||||
|
logger: self.logger.new(o!("crtc" => format!("{:?}", crtc))),
|
||||||
|
});
|
||||||
|
self.backends.borrow_mut().insert(crtc, Rc::downgrade(&backend));
|
||||||
|
Ok(backend)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn process_events(&mut self) {
|
||||||
|
self.dev.borrow_mut().process_events()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<D: RawDevice + ControlDevice + 'static> AsRawFd for GbmDevice<D>
|
||||||
|
where
|
||||||
|
<D as Device>::Return: ::std::borrow::Borrow<<D as RawDevice>::Surface>
|
||||||
|
{
|
||||||
|
fn as_raw_fd(&self) -> RawFd {
|
||||||
|
self.dev.borrow().as_raw_fd()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,89 @@
|
||||||
|
use drm::control::{crtc, Device as ControlDevice, ResourceInfo};
|
||||||
|
use gbm::BufferObject;
|
||||||
|
use std::cell::RefCell;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::rc::{Rc, Weak};
|
||||||
|
use std::os::unix::io::RawFd;
|
||||||
|
|
||||||
|
use backend::session::{AsSessionObserver, SessionObserver};
|
||||||
|
use backend::drm::{Device, RawDevice, RawSurface};
|
||||||
|
use super::{GbmDevice, GbmSurface};
|
||||||
|
|
||||||
|
/// `SessionObserver` linked to the `DrmDevice` it was created from.
|
||||||
|
pub struct GbmDeviceObserver<
|
||||||
|
S: SessionObserver + 'static,
|
||||||
|
D: RawDevice + ControlDevice + AsSessionObserver<S> + 'static,
|
||||||
|
>
|
||||||
|
where
|
||||||
|
<D as Device>::Return: ::std::borrow::Borrow<<D as RawDevice>::Surface>
|
||||||
|
{
|
||||||
|
observer: S,
|
||||||
|
backends: Weak<RefCell<HashMap<crtc::Handle, Weak<GbmSurface<D>>>>>,
|
||||||
|
logger: ::slog::Logger,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<
|
||||||
|
S: SessionObserver + 'static,
|
||||||
|
D: RawDevice + ControlDevice + AsSessionObserver<S> + 'static,
|
||||||
|
> AsSessionObserver<GbmDeviceObserver<S, D>> for GbmDevice<D>
|
||||||
|
where
|
||||||
|
<D as Device>::Return: ::std::borrow::Borrow<<D as RawDevice>::Surface>
|
||||||
|
{
|
||||||
|
fn observer(&mut self) -> GbmDeviceObserver<S, D> {
|
||||||
|
GbmDeviceObserver {
|
||||||
|
observer: (**self.dev.borrow_mut()).observer(),
|
||||||
|
backends: Rc::downgrade(&self.backends),
|
||||||
|
logger: self.logger.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<
|
||||||
|
S: SessionObserver + 'static,
|
||||||
|
D: RawDevice + ControlDevice + AsSessionObserver<S> + 'static,
|
||||||
|
> SessionObserver for GbmDeviceObserver<S, D>
|
||||||
|
where
|
||||||
|
<D as Device>::Return: ::std::borrow::Borrow<<D as RawDevice>::Surface>
|
||||||
|
{
|
||||||
|
fn pause(&mut self, devnum: Option<(u32, u32)>) {
|
||||||
|
self.observer.pause(devnum);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn activate(&mut self, devnum: Option<(u32, u32, Option<RawFd>)>) {
|
||||||
|
self.observer.activate(devnum);
|
||||||
|
let mut crtcs = Vec::new();
|
||||||
|
if let Some(backends) = self.backends.upgrade() {
|
||||||
|
for (crtc, backend) in backends.borrow().iter() {
|
||||||
|
if let Some(backend) = backend.upgrade() {
|
||||||
|
// restart rendering loop
|
||||||
|
if let Err(err) =
|
||||||
|
::std::borrow::Borrow::borrow(&backend.crtc).page_flip(backend.current_frame_buffer.get().handle())
|
||||||
|
{
|
||||||
|
warn!(self.logger, "Failed to restart rendering loop. Error: {}", err);
|
||||||
|
}
|
||||||
|
// reset cursor
|
||||||
|
{
|
||||||
|
let &(ref cursor, ref hotspot): &(BufferObject<()>, (u32, u32)) =
|
||||||
|
unsafe { &*backend.cursor.as_ptr() };
|
||||||
|
if crtc::set_cursor2(
|
||||||
|
&*backend.dev.borrow(),
|
||||||
|
*crtc,
|
||||||
|
cursor,
|
||||||
|
((*hotspot).0 as i32, (*hotspot).1 as i32),
|
||||||
|
).is_err()
|
||||||
|
{
|
||||||
|
if let Err(err) = crtc::set_cursor(&*backend.dev.borrow(), *crtc, cursor) {
|
||||||
|
error!(self.logger, "Failed to reset cursor. Error: {}", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
crtcs.push(*crtc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for crtc in crtcs {
|
||||||
|
backends.borrow_mut().remove(&crtc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,329 @@
|
||||||
|
use super::error::*;
|
||||||
|
use super::super::{Device, RawDevice, Surface, RawSurface};
|
||||||
|
|
||||||
|
use drm::control::{crtc, connector, framebuffer, Mode, ResourceInfo};
|
||||||
|
use gbm::{self, SurfaceBufferHandle, Format as GbmFormat, BufferObject, BufferObjectFlags};
|
||||||
|
use image::{ImageBuffer, Rgba};
|
||||||
|
|
||||||
|
use std::cell::{Cell, RefCell};
|
||||||
|
use std::rc::Rc;
|
||||||
|
use std::os::unix::io::AsRawFd;
|
||||||
|
|
||||||
|
use backend::drm::legacy::{LegacyDrmDevice, LegacyDrmSurface};
|
||||||
|
use backend::graphics::CursorBackend;
|
||||||
|
use backend::graphics::SwapBuffersError;
|
||||||
|
|
||||||
|
pub struct GbmSurface<D: RawDevice + 'static>
|
||||||
|
where
|
||||||
|
<D as Device>::Return: ::std::borrow::Borrow<<D as RawDevice>::Surface>
|
||||||
|
{
|
||||||
|
pub(in super) dev: Rc<RefCell<gbm::Device<D>>>,
|
||||||
|
pub(in super) surface: RefCell<gbm::Surface<framebuffer::Info>>,
|
||||||
|
pub(in super) crtc: <D as Device>::Return,
|
||||||
|
pub(in super) cursor: Cell<(BufferObject<()>, (u32, u32))>,
|
||||||
|
pub(in super) current_frame_buffer: Cell<framebuffer::Info>,
|
||||||
|
pub(in super) front_buffer: Cell<SurfaceBufferHandle<framebuffer::Info>>,
|
||||||
|
pub(in super) next_buffer: Cell<Option<SurfaceBufferHandle<framebuffer::Info>>>,
|
||||||
|
pub(in super) logger: ::slog::Logger,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<D: RawDevice + 'static> GbmSurface<D>
|
||||||
|
where
|
||||||
|
<D as Device>::Return: ::std::borrow::Borrow<<D as RawDevice>::Surface>
|
||||||
|
{
|
||||||
|
pub(in super) fn unlock_buffer(&self) {
|
||||||
|
// after the page swap is finished we need to release the rendered buffer.
|
||||||
|
// this is called from the PageFlipHandler
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn page_flip<F>(&self, flip: F) -> ::std::result::Result<(), SwapBuffersError>
|
||||||
|
where
|
||||||
|
F: FnOnce() -> ::std::result::Result<(), SwapBuffersError>
|
||||||
|
{
|
||||||
|
let res = {
|
||||||
|
let nb = self.next_buffer.take();
|
||||||
|
let res = nb.is_some();
|
||||||
|
self.next_buffer.set(nb);
|
||||||
|
res
|
||||||
|
};
|
||||||
|
if res {
|
||||||
|
// We cannot call lock_front_buffer anymore without releasing the previous buffer, which will happen when the page flip is done
|
||||||
|
warn!(
|
||||||
|
self.logger,
|
||||||
|
"Tried to swap with an already queued flip"
|
||||||
|
);
|
||||||
|
return Err(SwapBuffersError::AlreadySwapped);
|
||||||
|
}
|
||||||
|
|
||||||
|
// flip normally
|
||||||
|
flip()?;
|
||||||
|
|
||||||
|
// 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
|
||||||
|
.borrow()
|
||||||
|
.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()
|
||||||
|
.map_err(|_| SwapBuffersError::ContextLost)?
|
||||||
|
.cloned();
|
||||||
|
let fb = if let Some(info) = maybe_fb {
|
||||||
|
info
|
||||||
|
} else {
|
||||||
|
let fb = framebuffer::create(::std::borrow::Borrow::borrow(&self.crtc), &*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");
|
||||||
|
::std::borrow::Borrow::borrow(&self.crtc).page_flip(fb.handle())?;
|
||||||
|
|
||||||
|
self.current_frame_buffer.set(fb);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn recreate<F>(&self, flip: F) -> Result<()>
|
||||||
|
where
|
||||||
|
F: FnOnce() -> ::std::result::Result<(), SwapBuffersError>
|
||||||
|
{
|
||||||
|
let (w, h) = self.pending_mode().size();
|
||||||
|
|
||||||
|
// 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
|
||||||
|
.dev
|
||||||
|
.borrow_mut()
|
||||||
|
.create_surface(
|
||||||
|
w as u32,
|
||||||
|
h as u32,
|
||||||
|
GbmFormat::XRGB8888,
|
||||||
|
BufferObjectFlags::SCANOUT | BufferObjectFlags::RENDERING,
|
||||||
|
).chain_err(|| ErrorKind::SurfaceCreationFailed)?;
|
||||||
|
|
||||||
|
flip()?;
|
||||||
|
|
||||||
|
// 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(::std::borrow::Borrow::borrow(&self.crtc), 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 mut old_front_bo = self.front_buffer.replace({
|
||||||
|
let mut front_bo = surface
|
||||||
|
.lock_front_buffer()
|
||||||
|
.chain_err(|| ErrorKind::FrontBufferLockFailed)?;
|
||||||
|
|
||||||
|
debug!(
|
||||||
|
self.logger,
|
||||||
|
"FrontBuffer color format: {:?}",
|
||||||
|
front_bo.format()
|
||||||
|
);
|
||||||
|
|
||||||
|
// we also need a new framebuffer for the front buffer
|
||||||
|
let fb = framebuffer::create(::std::borrow::Borrow::borrow(&self.crtc), &*front_bo)
|
||||||
|
.chain_err(|| ErrorKind::UnderlyingBackendError)?;
|
||||||
|
|
||||||
|
::std::borrow::Borrow::borrow(&self.crtc).commit(fb.handle())
|
||||||
|
.chain_err(|| ErrorKind::UnderlyingBackendError)?;
|
||||||
|
|
||||||
|
front_bo.set_userdata(fb).unwrap();
|
||||||
|
front_bo
|
||||||
|
});
|
||||||
|
if let Ok(Some(fb)) = old_front_bo.take_userdata() {
|
||||||
|
if let Err(err) = framebuffer::destroy(::std::borrow::Borrow::borrow(&self.crtc), fb.handle()) {
|
||||||
|
warn!(
|
||||||
|
self.logger,
|
||||||
|
"Error releasing old front_buffer framebuffer: {:?}", err
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Drop the old surface after cleanup
|
||||||
|
*self.surface.borrow_mut() = surface;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<D: RawDevice + 'static> Surface for GbmSurface<D>
|
||||||
|
where
|
||||||
|
<D as Device>::Return: ::std::borrow::Borrow<<D as RawDevice>::Surface>
|
||||||
|
{
|
||||||
|
type Connectors = <<D as Device>::Surface as Surface>::Connectors;
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
fn crtc(&self) -> crtc::Handle {
|
||||||
|
::std::borrow::Borrow::borrow(&self.crtc)
|
||||||
|
.crtc()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn current_connectors(&self) -> Self::Connectors {
|
||||||
|
::std::borrow::Borrow::borrow(&self.crtc)
|
||||||
|
.current_connectors()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pending_connectors(&self) -> Self::Connectors {
|
||||||
|
::std::borrow::Borrow::borrow(&self.crtc)
|
||||||
|
.pending_connectors()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_connector(&self, connector: connector::Handle) -> Result<()> {
|
||||||
|
::std::borrow::Borrow::borrow(&self.crtc)
|
||||||
|
.add_connector(connector)
|
||||||
|
.chain_err(|| ErrorKind::UnderlyingBackendError)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove_connector(&self, connector: connector::Handle) -> Result<()> {
|
||||||
|
::std::borrow::Borrow::borrow(&self.crtc)
|
||||||
|
.remove_connector(connector)
|
||||||
|
.chain_err(|| ErrorKind::UnderlyingBackendError)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn current_mode(&self) -> Mode {
|
||||||
|
::std::borrow::Borrow::borrow(&self.crtc)
|
||||||
|
.current_mode()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pending_mode(&self) -> Mode {
|
||||||
|
::std::borrow::Borrow::borrow(&self.crtc)
|
||||||
|
.pending_mode()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn use_mode(&self, mode: Mode) -> Result<()> {
|
||||||
|
::std::borrow::Borrow::borrow(&self.crtc)
|
||||||
|
.use_mode(mode)
|
||||||
|
.chain_err(|| ErrorKind::UnderlyingBackendError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME:
|
||||||
|
//
|
||||||
|
// Option 1: When there is GAT support, impl `GraphicsBackend` for `LegacyDrmBackend`
|
||||||
|
// using a new generic `B: Buffer` and use this:
|
||||||
|
/*
|
||||||
|
impl<'a, D: RawDevice + 'static> CursorBackend<'a> for GbmSurface<D>
|
||||||
|
where
|
||||||
|
<D as Device>::Return: ::std::borrow::Borrow<<D as RawDevice>::Surface>,
|
||||||
|
<D as RawDevice>::Surface: CursorBackend<'a>,
|
||||||
|
<<D as RawDevice>::Surface as CursorBackend<'a>>::CursorFormat: Buffer,
|
||||||
|
<<D as RawDevice>::Surface as CursorBackend<'a>>::Error: ::std::error::Error + Send
|
||||||
|
{
|
||||||
|
*/
|
||||||
|
//
|
||||||
|
// Option 2: When equality checks in where clauses are supported, we could at least do this:
|
||||||
|
/*
|
||||||
|
impl<'a, D: RawDevice + 'static> GraphicsBackend<'a> for GbmSurface<D>
|
||||||
|
where
|
||||||
|
<D as Device>::Return: ::std::borrow::Borrow<<D as RawDevice>::Surface>,
|
||||||
|
<D as RawDevice>::Surface: CursorBackend<'a>,
|
||||||
|
<<D as RawDevice>::Surface as CursorBackend<'a>>::CursorFormat=&'a Buffer,
|
||||||
|
<<D as RawDevice>::Surface as CursorBackend<'a>>::Error: ::std::error::Error + Send
|
||||||
|
{
|
||||||
|
*/
|
||||||
|
// But for now got to do this:
|
||||||
|
|
||||||
|
impl<'a, A: AsRawFd + 'static> CursorBackend<'a> for GbmSurface<LegacyDrmDevice<A>> {
|
||||||
|
type CursorFormat = &'a ImageBuffer<Rgba<u8>, Vec<u8>>;
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
fn set_cursor_position(&self, x: u32, y: u32) -> Result<()> {
|
||||||
|
ResultExt::chain_err(
|
||||||
|
::std::borrow::Borrow::<Rc<LegacyDrmSurface<A>>>::borrow(&self.crtc)
|
||||||
|
.set_cursor_position(x, y),
|
||||||
|
|| ErrorKind::UnderlyingBackendError)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_cursor_representation<'b>(
|
||||||
|
&'b self,
|
||||||
|
buffer: &ImageBuffer<Rgba<u8>, Vec<u8>>,
|
||||||
|
hotspot: (u32, u32),
|
||||||
|
) -> Result<()>
|
||||||
|
where 'a: 'b
|
||||||
|
{
|
||||||
|
let (w, h) = buffer.dimensions();
|
||||||
|
debug!(self.logger, "Importing cursor");
|
||||||
|
|
||||||
|
// import the cursor into a buffer we can render
|
||||||
|
let mut cursor = self
|
||||||
|
.dev
|
||||||
|
.borrow_mut()
|
||||||
|
.create_buffer_object(
|
||||||
|
w,
|
||||||
|
h,
|
||||||
|
GbmFormat::ARGB8888,
|
||||||
|
BufferObjectFlags::CURSOR | BufferObjectFlags::WRITE,
|
||||||
|
).chain_err(|| ErrorKind::BufferCreationFailed)?;
|
||||||
|
|
||||||
|
cursor
|
||||||
|
.write(&**buffer)
|
||||||
|
.chain_err(|| ErrorKind::BufferWriteFailed)?
|
||||||
|
.chain_err(|| ErrorKind::BufferWriteFailed)?;
|
||||||
|
|
||||||
|
trace!(self.logger, "Setting the new imported cursor");
|
||||||
|
|
||||||
|
ResultExt::chain_err(
|
||||||
|
::std::borrow::Borrow::<Rc<LegacyDrmSurface<A>>>::borrow(&self.crtc)
|
||||||
|
.set_cursor_representation(&cursor, hotspot),
|
||||||
|
|| ErrorKind::UnderlyingBackendError)?;
|
||||||
|
|
||||||
|
// and store it
|
||||||
|
self.cursor.set((cursor, hotspot));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<D: RawDevice + 'static> Drop for GbmSurface<D>
|
||||||
|
where
|
||||||
|
<D as Device>::Return: ::std::borrow::Borrow<<D as RawDevice>::Surface>
|
||||||
|
{
|
||||||
|
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)
|
||||||
|
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.borrow().lock_front_buffer() {
|
||||||
|
next.take_userdata()
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
} {
|
||||||
|
// ignore failure at this point
|
||||||
|
let _ = framebuffer::destroy(::std::borrow::Borrow::borrow(&self.crtc), fb.handle());
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Ok(Some(fb)) = self.front_buffer.get_mut().take_userdata() {
|
||||||
|
// ignore failure at this point
|
||||||
|
let _ = framebuffer::destroy(::std::borrow::Borrow::borrow(&self.crtc), fb.handle());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,8 +1,7 @@
|
||||||
//!
|
//!
|
||||||
//! Errors thrown by the `DrmDevice` and `DrmBackend`
|
//! Errors thrown by the `LegacyDrmDevice` and `LegacyDrmSurface`
|
||||||
//!
|
//!
|
||||||
|
|
||||||
use backend::graphics::egl::error as egl;
|
|
||||||
use drm::control::{connector, crtc, Mode};
|
use drm::control::{connector, crtc, Mode};
|
||||||
|
|
||||||
error_chain! {
|
error_chain! {
|
||||||
|
@ -23,18 +22,6 @@ error_chain! {
|
||||||
description("Unable to determine device id of drm device"),
|
description("Unable to determine device id of drm device"),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[doc = "Creation of gbm resource failed"]
|
|
||||||
GbmInitFailed {
|
|
||||||
description("Creation of gbm resource failed"),
|
|
||||||
display("Creation of gbm resource failed"),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[doc = "Swapping front buffers failed"]
|
|
||||||
FailedToSwap {
|
|
||||||
description("Swapping front buffers failed"),
|
|
||||||
display("Swapping front buffers failed"),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[doc = "Device is currently paused"]
|
#[doc = "Device is currently paused"]
|
||||||
DeviceInactive {
|
DeviceInactive {
|
||||||
description("Device is currently paused, operation rejected"),
|
description("Device is currently paused, operation rejected"),
|
||||||
|
@ -60,7 +47,7 @@ error_chain! {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
links {
|
foreign_links {
|
||||||
EGL(egl::Error, egl::ErrorKind) #[doc = "EGL error"];
|
FailedToSwap(::backend::graphics::SwapBuffersError) #[doc = "Swapping front buffers failed"];
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,257 @@
|
||||||
|
use super::{Device, RawDevice, Surface, DeviceHandler, DevPath};
|
||||||
|
|
||||||
|
use drm::Device as BasicDevice;
|
||||||
|
use drm::control::{crtc, connector, encoder, Device as ControlDevice, Mode, ResourceInfo};
|
||||||
|
use nix::libc::dev_t;
|
||||||
|
use nix::sys::stat::fstat;
|
||||||
|
|
||||||
|
use std::cell::RefCell;
|
||||||
|
use std::collections::{HashMap, HashSet};
|
||||||
|
use std::rc::{Rc, Weak};
|
||||||
|
use std::os::unix::io::{AsRawFd, RawFd};
|
||||||
|
use std::sync::{Arc, RwLock};
|
||||||
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
|
|
||||||
|
mod surface;
|
||||||
|
pub use self::surface::LegacyDrmSurface;
|
||||||
|
use self::surface::State;
|
||||||
|
|
||||||
|
pub mod error;
|
||||||
|
use self::error::*;
|
||||||
|
|
||||||
|
#[cfg(feature = "backend_session")]
|
||||||
|
pub mod session;
|
||||||
|
|
||||||
|
pub struct LegacyDrmDevice<A: AsRawFd + 'static> {
|
||||||
|
dev: Rc<Dev<A>>,
|
||||||
|
dev_id: dev_t,
|
||||||
|
priviledged: bool,
|
||||||
|
active: Arc<AtomicBool>,
|
||||||
|
old_state: HashMap<crtc::Handle, (crtc::Info, Vec<connector::Handle>)>,
|
||||||
|
backends: Rc<RefCell<HashMap<crtc::Handle, Weak<LegacyDrmSurface<A>>>>>,
|
||||||
|
handler: Option<RefCell<Box<DeviceHandler<Device=LegacyDrmDevice<A>>>>>,
|
||||||
|
logger: ::slog::Logger,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(in crate::backend::drm) struct Dev<A: AsRawFd + 'static>(A);
|
||||||
|
impl<A: AsRawFd + 'static> AsRawFd for Dev<A> {
|
||||||
|
fn as_raw_fd(&self) -> RawFd {
|
||||||
|
self.0.as_raw_fd()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<A: AsRawFd + 'static> BasicDevice for Dev<A> {}
|
||||||
|
impl<A: AsRawFd + 'static> ControlDevice for Dev<A> {}
|
||||||
|
|
||||||
|
impl<A: AsRawFd + 'static> LegacyDrmDevice<A> {
|
||||||
|
/// Create a new `LegacyDrmDevice` from an open drm node
|
||||||
|
///
|
||||||
|
/// Returns an error if the file is no valid drm node or context creation was not
|
||||||
|
/// successful.
|
||||||
|
pub fn new<L>(dev: A, logger: L) -> Result<Self>
|
||||||
|
where
|
||||||
|
L: Into<Option<::slog::Logger>>,
|
||||||
|
{
|
||||||
|
let log = ::slog_or_stdlog(logger).new(o!("smithay_module" => "backend_drm"));
|
||||||
|
|
||||||
|
let dev_id = fstat(dev.as_raw_fd())
|
||||||
|
.chain_err(|| ErrorKind::UnableToGetDeviceId)?
|
||||||
|
.st_rdev;
|
||||||
|
|
||||||
|
let mut drm = LegacyDrmDevice {
|
||||||
|
// Open the drm device and create a context based on that
|
||||||
|
dev: Rc::new(Dev(dev)),
|
||||||
|
dev_id,
|
||||||
|
priviledged: true,
|
||||||
|
active: Arc::new(AtomicBool::new(true)),
|
||||||
|
old_state: HashMap::new(),
|
||||||
|
backends: Rc::new(RefCell::new(HashMap::new())),
|
||||||
|
handler: None,
|
||||||
|
logger: log.clone(),
|
||||||
|
};
|
||||||
|
|
||||||
|
info!(log, "DrmDevice initializing");
|
||||||
|
|
||||||
|
// we want to modeset, so we better be the master, if we run via a tty session
|
||||||
|
if drm.set_master().is_err() {
|
||||||
|
warn!(log, "Unable to become drm master, assuming unpriviledged mode");
|
||||||
|
drm.priviledged = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
let res_handles = drm.resource_handles().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!("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!("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!("Error loading crtc info on {:?}", drm.dev_path()))
|
||||||
|
})?;
|
||||||
|
drm.old_state
|
||||||
|
.entry(crtc)
|
||||||
|
.or_insert((info, Vec::new()))
|
||||||
|
.1
|
||||||
|
.push(con);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(drm)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn dev_id(&self) -> dev_t {
|
||||||
|
self.dev_id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<A: AsRawFd + 'static> AsRawFd for LegacyDrmDevice<A> {
|
||||||
|
fn as_raw_fd(&self) -> RawFd {
|
||||||
|
self.dev.0.as_raw_fd()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<A: AsRawFd + 'static> BasicDevice for LegacyDrmDevice<A> {}
|
||||||
|
impl<A: AsRawFd + 'static> ControlDevice for LegacyDrmDevice<A> {}
|
||||||
|
|
||||||
|
impl<A: AsRawFd + 'static> Device for LegacyDrmDevice<A> {
|
||||||
|
type Surface = LegacyDrmSurface<A>;
|
||||||
|
type Return = Rc<LegacyDrmSurface<A>>;
|
||||||
|
|
||||||
|
fn set_handler(&mut self, handler: impl DeviceHandler<Device=Self> + 'static) {
|
||||||
|
self.handler = Some(RefCell::new(Box::new(handler)));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn clear_handler(&mut self) {
|
||||||
|
let _ = self.handler.take();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_surface(
|
||||||
|
&mut self,
|
||||||
|
crtc: crtc::Handle,
|
||||||
|
mode: Mode,
|
||||||
|
connectors: impl Into<<Self::Surface as Surface>::Connectors>
|
||||||
|
) -> Result<Rc<LegacyDrmSurface<A>>> {
|
||||||
|
if self.backends.borrow().contains_key(&crtc) {
|
||||||
|
bail!(ErrorKind::CrtcAlreadyInUse(crtc));
|
||||||
|
}
|
||||||
|
|
||||||
|
if !self.active.load(Ordering::SeqCst) {
|
||||||
|
bail!(ErrorKind::DeviceInactive);
|
||||||
|
}
|
||||||
|
|
||||||
|
let connectors: HashSet<_> = connectors.into();
|
||||||
|
// 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, *connector).chain_err(|| {
|
||||||
|
ErrorKind::DrmDev(format!("Error loading connector info on {:?}", self.dev_path()))
|
||||||
|
})?;
|
||||||
|
|
||||||
|
// check the mode
|
||||||
|
if !con_info.modes().contains(&mode) {
|
||||||
|
bail!(ErrorKind::ModeNotSuitable(mode));
|
||||||
|
}
|
||||||
|
|
||||||
|
// check for every connector which encoders it does support
|
||||||
|
let encoders = con_info
|
||||||
|
.encoders()
|
||||||
|
.iter()
|
||||||
|
.map(|encoder| {
|
||||||
|
encoder::Info::load_from_device(self, *encoder).chain_err(|| {
|
||||||
|
ErrorKind::DrmDev(format!("Error loading encoder info on {:?}", self.dev_path()))
|
||||||
|
})
|
||||||
|
}).collect::<Result<Vec<encoder::Info>>>()?;
|
||||||
|
|
||||||
|
// and if any encoder supports the selected crtc
|
||||||
|
let resource_handles = self.resource_handles().chain_err(|| {
|
||||||
|
ErrorKind::DrmDev(format!("Error loading drm resources on {:?}", self.dev_path()))
|
||||||
|
})?;
|
||||||
|
if !encoders
|
||||||
|
.iter()
|
||||||
|
.map(|encoder| encoder.possible_crtcs())
|
||||||
|
.any(|crtc_list| resource_handles.filter_crtcs(crtc_list).contains(&crtc))
|
||||||
|
{
|
||||||
|
bail!(ErrorKind::NoSuitableEncoder(con_info, crtc))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// configuration is valid, the kernel will figure out the rest
|
||||||
|
let logger = self.logger.new(o!("crtc" => format!("{:?}", crtc)));
|
||||||
|
|
||||||
|
let state = State {
|
||||||
|
mode,
|
||||||
|
connectors,
|
||||||
|
};
|
||||||
|
|
||||||
|
let backend = Rc::new(LegacyDrmSurface {
|
||||||
|
dev: self.dev.clone(),
|
||||||
|
crtc,
|
||||||
|
state: RwLock::new(state.clone()),
|
||||||
|
pending: RwLock::new(state),
|
||||||
|
logger,
|
||||||
|
});
|
||||||
|
|
||||||
|
self.backends.borrow_mut().insert(crtc, Rc::downgrade(&backend));
|
||||||
|
Ok(backend)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn process_events(&mut self) {
|
||||||
|
match crtc::receive_events(self) {
|
||||||
|
Ok(events) => for event in events {
|
||||||
|
if let crtc::Event::PageFlip(event) = event {
|
||||||
|
if self.active.load(Ordering::SeqCst) {
|
||||||
|
if let Some(backend) = self.backends.borrow().get(&event.crtc).iter().flat_map(|x| x.upgrade()).next() {
|
||||||
|
trace!(self.logger, "Handling event for backend {:?}", event.crtc);
|
||||||
|
if let Some(handler) = self.handler.as_ref() {
|
||||||
|
handler.borrow_mut().vblank(&backend);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.backends.borrow_mut().remove(&event.crtc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(err) => if let Some(handler) = self.handler.as_ref() {
|
||||||
|
handler.borrow_mut().error(ResultExt::<()>::chain_err(Err(err), ||
|
||||||
|
ErrorKind::DrmDev(format!("Error processing drm events on {:?}", self.dev_path()))
|
||||||
|
).unwrap_err());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<A: AsRawFd + 'static> RawDevice for LegacyDrmDevice<A> {
|
||||||
|
type Surface = LegacyDrmSurface<A>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<A: AsRawFd + 'static> Drop for LegacyDrmDevice<A> {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
self.backends.borrow_mut().clear();
|
||||||
|
if Rc::strong_count(&self.dev) > 1 {
|
||||||
|
panic!("Pending DrmBackends. You need to free all backends before the DrmDevice gets destroyed");
|
||||||
|
}
|
||||||
|
if self.active.load(Ordering::SeqCst) {
|
||||||
|
for (handle, (info, connectors)) in self.old_state.drain() {
|
||||||
|
if let Err(err) = crtc::set(
|
||||||
|
&*self.dev,
|
||||||
|
handle,
|
||||||
|
info.fb(),
|
||||||
|
&connectors,
|
||||||
|
info.position(),
|
||||||
|
info.mode(),
|
||||||
|
) {
|
||||||
|
error!(self.logger, "Failed to reset crtc ({:?}). Error: {}", handle, err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if self.priviledged {
|
||||||
|
if let Err(err) = self.drop_master() {
|
||||||
|
error!(self.logger, "Failed to drop drm master state. Error: {}", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,97 @@
|
||||||
|
use drm::Device as BasicDevice;
|
||||||
|
use drm::control::{crtc, connector, Device as ControlDevice};
|
||||||
|
use nix::libc::dev_t;
|
||||||
|
use nix::sys::stat;
|
||||||
|
use std::cell::RefCell;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::rc::{Rc, Weak};
|
||||||
|
use std::sync::Arc;
|
||||||
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
|
use std::os::unix::io::{AsRawFd, RawFd};
|
||||||
|
|
||||||
|
use backend::session::{AsSessionObserver, SessionObserver};
|
||||||
|
use super::{LegacyDrmDevice, LegacyDrmSurface, Dev};
|
||||||
|
|
||||||
|
/// `SessionObserver` linked to the `DrmDevice` it was created from.
|
||||||
|
pub struct LegacyDrmDeviceObserver<A: AsRawFd + 'static> {
|
||||||
|
dev: Weak<Dev<A>>,
|
||||||
|
dev_id: dev_t,
|
||||||
|
priviledged: bool,
|
||||||
|
active: Arc<AtomicBool>,
|
||||||
|
old_state: HashMap<crtc::Handle, (crtc::Info, Vec<connector::Handle>)>,
|
||||||
|
backends: Weak<RefCell<HashMap<crtc::Handle, Weak<LegacyDrmSurface<A>>>>>,
|
||||||
|
logger: ::slog::Logger,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<A: ControlDevice + 'static> AsSessionObserver<LegacyDrmDeviceObserver<A>> for LegacyDrmDevice<A> {
|
||||||
|
fn observer(&mut self) -> LegacyDrmDeviceObserver<A> {
|
||||||
|
LegacyDrmDeviceObserver {
|
||||||
|
dev: Rc::downgrade(&self.dev),
|
||||||
|
dev_id: self.dev_id,
|
||||||
|
old_state: self.old_state.clone(),
|
||||||
|
active: self.active.clone(),
|
||||||
|
priviledged: self.priviledged,
|
||||||
|
backends: Rc::downgrade(&self.backends),
|
||||||
|
logger: self.logger.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<A: ControlDevice + 'static> SessionObserver for LegacyDrmDeviceObserver<A> {
|
||||||
|
fn pause(&mut self, devnum: Option<(u32, u32)>) {
|
||||||
|
if let Some((major, minor)) = devnum {
|
||||||
|
if major as u64 != stat::major(self.dev_id) || minor as u64 != stat::minor(self.dev_id) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(device) = self.dev.upgrade() {
|
||||||
|
if let Some(backends) = self.backends.upgrade() {
|
||||||
|
for surface in backends.borrow().values().filter_map(Weak::upgrade) {
|
||||||
|
let _ = crtc::clear_cursor(&*device, surface.crtc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (handle, &(ref info, ref connectors)) in &self.old_state {
|
||||||
|
if let Err(err) = crtc::set(
|
||||||
|
&*device,
|
||||||
|
*handle,
|
||||||
|
info.fb(),
|
||||||
|
connectors,
|
||||||
|
info.position(),
|
||||||
|
info.mode(),
|
||||||
|
) {
|
||||||
|
error!(self.logger, "Failed to reset crtc ({:?}). Error: {}", handle, err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.active.store(false, Ordering::SeqCst);
|
||||||
|
if self.priviledged {
|
||||||
|
if let Some(device) = self.dev.upgrade() {
|
||||||
|
if let Err(err) = device.drop_master() {
|
||||||
|
error!(self.logger, "Failed to drop drm master state. Error: {}", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn activate(&mut self, devnum: Option<(u32, u32, Option<RawFd>)>) {
|
||||||
|
if let Some((major, minor, fd)) = devnum {
|
||||||
|
if major as u64 != stat::major(self.dev_id) || minor as u64 != stat::minor(self.dev_id) {
|
||||||
|
return;
|
||||||
|
} else if let Some(fd) = fd {
|
||||||
|
info!(self.logger, "Replacing fd");
|
||||||
|
if let Some(device) = self.dev.upgrade() {
|
||||||
|
::nix::unistd::dup2(device.as_raw_fd(), fd)
|
||||||
|
.expect("Failed to replace file descriptor of drm device");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.active.store(true, Ordering::SeqCst);
|
||||||
|
if self.priviledged {
|
||||||
|
if let Some(device) = self.dev.upgrade() {
|
||||||
|
if let Err(err) = device.set_master() {
|
||||||
|
crit!(self.logger, "Failed to acquire drm master again. Error: {}", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,264 @@
|
||||||
|
use drm::Device as BasicDevice;
|
||||||
|
use drm::control::{connector, crtc, encoder, framebuffer, Device as ControlDevice, Mode, ResourceInfo};
|
||||||
|
pub use drm::buffer::Buffer;
|
||||||
|
|
||||||
|
use std::collections::HashSet;
|
||||||
|
use std::rc::Rc;
|
||||||
|
use std::os::unix::io::{AsRawFd, RawFd};
|
||||||
|
use std::sync::RwLock;
|
||||||
|
|
||||||
|
use backend::drm::{RawSurface, Surface, DevPath};
|
||||||
|
use backend::graphics::CursorBackend;
|
||||||
|
use backend::graphics::SwapBuffersError;
|
||||||
|
|
||||||
|
use super::{Dev, error::*};
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||||
|
pub struct State {
|
||||||
|
pub mode: Mode,
|
||||||
|
pub connectors: HashSet<connector::Handle>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct LegacyDrmSurface<A: AsRawFd + 'static> {
|
||||||
|
pub(in super) dev: Rc<Dev<A>>,
|
||||||
|
pub(in super) crtc: crtc::Handle,
|
||||||
|
pub(in super) state: RwLock<State>,
|
||||||
|
pub(in super) pending: RwLock<State>,
|
||||||
|
pub(in super) logger: ::slog::Logger,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<A: AsRawFd + 'static> AsRawFd for LegacyDrmSurface<A> {
|
||||||
|
fn as_raw_fd(&self) -> RawFd {
|
||||||
|
self.dev.as_raw_fd()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<A: AsRawFd + 'static> BasicDevice for LegacyDrmSurface<A> {}
|
||||||
|
impl<A: AsRawFd + 'static> ControlDevice for LegacyDrmSurface<A> {}
|
||||||
|
|
||||||
|
impl<'a, A: AsRawFd + 'static> CursorBackend<'a> for LegacyDrmSurface<A> {
|
||||||
|
type CursorFormat = &'a Buffer;
|
||||||
|
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, self.crtc, (x as i32, y as i32)).chain_err(|| {
|
||||||
|
ErrorKind::DrmDev(format!(
|
||||||
|
"Error moving cursor on {:?}",
|
||||||
|
self.dev_path()
|
||||||
|
))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_cursor_representation<'b>(
|
||||||
|
&'b self,
|
||||||
|
buffer: Self::CursorFormat,
|
||||||
|
hotspot: (u32, u32),
|
||||||
|
) -> Result<()>
|
||||||
|
where 'a: 'b
|
||||||
|
{
|
||||||
|
trace!(self.logger, "Setting the new imported cursor");
|
||||||
|
|
||||||
|
if crtc::set_cursor2(
|
||||||
|
self,
|
||||||
|
self.crtc,
|
||||||
|
buffer,
|
||||||
|
(hotspot.0 as i32, hotspot.1 as i32),
|
||||||
|
).is_err()
|
||||||
|
{
|
||||||
|
crtc::set_cursor(self, self.crtc, buffer).chain_err(|| {
|
||||||
|
ErrorKind::DrmDev(format!(
|
||||||
|
"Failed to set cursor on {:?}",
|
||||||
|
self.dev_path()
|
||||||
|
))
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<A: AsRawFd + 'static> Surface for LegacyDrmSurface<A> {
|
||||||
|
type Error = Error;
|
||||||
|
type Connectors = HashSet<connector::Handle>;
|
||||||
|
|
||||||
|
fn crtc(&self) -> crtc::Handle {
|
||||||
|
self.crtc
|
||||||
|
}
|
||||||
|
|
||||||
|
fn current_connectors(&self) -> Self::Connectors {
|
||||||
|
self.state.read().unwrap().connectors.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pending_connectors(&self) -> Self::Connectors {
|
||||||
|
self.pending.read().unwrap().connectors.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn current_mode(&self) -> Mode {
|
||||||
|
self.state.read().unwrap().mode.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pending_mode(&self) -> Mode {
|
||||||
|
self.pending.read().unwrap().mode.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_connector(&self, connector: connector::Handle) -> Result<()> {
|
||||||
|
let info = connector::Info::load_from_device(self, connector).chain_err(|| {
|
||||||
|
ErrorKind::DrmDev(format!(
|
||||||
|
"Error loading connector info on {:?}",
|
||||||
|
self.dev_path()
|
||||||
|
))
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let mut pending = self.pending.write().unwrap();
|
||||||
|
|
||||||
|
// check if the connector can handle the current mode
|
||||||
|
if info.modes().contains(&pending.mode) {
|
||||||
|
// check if there is a valid encoder
|
||||||
|
let encoders = info
|
||||||
|
.encoders()
|
||||||
|
.iter()
|
||||||
|
.map(|encoder| {
|
||||||
|
encoder::Info::load_from_device(self, *encoder).chain_err(|| {
|
||||||
|
ErrorKind::DrmDev(format!(
|
||||||
|
"Error loading encoder info on {:?}",
|
||||||
|
self.dev_path()
|
||||||
|
))
|
||||||
|
})
|
||||||
|
}).collect::<Result<Vec<encoder::Info>>>()?;
|
||||||
|
|
||||||
|
// and if any encoder supports the selected crtc
|
||||||
|
let resource_handles = self.resource_handles().chain_err(|| {
|
||||||
|
ErrorKind::DrmDev(format!(
|
||||||
|
"Error loading resources on {:?}",
|
||||||
|
self.dev_path()
|
||||||
|
))
|
||||||
|
})?;
|
||||||
|
if !encoders
|
||||||
|
.iter()
|
||||||
|
.map(|encoder| encoder.possible_crtcs())
|
||||||
|
.all(|crtc_list| {
|
||||||
|
resource_handles
|
||||||
|
.filter_crtcs(crtc_list)
|
||||||
|
.contains(&self.crtc)
|
||||||
|
}) {
|
||||||
|
bail!(ErrorKind::NoSuitableEncoder(info, self.crtc));
|
||||||
|
}
|
||||||
|
|
||||||
|
pending.connectors.insert(connector);
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
bail!(ErrorKind::ModeNotSuitable(pending.mode));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove_connector(&self, connector: connector::Handle) -> Result<()> {
|
||||||
|
self.pending.write().unwrap().connectors.remove(&connector);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn use_mode(&self, mode: Mode) -> Result<()> {
|
||||||
|
let mut pending = self.pending.write().unwrap();
|
||||||
|
|
||||||
|
// check the connectors
|
||||||
|
for connector in &pending.connectors {
|
||||||
|
if !connector::Info::load_from_device(self, *connector)
|
||||||
|
.chain_err(|| {
|
||||||
|
ErrorKind::DrmDev(format!(
|
||||||
|
"Error loading connector info on {:?}",
|
||||||
|
self.dev_path()
|
||||||
|
))
|
||||||
|
})?.modes()
|
||||||
|
.contains(&mode)
|
||||||
|
{
|
||||||
|
bail!(ErrorKind::ModeNotSuitable(mode));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pending.mode = mode;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<A: AsRawFd + 'static> RawSurface for LegacyDrmSurface<A> {
|
||||||
|
fn commit_pending(&self) -> bool {
|
||||||
|
*self.pending.read().unwrap() != *self.state.read().unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn commit(&self, framebuffer: framebuffer::Handle) -> Result<()> {
|
||||||
|
let mut current = self.state.write().unwrap();
|
||||||
|
let pending = self.pending.read().unwrap();
|
||||||
|
|
||||||
|
{
|
||||||
|
let removed = current.connectors.difference(&pending.connectors);
|
||||||
|
let added = pending.connectors.difference(¤t.connectors);
|
||||||
|
|
||||||
|
for conn in removed {
|
||||||
|
if let Ok(info) = connector::Info::load_from_device(self, *conn) {
|
||||||
|
info!(
|
||||||
|
self.logger,
|
||||||
|
"Removing connector: {:?}",
|
||||||
|
info.connector_type()
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
info!(self.logger, "Removing unknown connector");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for conn in added {
|
||||||
|
if let Ok(info) = connector::Info::load_from_device(self, *conn) {
|
||||||
|
info!(
|
||||||
|
self.logger,
|
||||||
|
"Adding connector: {:?}",
|
||||||
|
info.connector_type()
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
info!(self.logger, "Adding unknown connector");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if current.mode != pending.mode {
|
||||||
|
info!(self.logger, "Setting new mode: {:?}", pending.mode.name());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
debug!(self.logger, "Setting screen");
|
||||||
|
crtc::set(
|
||||||
|
self,
|
||||||
|
self.crtc,
|
||||||
|
framebuffer,
|
||||||
|
&pending.connectors.iter().map(|x| *x).collect::<Vec<connector::Handle>>(),
|
||||||
|
(0, 0),
|
||||||
|
Some(pending.mode),
|
||||||
|
).chain_err(|| {
|
||||||
|
ErrorKind::DrmDev(format!(
|
||||||
|
"Error setting crtc {:?} on {:?}",
|
||||||
|
self.crtc,
|
||||||
|
self.dev_path()
|
||||||
|
))
|
||||||
|
})?;
|
||||||
|
|
||||||
|
*current = pending.clone();
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn page_flip(&self, framebuffer: framebuffer::Handle) -> ::std::result::Result<(), SwapBuffersError> {
|
||||||
|
trace!(self.logger, "Queueing Page flip");
|
||||||
|
|
||||||
|
crtc::page_flip(
|
||||||
|
self,
|
||||||
|
self.crtc,
|
||||||
|
framebuffer,
|
||||||
|
&[crtc::PageFlipFlags::PageFlipEvent],
|
||||||
|
).map_err(|_| SwapBuffersError::ContextLost)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<A: AsRawFd + 'static> Drop for LegacyDrmSurface<A> {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
// ignore failure at this point
|
||||||
|
let _ = crtc::clear_cursor(self, self.crtc);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,452 +1,75 @@
|
||||||
//! Drm/Kms types and backend implementations
|
use drm::Device as BasicDevice;
|
||||||
//!
|
use drm::control::Device as ControlDevice;
|
||||||
//! This module provide a `DrmDevice` which acts as a representation for any DRM
|
pub use drm::control::crtc;
|
||||||
//! device and can be used to create the second provided structure a `DrmBackend`.
|
pub use drm::control::connector;
|
||||||
//!
|
pub use drm::control::framebuffer;
|
||||||
//! Initialization happens through the types provided by [`drm-rs`](https://docs.rs/drm/).
|
pub use drm::control::Mode;
|
||||||
//!
|
|
||||||
//! Three entities are relevant for the initialization procedure.
|
|
||||||
//!
|
|
||||||
//! "Crtc"s represent scanout engines of the device pointer to one framebuffer. There responsibility
|
|
||||||
//! is to read the data of the framebuffer and export it into an "Encoder". The number of crtc's
|
|
||||||
//! represent the number of independent output devices the hardware may handle.
|
|
||||||
//!
|
|
||||||
//! An "Encoder" encodes the data of connected crtcs into a video signal for a fixed set
|
|
||||||
//! of connectors. E.g. you might have an analog encoder based on a DAG for VGA ports, but another
|
|
||||||
//! one for digital ones. Also not every encoder might be connected to every crtc.
|
|
||||||
//!
|
|
||||||
//! The last entity the "Connector" represents a port on your computer, possibly with a connected
|
|
||||||
//! monitor, TV, capture card, etc.
|
|
||||||
//!
|
|
||||||
//! The `DrmBackend` created from a `DrmDevice` represents a crtc of the device you can render to
|
|
||||||
//! and that feeds a given set of connectors, that can be manipulated at runtime.
|
|
||||||
//!
|
|
||||||
//! From these circumstances it becomes clear, that one crtc might only send it's data to a connector,
|
|
||||||
//! that is attached to any encoder that is attached to the crtc itself. It is the responsibility of the
|
|
||||||
//! user to ensure that a given set of a crtc with it's connectors is valid or an error will be thrown.
|
|
||||||
//!
|
|
||||||
//! For more details refer to the [`drm-rs` documentation](https://docs.rs/drm).
|
|
||||||
//!
|
|
||||||
//!
|
|
||||||
//! ## How to use it
|
|
||||||
//!
|
|
||||||
//! ### Initialization
|
|
||||||
//!
|
|
||||||
//! To initialize the `DrmDevice` you need either a `RawFd` or a `File` of
|
|
||||||
//! your DRM node. The `File` is recommended as it represents the save API.
|
|
||||||
//!
|
|
||||||
//! Once you got your `DrmDevice` you can then use it to create `DrmBackend`s.
|
|
||||||
//! You will need to use the `drm` crate to provide the required types to create
|
|
||||||
//! a backend.
|
|
||||||
//!
|
|
||||||
//! ```rust,no_run
|
|
||||||
//! extern crate drm;
|
|
||||||
//! extern crate smithay;
|
|
||||||
//! extern crate wayland_server;
|
|
||||||
//!
|
|
||||||
//! use drm::Device as BasicDevice;
|
|
||||||
//! use drm::control::{Device as ControlDevice, ResourceInfo};
|
|
||||||
//! use drm::control::connector::{Info as ConnectorInfo, State as ConnectorState};
|
|
||||||
//! use drm::control::encoder::{Info as EncoderInfo};
|
|
||||||
//! use std::fs::{File, OpenOptions};
|
|
||||||
//! use std::os::unix::io::RawFd;
|
|
||||||
//! use std::os::unix::io::AsRawFd;
|
|
||||||
//! use smithay::backend::drm::{DrmDevice, DrmBackend};
|
|
||||||
//!
|
|
||||||
//! #[derive(Debug)]
|
|
||||||
//! pub struct Card(File);
|
|
||||||
//!
|
|
||||||
//! impl AsRawFd for Card {
|
|
||||||
//! fn as_raw_fd(&self) -> RawFd {
|
|
||||||
//! self.0.as_raw_fd()
|
|
||||||
//! }
|
|
||||||
//! }
|
|
||||||
//!
|
|
||||||
//! impl BasicDevice for Card {}
|
|
||||||
//! impl ControlDevice for Card {}
|
|
||||||
//!
|
|
||||||
//! # fn main() {
|
|
||||||
//! // Open the drm device
|
|
||||||
//! let mut options = OpenOptions::new();
|
|
||||||
//! options.read(true);
|
|
||||||
//! options.write(true);
|
|
||||||
//! let mut device = DrmDevice::new(
|
|
||||||
//! Card(options.open("/dev/dri/card0").unwrap()), // try to detect it properly
|
|
||||||
//! None /*put a logger here*/
|
|
||||||
//! ).unwrap();
|
|
||||||
//!
|
|
||||||
//! // Get a set of all modesetting resource handles
|
|
||||||
//! let res_handles = device.resource_handles().unwrap();
|
|
||||||
//!
|
|
||||||
//! // Use first connected connector for this example
|
|
||||||
//! let connector_info = res_handles.connectors().iter()
|
|
||||||
//! .map(|conn| ConnectorInfo::load_from_device(&device, *conn).unwrap())
|
|
||||||
//! .find(|conn| conn.connection_state() == ConnectorState::Connected)
|
|
||||||
//! .unwrap();
|
|
||||||
//!
|
|
||||||
//! // Use the first encoder
|
|
||||||
//! let encoder_info = EncoderInfo::load_from_device(&device, connector_info.encoders()[0]).unwrap();
|
|
||||||
//!
|
|
||||||
//! // use the connected crtc if any
|
|
||||||
//! let crtc = encoder_info.current_crtc()
|
|
||||||
//! // or use the first one that is compatible with the encoder
|
|
||||||
//! .unwrap_or_else(||
|
|
||||||
//! *res_handles.filter_crtcs(encoder_info.possible_crtcs())
|
|
||||||
//! .iter()
|
|
||||||
//! .next()
|
|
||||||
//! .unwrap());
|
|
||||||
//!
|
|
||||||
//! // Use first mode (usually the highest resolution)
|
|
||||||
//! let mode = connector_info.modes()[0];
|
|
||||||
//!
|
|
||||||
//! // Create the backend
|
|
||||||
//! let backend = device.create_backend(
|
|
||||||
//! crtc,
|
|
||||||
//! mode,
|
|
||||||
//! vec![connector_info.handle()]
|
|
||||||
//! ).unwrap();
|
|
||||||
//! # }
|
|
||||||
//! ```
|
|
||||||
//!
|
|
||||||
//! ### Page Flips / Tear-free video
|
|
||||||
//! Calling the usual `EglGraphicsBackend::swap_buffers` function on a
|
|
||||||
//! `DrmBackend` works the same to finish the rendering, but will return
|
|
||||||
//! `SwapBuffersError::AlreadySwapped` for any new calls until the page flip of the
|
|
||||||
//! crtc has happened.
|
|
||||||
//!
|
|
||||||
//! You can monitor the page flips by registering the `DrmDevice` as and
|
|
||||||
//! `FdEventSourceHandler` and setting a `DrmHandler` on it. You will be notified
|
|
||||||
//! whenever a page flip has happened, so you can render the next frame immediately
|
|
||||||
//! and get a tear-free representation on the display.
|
|
||||||
//!
|
|
||||||
//! You need to render at least once to successfully trigger the first event.
|
|
||||||
//!
|
|
||||||
//! ```rust,no_run
|
|
||||||
//! # extern crate drm;
|
|
||||||
//! # extern crate smithay;
|
|
||||||
//! # extern crate wayland_server;
|
|
||||||
//! #
|
|
||||||
//! # use drm::Device as BasicDevice;
|
|
||||||
//! # use drm::control::{Device as ControlDevice, ResourceInfo};
|
|
||||||
//! # use drm::control::connector::{Info as ConnectorInfo, State as ConnectorState};
|
|
||||||
//! use drm::control::crtc::{Handle as CrtcHandle};
|
|
||||||
//! use drm::result::Error as DrmError;
|
|
||||||
//! # use std::fs::{File, OpenOptions};
|
|
||||||
//! # use std::os::unix::io::RawFd;
|
|
||||||
//! # use std::os::unix::io::AsRawFd;
|
|
||||||
//! # use std::time::Duration;
|
|
||||||
//! use smithay::backend::drm::{DrmDevice, DrmBackend, DrmHandler, drm_device_bind};
|
|
||||||
//! use smithay::backend::graphics::egl::EGLGraphicsBackend;
|
|
||||||
//! #
|
|
||||||
//! # #[derive(Debug)]
|
|
||||||
//! # pub struct Card(File);
|
|
||||||
//! # impl AsRawFd for Card {
|
|
||||||
//! # fn as_raw_fd(&self) -> RawFd {
|
|
||||||
//! # self.0.as_raw_fd()
|
|
||||||
//! # }
|
|
||||||
//! # }
|
|
||||||
//! # impl BasicDevice for Card {}
|
|
||||||
//! # impl ControlDevice for Card {}
|
|
||||||
//! #
|
|
||||||
//! # fn main() {
|
|
||||||
//! #
|
|
||||||
//! # let mut event_loop = wayland_server::calloop::EventLoop::<()>::new().unwrap();
|
|
||||||
//! # let mut display = wayland_server::Display::new(event_loop.handle());
|
|
||||||
//! #
|
|
||||||
//! # let mut options = OpenOptions::new();
|
|
||||||
//! # options.read(true);
|
|
||||||
//! # options.write(true);
|
|
||||||
//! # let mut device = DrmDevice::new(
|
|
||||||
//! # Card(options.open("/dev/dri/card0").unwrap()), // try to detect it properly
|
|
||||||
//! # None /*put a logger here*/
|
|
||||||
//! # ).unwrap();
|
|
||||||
//! #
|
|
||||||
//! # let res_handles = device.resource_handles().unwrap();
|
|
||||||
//! # let connector_info = res_handles.connectors().iter()
|
|
||||||
//! # .map(|conn| ConnectorInfo::load_from_device(&device, *conn).unwrap())
|
|
||||||
//! # .find(|conn| conn.connection_state() == ConnectorState::Connected)
|
|
||||||
//! # .unwrap();
|
|
||||||
//! # let crtc = res_handles.crtcs()[0];
|
|
||||||
//! # let mode = connector_info.modes()[0];
|
|
||||||
//! # let backend = device.create_backend(
|
|
||||||
//! # crtc,
|
|
||||||
//! # mode,
|
|
||||||
//! # vec![connector_info.handle()]
|
|
||||||
//! # ).unwrap();
|
|
||||||
//!
|
|
||||||
//! struct MyDrmHandler(DrmBackend<Card>);
|
|
||||||
//!
|
|
||||||
//! impl DrmHandler<Card> for MyDrmHandler {
|
|
||||||
//! fn ready(
|
|
||||||
//! &mut self,
|
|
||||||
//! _device: &mut DrmDevice<Card>,
|
|
||||||
//! _crtc: CrtcHandle,
|
|
||||||
//! _frame: u32,
|
|
||||||
//! _duration: Duration)
|
|
||||||
//! {
|
|
||||||
//! // render surfaces and swap again
|
|
||||||
//! self.0.swap_buffers().unwrap();
|
|
||||||
//! }
|
|
||||||
//! fn error(
|
|
||||||
//! &mut self,
|
|
||||||
//! device: &mut DrmDevice<Card>,
|
|
||||||
//! error: DrmError)
|
|
||||||
//! {
|
|
||||||
//! panic!("DrmDevice errored: {}", error);
|
|
||||||
//! }
|
|
||||||
//! }
|
|
||||||
//!
|
|
||||||
//! // render something (like clear_color)
|
|
||||||
//! backend.swap_buffers().unwrap();
|
|
||||||
//!
|
|
||||||
//! let (_source, _device_rc) = drm_device_bind(
|
|
||||||
//! &event_loop.handle(),
|
|
||||||
//! device,
|
|
||||||
//! MyDrmHandler(backend)
|
|
||||||
//! ).map_err(|(err, _)| err).unwrap();
|
|
||||||
//!
|
|
||||||
//! /* And then run the event loop once all your setup is done */
|
|
||||||
//! # }
|
|
||||||
//! ```
|
|
||||||
|
|
||||||
use backend::graphics::egl::{
|
use std::borrow::Borrow;
|
||||||
context::{EGLContext, GlAttributes},
|
use std::error::Error;
|
||||||
error::Result as EGLResult,
|
use std::path::PathBuf;
|
||||||
native::Gbm,
|
use std::os::unix::io::AsRawFd;
|
||||||
wayland::{EGLDisplay, EGLWaylandExtensions},
|
|
||||||
};
|
|
||||||
#[cfg(feature = "backend_session")]
|
|
||||||
use backend::session::{AsSessionObserver, SessionObserver};
|
|
||||||
use drm::{
|
|
||||||
control::{connector, crtc, encoder, framebuffer, Device as ControlDevice, Mode, ResourceInfo},
|
|
||||||
result::Error as DrmError,
|
|
||||||
Device as BasicDevice,
|
|
||||||
};
|
|
||||||
use gbm::{BufferObject, Device as GbmDevice};
|
|
||||||
use nix::{
|
|
||||||
self,
|
|
||||||
sys::stat::{self, dev_t, fstat},
|
|
||||||
};
|
|
||||||
use std::{
|
|
||||||
cell::RefCell,
|
|
||||||
collections::HashMap,
|
|
||||||
hash::{Hash, Hasher},
|
|
||||||
io::Error as IoError,
|
|
||||||
os::unix::io::{AsRawFd, RawFd},
|
|
||||||
path::PathBuf,
|
|
||||||
rc::{Rc, Weak},
|
|
||||||
sync::{
|
|
||||||
atomic::{AtomicBool, Ordering},
|
|
||||||
Arc, Once, ONCE_INIT,
|
|
||||||
},
|
|
||||||
time::Duration,
|
|
||||||
};
|
|
||||||
|
|
||||||
use wayland_server::{
|
use wayland_server::calloop::generic::{EventedFd, Generic};
|
||||||
calloop::{
|
use wayland_server::calloop::{LoopHandle, Source};
|
||||||
generic::{EventedRawFd, Generic},
|
use wayland_server::calloop::mio::Ready;
|
||||||
mio::Ready,
|
pub use wayland_server::calloop::InsertError;
|
||||||
LoopHandle, Source,
|
|
||||||
},
|
|
||||||
Display,
|
|
||||||
};
|
|
||||||
|
|
||||||
mod backend;
|
use super::graphics::SwapBuffersError;
|
||||||
pub mod error;
|
|
||||||
|
|
||||||
pub use self::backend::DrmBackend;
|
#[cfg(feature = "backend_drm_legacy")]
|
||||||
use self::{backend::DrmBackendInternal, error::*};
|
pub mod legacy;
|
||||||
|
#[cfg(feature = "backend_drm_gbm")]
|
||||||
|
pub mod gbm;
|
||||||
|
#[cfg(feature = "backend_drm_egl")]
|
||||||
|
pub mod egl;
|
||||||
|
|
||||||
static LOAD: Once = ONCE_INIT;
|
pub trait DeviceHandler {
|
||||||
|
type Device: Device + ?Sized;
|
||||||
/// Representation of an open DRM device node to create rendering backends
|
fn vblank(&mut self, surface: &<<Self as DeviceHandler>::Device as Device>::Surface);
|
||||||
pub struct DrmDevice<A: ControlDevice + 'static> {
|
fn error(&mut self, error: <<<Self as DeviceHandler>::Device as Device>::Surface as Surface>::Error);
|
||||||
context: Rc<EGLContext<Gbm<framebuffer::Info>, GbmDevice<A>>>,
|
|
||||||
old_state: HashMap<crtc::Handle, (crtc::Info, Vec<connector::Handle>)>,
|
|
||||||
device_id: dev_t,
|
|
||||||
backends: Rc<RefCell<HashMap<crtc::Handle, Weak<DrmBackendInternal<A>>>>>,
|
|
||||||
active: Arc<AtomicBool>,
|
|
||||||
privileged: bool,
|
|
||||||
logger: ::slog::Logger,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<A: ControlDevice + 'static> DrmDevice<A> {
|
pub trait Device: AsRawFd + DevPath {
|
||||||
/// Create a new `DrmDevice` from an open DRM node
|
type Surface: Surface;
|
||||||
///
|
type Return: Borrow<Self::Surface>;
|
||||||
/// Returns an error if the file is no valid DRM node or context creation was not
|
|
||||||
/// successful.
|
|
||||||
pub fn new<L>(dev: A, logger: L) -> Result<Self>
|
|
||||||
where
|
|
||||||
L: Into<Option<::slog::Logger>>,
|
|
||||||
{
|
|
||||||
DrmDevice::new_with_gl_attr(
|
|
||||||
dev,
|
|
||||||
GlAttributes {
|
|
||||||
version: None,
|
|
||||||
profile: None,
|
|
||||||
debug: cfg!(debug_assertions),
|
|
||||||
vsync: true,
|
|
||||||
},
|
|
||||||
logger,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create a new `DrmDevice` from an open DRM node and given `GlAttributes`
|
fn set_handler(&mut self, handler: impl DeviceHandler<Device=Self> + 'static);
|
||||||
///
|
fn clear_handler(&mut self);
|
||||||
/// Returns an error if the file is no valid DRM node or context creation was not
|
fn create_surface(
|
||||||
/// successful.
|
|
||||||
pub fn new_with_gl_attr<L>(dev: A, attributes: GlAttributes, logger: L) -> Result<Self>
|
|
||||||
where
|
|
||||||
L: Into<Option<::slog::Logger>>,
|
|
||||||
{
|
|
||||||
let log = ::slog_or_stdlog(logger).new(o!("smithay_module" => "backend_drm"));
|
|
||||||
|
|
||||||
/* GBM will load a dri driver, but even though they need symbols from
|
|
||||||
* libglapi, in some version of Mesa they are not linked to it. Since
|
|
||||||
* only the gl-renderer module links to it, these symbols won't be globally available,
|
|
||||||
* and loading the DRI driver fails.
|
|
||||||
* Workaround this by dlopen()'ing libglapi with RTLD_GLOBAL.
|
|
||||||
*/
|
|
||||||
LOAD.call_once(|| unsafe {
|
|
||||||
nix::libc::dlopen(
|
|
||||||
"libglapi.so.0".as_ptr() as *const _,
|
|
||||||
nix::libc::RTLD_LAZY | nix::libc::RTLD_GLOBAL,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
let device_id = fstat(dev.as_raw_fd())
|
|
||||||
.chain_err(|| ErrorKind::UnableToGetDeviceId)?
|
|
||||||
.st_rdev;
|
|
||||||
|
|
||||||
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,
|
|
||||||
Default::default(),
|
|
||||||
log.clone(),
|
|
||||||
).map_err(Error::from)?,
|
|
||||||
),
|
|
||||||
backends: Rc::new(RefCell::new(HashMap::new())),
|
|
||||||
device_id,
|
|
||||||
old_state: HashMap::new(),
|
|
||||||
active: Arc::new(AtomicBool::new(true)),
|
|
||||||
privileged: true,
|
|
||||||
logger: log.clone(),
|
|
||||||
};
|
|
||||||
|
|
||||||
info!(log, "DrmDevice initializing");
|
|
||||||
|
|
||||||
// we want to mode-set, so we better be the master, if we run via a tty session
|
|
||||||
if drm.set_master().is_err() {
|
|
||||||
warn!(log, "Unable to become drm master, assuming unprivileged mode");
|
|
||||||
drm.privileged = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
let res_handles = drm.resource_handles().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!("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!("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!("Error loading crtc info on {:?}", drm.dev_path()))
|
|
||||||
})?;
|
|
||||||
drm.old_state
|
|
||||||
.entry(crtc)
|
|
||||||
.or_insert((info, Vec::new()))
|
|
||||||
.1
|
|
||||||
.push(con);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(drm)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create a new backend on a given crtc with a given `Mode` for a given amount
|
|
||||||
/// of `connectors` (mirroring).
|
|
||||||
///
|
|
||||||
/// Errors if initialization fails or the mode is not available on all given
|
|
||||||
/// connectors.
|
|
||||||
pub fn create_backend<I>(
|
|
||||||
&mut self,
|
&mut self,
|
||||||
crtc: crtc::Handle,
|
ctrc: crtc::Handle,
|
||||||
mode: Mode,
|
mode: Mode,
|
||||||
connectors: I,
|
connectors: impl Into<<Self::Surface as Surface>::Connectors>
|
||||||
) -> Result<DrmBackend<A>>
|
) -> Result<Self::Return, <Self::Surface as Surface>::Error>;
|
||||||
where
|
fn process_events(&mut self);
|
||||||
I: Into<Vec<connector::Handle>>,
|
}
|
||||||
{
|
|
||||||
if self.backends.borrow().contains_key(&crtc) {
|
|
||||||
bail!(ErrorKind::CrtcAlreadyInUse(crtc));
|
|
||||||
}
|
|
||||||
|
|
||||||
if !self.active.load(Ordering::SeqCst) {
|
pub trait RawDevice: Device<Surface=<Self as RawDevice>::Surface>
|
||||||
bail!(ErrorKind::DeviceInactive);
|
where
|
||||||
}
|
<Self as Device>::Return: Borrow<<Self as RawDevice>::Surface>
|
||||||
|
{
|
||||||
|
type Surface: RawSurface;
|
||||||
|
}
|
||||||
|
|
||||||
// check if the given connectors and crtc match
|
pub trait Surface {
|
||||||
let connectors = connectors.into();
|
type Connectors: IntoIterator<Item=connector::Handle>;
|
||||||
|
type Error: Error + Send;
|
||||||
|
|
||||||
// check if we have an encoder for every connector and the mode mode
|
fn crtc(&self) -> crtc::Handle;
|
||||||
for connector in &connectors {
|
fn current_connectors(&self) -> Self::Connectors;
|
||||||
let con_info = connector::Info::load_from_device(self, *connector).chain_err(|| {
|
fn pending_connectors(&self) -> Self::Connectors;
|
||||||
ErrorKind::DrmDev(format!("Error loading connector info on {:?}", self.dev_path()))
|
fn add_connector(&self, connector: connector::Handle) -> Result<(), Self::Error>;
|
||||||
})?;
|
fn remove_connector(&self, connector: connector::Handle) -> Result<(), Self::Error>;
|
||||||
|
fn current_mode(&self) -> Mode;
|
||||||
|
fn pending_mode(&self) -> Mode;
|
||||||
|
fn use_mode(&self, mode: Mode) -> Result<(), Self::Error>;
|
||||||
|
}
|
||||||
|
|
||||||
// check the mode
|
pub trait RawSurface: Surface + ControlDevice + BasicDevice {
|
||||||
if !con_info.modes().contains(&mode) {
|
fn commit_pending(&self) -> bool;
|
||||||
bail!(ErrorKind::ModeNotSuitable(mode));
|
fn commit(&self, framebuffer: framebuffer::Handle) -> Result<(), <Self as Surface>::Error>;
|
||||||
}
|
fn page_flip(&self, framebuffer: framebuffer::Handle) -> Result<(), SwapBuffersError>;
|
||||||
|
|
||||||
// check for every connector which encoders it does support
|
|
||||||
let encoders = con_info
|
|
||||||
.encoders()
|
|
||||||
.iter()
|
|
||||||
.map(|encoder| {
|
|
||||||
encoder::Info::load_from_device(self, *encoder).chain_err(|| {
|
|
||||||
ErrorKind::DrmDev(format!("Error loading encoder info on {:?}", self.dev_path()))
|
|
||||||
})
|
|
||||||
}).collect::<Result<Vec<encoder::Info>>>()?;
|
|
||||||
|
|
||||||
// and if any encoder supports the selected crtc
|
|
||||||
let resource_handles = self.resource_handles().chain_err(|| {
|
|
||||||
ErrorKind::DrmDev(format!("Error loading drm resources on {:?}", self.dev_path()))
|
|
||||||
})?;
|
|
||||||
if !encoders
|
|
||||||
.iter()
|
|
||||||
.map(|encoder| encoder.possible_crtcs())
|
|
||||||
.any(|crtc_list| resource_handles.filter_crtcs(crtc_list).contains(&crtc))
|
|
||||||
{
|
|
||||||
bail!(ErrorKind::NoSuitableEncoder(con_info, crtc))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// configuration is valid, the kernel will figure out the rest
|
|
||||||
|
|
||||||
let logger = self.logger.new(o!("crtc" => format!("{:?}", crtc)));
|
|
||||||
let backend = DrmBackend::new(self.context.clone(), crtc, mode, connectors, logger)?;
|
|
||||||
self.backends.borrow_mut().insert(crtc, backend.weak());
|
|
||||||
Ok(backend)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns an internal device id, that is unique per boot per system
|
|
||||||
pub fn device_id(&self) -> u64 {
|
|
||||||
self.device_id
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Trait for types representing open devices
|
/// Trait for types representing open devices
|
||||||
|
@ -459,247 +82,25 @@ impl<A: AsRawFd> DevPath for A {
|
||||||
fn dev_path(&self) -> Option<PathBuf> {
|
fn dev_path(&self) -> Option<PathBuf> {
|
||||||
use std::fs;
|
use std::fs;
|
||||||
|
|
||||||
fs::read_link(format!("/proc/self/fd/{:?}", self.as_raw_fd())).ok()
|
fs::read_link(format!("/proc/self/fd/{:?}", self.as_raw_fd())).ok()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<A: ControlDevice + 'static> PartialEq for DrmDevice<A> {
|
/// Bind a `Device` to an `EventLoop`,
|
||||||
fn eq(&self, other: &DrmDevice<A>) -> bool {
|
|
||||||
self.device_id == other.device_id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<A: ControlDevice + 'static> Eq for DrmDevice<A> {}
|
|
||||||
|
|
||||||
impl<A: ControlDevice + 'static> Hash for DrmDevice<A> {
|
|
||||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
|
||||||
self.device_id.hash(state);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// for users convenience and FdEventSource registering
|
|
||||||
impl<A: ControlDevice + 'static> AsRawFd for DrmDevice<A> {
|
|
||||||
fn as_raw_fd(&self) -> RawFd {
|
|
||||||
self.context.as_raw_fd()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<A: ControlDevice + 'static> BasicDevice for DrmDevice<A> {}
|
|
||||||
impl<A: ControlDevice + 'static> ControlDevice for DrmDevice<A> {}
|
|
||||||
|
|
||||||
impl<A: ControlDevice + 'static> EGLWaylandExtensions for DrmDevice<A> {
|
|
||||||
fn bind_wl_display(&self, display: &Display) -> EGLResult<EGLDisplay> {
|
|
||||||
self.context.bind_wl_display(display)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<A: ControlDevice + 'static> Drop for DrmDevice<A> {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
if Rc::strong_count(&self.context) > 1 {
|
|
||||||
panic!("Pending DrmBackends. You need to 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,
|
|
||||||
handle,
|
|
||||||
info.fb(),
|
|
||||||
&connectors,
|
|
||||||
info.position(),
|
|
||||||
info.mode(),
|
|
||||||
) {
|
|
||||||
error!(self.logger, "Failed to reset crtc ({:?}). Error: {}", handle, err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if self.privileged {
|
|
||||||
if let Err(err) = self.drop_master() {
|
|
||||||
error!(self.logger, "Failed to drop drm master state. Error: {}", err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Handler for DRM node events
|
|
||||||
///
|
///
|
||||||
/// See module-level documentation for its use
|
/// This will cause it to recieve events and feed them into an `DeviceHandler`
|
||||||
pub trait DrmHandler<A: ControlDevice + 'static> {
|
pub fn device_bind<D: Device + 'static, Data>(
|
||||||
/// The `DrmBackend` of crtc has finished swapping buffers and new frame can now
|
|
||||||
/// (and should be immediately) be rendered.
|
|
||||||
fn ready(&mut self, device: &mut DrmDevice<A>, crtc: crtc::Handle, frame: u32, duration: Duration);
|
|
||||||
/// The `DrmDevice` has thrown an error.
|
|
||||||
///
|
|
||||||
/// The related backends are most likely *not* usable anymore and
|
|
||||||
/// the whole stack has to be recreated..
|
|
||||||
fn error(&mut self, device: &mut DrmDevice<A>, 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<A, H, Data: 'static>(
|
|
||||||
handle: &LoopHandle<Data>,
|
handle: &LoopHandle<Data>,
|
||||||
device: DrmDevice<A>,
|
device: D,
|
||||||
mut handler: H,
|
) -> ::std::result::Result<Source<Generic<EventedFd<D>>>, InsertError<Generic<EventedFd<D>>>>
|
||||||
) -> ::std::result::Result<(Source<Generic<EventedRawFd>>, Rc<RefCell<DrmDevice<A>>>), (IoError, DrmDevice<A>)>
|
|
||||||
where
|
where
|
||||||
A: ControlDevice + 'static,
|
D: Device,
|
||||||
H: DrmHandler<A> + 'static,
|
Data: 'static,
|
||||||
{
|
{
|
||||||
let fd = device.as_raw_fd();
|
let mut source = Generic::from_fd_source(device);
|
||||||
let device = Rc::new(RefCell::new(device));
|
|
||||||
|
|
||||||
let mut source = Generic::from_raw_fd(fd);
|
|
||||||
source.set_interest(Ready::readable());
|
source.set_interest(Ready::readable());
|
||||||
|
|
||||||
match handle.insert_source(source, {
|
handle.insert_source(source, |evt, _| {
|
||||||
let device = device.clone();
|
evt.source.borrow_mut().0.process_events();
|
||||||
move |_evt, _| {
|
})
|
||||||
let mut device = device.borrow_mut();
|
|
||||||
process_events(&mut *device, &mut handler);
|
|
||||||
}
|
|
||||||
}) {
|
|
||||||
Ok(source) => Ok((source, device)),
|
|
||||||
Err(e) => {
|
|
||||||
let device = Rc::try_unwrap(device).unwrap_or_else(|_| unreachable!());
|
|
||||||
Err((e.into(), device.into_inner()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn process_events<A, H>(device: &mut DrmDevice<A>, handler: &mut H)
|
|
||||||
where
|
|
||||||
A: ControlDevice + 'static,
|
|
||||||
H: DrmHandler<A> + 'static,
|
|
||||||
{
|
|
||||||
match crtc::receive_events(&*device) {
|
|
||||||
Ok(events) => for event in events {
|
|
||||||
if let crtc::Event::PageFlip(event) = event {
|
|
||||||
if device.active.load(Ordering::SeqCst) {
|
|
||||||
let backends = device.backends.borrow().clone();
|
|
||||||
if let Some(backend) = backends.get(&event.crtc).iter().flat_map(|x| x.upgrade()).next() {
|
|
||||||
// we can now unlock the buffer
|
|
||||||
backend.unlock_buffer();
|
|
||||||
trace!(device.logger, "Handling event for backend {:?}", event.crtc);
|
|
||||||
// and then call the user to render the next frame
|
|
||||||
handler.ready(device, event.crtc, event.frame, event.duration);
|
|
||||||
} else {
|
|
||||||
device.backends.borrow_mut().remove(&event.crtc);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Err(err) => handler.error(device, err),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// `SessionObserver` linked to the `DrmDevice` it was created from.
|
|
||||||
pub struct DrmDeviceObserver<A: ControlDevice + 'static> {
|
|
||||||
context: Weak<EGLContext<Gbm<framebuffer::Info>, GbmDevice<A>>>,
|
|
||||||
device_id: dev_t,
|
|
||||||
backends: Rc<RefCell<HashMap<crtc::Handle, Weak<DrmBackendInternal<A>>>>>,
|
|
||||||
old_state: HashMap<crtc::Handle, (crtc::Info, Vec<connector::Handle>)>,
|
|
||||||
active: Arc<AtomicBool>,
|
|
||||||
privileged: bool,
|
|
||||||
logger: ::slog::Logger,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "backend_session")]
|
|
||||||
impl<A: ControlDevice + 'static> AsSessionObserver<DrmDeviceObserver<A>> for DrmDevice<A> {
|
|
||||||
fn observer(&mut self) -> DrmDeviceObserver<A> {
|
|
||||||
DrmDeviceObserver {
|
|
||||||
context: Rc::downgrade(&self.context),
|
|
||||||
device_id: self.device_id,
|
|
||||||
backends: self.backends.clone(),
|
|
||||||
old_state: self.old_state.clone(),
|
|
||||||
active: self.active.clone(),
|
|
||||||
privileged: self.privileged,
|
|
||||||
logger: self.logger.clone(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "backend_session")]
|
|
||||||
impl<A: ControlDevice + 'static> SessionObserver for DrmDeviceObserver<A> {
|
|
||||||
fn pause(&mut self, devnum: Option<(u32, u32)>) {
|
|
||||||
if let Some((major, minor)) = devnum {
|
|
||||||
if major as u64 != stat::major(self.device_id) || minor as u64 != stat::minor(self.device_id) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if let Some(device) = self.context.upgrade() {
|
|
||||||
for (handle, &(ref info, ref connectors)) in &self.old_state {
|
|
||||||
if let Err(err) = crtc::set(
|
|
||||||
&*device,
|
|
||||||
*handle,
|
|
||||||
info.fb(),
|
|
||||||
connectors,
|
|
||||||
info.position(),
|
|
||||||
info.mode(),
|
|
||||||
) {
|
|
||||||
error!(self.logger, "Failed to reset crtc ({:?}). Error: {}", handle, err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self.active.store(false, Ordering::SeqCst);
|
|
||||||
if self.privileged {
|
|
||||||
if let Some(device) = self.context.upgrade() {
|
|
||||||
if let Err(err) = device.drop_master() {
|
|
||||||
error!(self.logger, "Failed to drop drm master state. Error: {}", err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn activate(&mut self, devnum: Option<(u32, u32, Option<RawFd>)>) {
|
|
||||||
if let Some((major, minor, fd)) = devnum {
|
|
||||||
if major as u64 != stat::major(self.device_id) || minor as u64 != stat::minor(self.device_id) {
|
|
||||||
return;
|
|
||||||
} else if let Some(fd) = fd {
|
|
||||||
info!(self.logger, "Replacing fd");
|
|
||||||
if let Some(device) = self.context.upgrade() {
|
|
||||||
nix::unistd::dup2(device.as_raw_fd(), fd)
|
|
||||||
.expect("Failed to replace file descriptor of drm device");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self.active.store(true, Ordering::SeqCst);
|
|
||||||
if self.privileged {
|
|
||||||
if let Some(device) = self.context.upgrade() {
|
|
||||||
if let Err(err) = device.set_master() {
|
|
||||||
crit!(self.logger, "Failed to acquire drm master again. Error: {}", err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let mut crtcs = Vec::new();
|
|
||||||
for (crtc, backend) in self.backends.borrow().iter() {
|
|
||||||
if let Some(backend) = backend.upgrade() {
|
|
||||||
backend.unlock_buffer();
|
|
||||||
if let Err(err) = backend.page_flip(None) {
|
|
||||||
error!(
|
|
||||||
self.logger,
|
|
||||||
"Failed to activate crtc ({:?}) again. Error: {}", crtc, err
|
|
||||||
);
|
|
||||||
}
|
|
||||||
// reset cursor
|
|
||||||
{
|
|
||||||
let &(ref cursor, ref hotspot): &(BufferObject<()>, (u32, u32)) =
|
|
||||||
unsafe { &*backend.cursor.as_ptr() };
|
|
||||||
if crtc::set_cursor2(
|
|
||||||
&*backend.context,
|
|
||||||
*crtc,
|
|
||||||
cursor,
|
|
||||||
((*hotspot).0 as i32, (*hotspot).1 as i32),
|
|
||||||
).is_err()
|
|
||||||
{
|
|
||||||
if let Err(err) = crtc::set_cursor(&*backend.context, *crtc, cursor) {
|
|
||||||
error!(self.logger, "Failed to reset cursor. Error: {}", err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
crtcs.push(*crtc);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for crtc in crtcs {
|
|
||||||
self.backends.borrow_mut().remove(&crtc);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue