Merge pull request #206 from Smithay/fix/drm_connector

Fix drm mode and connector handling
This commit is contained in:
Victor Brekenfeld 2020-05-01 16:46:36 +02:00 committed by GitHub
commit 28166ce002
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 1643 additions and 570 deletions

View File

@ -7,7 +7,7 @@ use glium::{
self, self,
index::PrimitiveType, index::PrimitiveType,
texture::{MipmapsOption, Texture2d, UncompressedFloatFormat}, texture::{MipmapsOption, Texture2d, UncompressedFloatFormat},
Frame, GlObject, Surface, GlObject, Surface,
}; };
use slog::Logger; use slog::Logger;
@ -16,7 +16,10 @@ use smithay::backend::egl::display::EGLBufferReader;
use smithay::{ use smithay::{
backend::{ backend::{
egl::{BufferAccessError, EGLImages, Format}, egl::{BufferAccessError, EGLImages, Format},
graphics::{gl::GLGraphicsBackend, glium::GliumGraphicsBackend}, graphics::{
gl::GLGraphicsBackend,
glium::{Frame, GliumGraphicsBackend},
},
}, },
reexports::wayland_server::protocol::{wl_buffer, wl_surface}, reexports::wayland_server::protocol::{wl_buffer, wl_surface},
wayland::{ wayland::{
@ -159,7 +162,10 @@ impl<F: GLGraphicsBackend + 'static> GliumDrawer<F> {
let images = if let Some(display) = &self.egl_buffer_reader.borrow().as_ref() { let images = if let Some(display) = &self.egl_buffer_reader.borrow().as_ref() {
display.egl_buffer_contents(buffer) display.egl_buffer_contents(buffer)
} else { } else {
Err(BufferAccessError::NotManaged(buffer)) Err(BufferAccessError::NotManaged(
buffer,
smithay::backend::egl::EGLError::BadDisplay,
))
}; };
match images { match images {
Ok(images) => { Ok(images) => {
@ -193,7 +199,7 @@ impl<F: GLGraphicsBackend + 'static> GliumDrawer<F> {
images: Some(images), // I guess we need to keep this alive ? images: Some(images), // I guess we need to keep this alive ?
}) })
} }
Err(BufferAccessError::NotManaged(buffer)) => { Err(BufferAccessError::NotManaged(buffer, _)) => {
// this is not an EGL buffer, try SHM // this is not an EGL buffer, try SHM
self.texture_from_shm_buffer(buffer) self.texture_from_shm_buffer(buffer)
} }
@ -235,7 +241,7 @@ impl<F: GLGraphicsBackend + 'static> GliumDrawer<F> {
pub fn render_texture( pub fn render_texture(
&self, &self,
target: &mut glium::Frame, target: &mut Frame,
texture: &Texture2d, texture: &Texture2d,
texture_kind: usize, texture_kind: usize,
y_inverted: bool, y_inverted: bool,

View File

@ -324,7 +324,9 @@ impl<S: SessionNotifier, Data: 'static> UdevHandlerImpl<S, Data> {
for crtc in res_handles.filter_crtcs(encoder_info.possible_crtcs()) { for crtc in res_handles.filter_crtcs(encoder_info.possible_crtcs()) {
if let Entry::Vacant(entry) = backends.entry(crtc) { if let Entry::Vacant(entry) = backends.entry(crtc) {
let renderer = GliumDrawer::init( let renderer = GliumDrawer::init(
device.create_surface(crtc).unwrap(), device
.create_surface(crtc, connector_info.modes()[0], &[connector_info.handle()])
.unwrap(),
egl_buffer_reader.clone(), egl_buffer_reader.clone(),
logger.clone(), logger.clone(),
); );
@ -394,7 +396,7 @@ impl<S: SessionNotifier, Data: 'static> UdevHandlerImpl<S, Data> {
) )
.ok() .ok()
.and_then( .and_then(
|fd| match FallbackDevice::new(SessionFd(fd), self.logger.clone()) { |fd| match FallbackDevice::new(SessionFd(fd), true, self.logger.clone()) {
Ok(drm) => Some(drm), Ok(drm) => Some(drm),
Err(err) => { Err(err) => {
error!(self.logger, "Skipping drm device, because of error: {}", err); error!(self.logger, "Skipping drm device, because of error: {}", err);

View File

@ -56,7 +56,8 @@ fn main() {
let mut options = OpenOptions::new(); let mut options = OpenOptions::new();
options.read(true); options.read(true);
options.write(true); options.write(true);
let mut device = AtomicDrmDevice::new(options.open("/dev/dri/card0").unwrap(), log.clone()).unwrap(); let mut device =
AtomicDrmDevice::new(options.open("/dev/dri/card0").unwrap(), true, log.clone()).unwrap();
// Get a set of all modesetting resource handles (excluding planes): // Get a set of all modesetting resource handles (excluding planes):
let res_handles = Device::resource_handles(&device).unwrap(); let res_handles = Device::resource_handles(&device).unwrap();
@ -96,9 +97,11 @@ fn main() {
let mode = connector_info.modes()[0]; // Use first mode (usually highest resoltion, but in reality you should filter and sort and check and match with other connectors, if you use more then one.) let mode = connector_info.modes()[0]; // Use first mode (usually highest resoltion, but in reality you should filter and sort and check and match with other connectors, if you use more then one.)
// Initialize the hardware backend // Initialize the hardware backend
let surface = Rc::new(device.create_surface(crtc).unwrap()); let surface = Rc::new(
surface.set_connectors(&[connector_info.handle()]).unwrap(); device
surface.use_mode(Some(mode)).unwrap(); .create_surface(crtc, mode, &[connector_info.handle()])
.unwrap(),
);
for conn in surface.current_connectors().into_iter() { for conn in surface.current_connectors().into_iter() {
if conn != connector_info.handle() { if conn != connector_info.handle() {

View File

@ -9,7 +9,7 @@ use smithay::{
common::Error, common::Error,
device_bind, device_bind,
legacy::{LegacyDrmDevice, LegacyDrmSurface}, legacy::{LegacyDrmDevice, LegacyDrmSurface},
Device, DeviceHandler, RawSurface, Surface, Device, DeviceHandler, RawSurface,
}, },
reexports::{ reexports::{
calloop::EventLoop, calloop::EventLoop,
@ -40,7 +40,8 @@ fn main() {
let mut options = OpenOptions::new(); let mut options = OpenOptions::new();
options.read(true); options.read(true);
options.write(true); options.write(true);
let mut device = LegacyDrmDevice::new(options.open("/dev/dri/card0").unwrap(), log.clone()).unwrap(); let mut device =
LegacyDrmDevice::new(options.open("/dev/dri/card0").unwrap(), true, log.clone()).unwrap();
// Get a set of all modesetting resource handles (excluding planes): // Get a set of all modesetting resource handles (excluding planes):
let res_handles = Device::resource_handles(&device).unwrap(); let res_handles = Device::resource_handles(&device).unwrap();
@ -78,10 +79,12 @@ fn main() {
let mode = connector_info.modes()[0]; // Use first mode (usually highest resoltion, but in reality you should filter and sort and check and match with other connectors, if you use more then one.) let mode = connector_info.modes()[0]; // Use first mode (usually highest resoltion, but in reality you should filter and sort and check and match with other connectors, if you use more then one.)
// Initialize the hardware backend // Initialize the hardware backend
let surface = Rc::new(device.create_surface(crtc).unwrap()); let surface = Rc::new(
device
.create_surface(crtc, mode, &[connector_info.handle()])
.unwrap(),
);
surface.use_mode(Some(mode)).unwrap();
surface.set_connectors(&[connector_info.handle()]).unwrap();
/* /*
* Lets create buffers and framebuffers. * Lets create buffers and framebuffers.
* We use drm-rs DumbBuffers, because they always work and require little to no setup. * We use drm-rs DumbBuffers, because they always work and require little to no setup.

View File

@ -19,7 +19,8 @@ use std::sync::{
use drm::control::{atomic::AtomicModeReq, AtomicCommitFlags, Device as ControlDevice, Event}; use drm::control::{atomic::AtomicModeReq, AtomicCommitFlags, Device as ControlDevice, Event};
use drm::control::{ use drm::control::{
connector, crtc, encoder, framebuffer, plane, property, PropertyValueSet, ResourceHandle, ResourceHandles, connector, crtc, encoder, framebuffer, plane, property, Mode, PropertyValueSet, ResourceHandle,
ResourceHandles,
}; };
use drm::SystemError as DrmError; use drm::SystemError as DrmError;
use drm::{ClientCapability, Device as BasicDevice}; use drm::{ClientCapability, Device as BasicDevice};
@ -187,9 +188,21 @@ impl<A: AsRawFd + 'static> Dev<A> {
impl<A: AsRawFd + 'static> AtomicDrmDevice<A> { impl<A: AsRawFd + 'static> AtomicDrmDevice<A> {
/// Create a new [`AtomicDrmDevice`] from an open drm node /// Create a new [`AtomicDrmDevice`] from an open drm node
/// ///
/// Returns an error if the file is no valid drm node or context creation was not /// # Arguments
/// successful. ///
pub fn new<L>(fd: A, logger: L) -> Result<Self, Error> /// - `fd` - Open drm node
/// - `disable_connectors` - Setting this to true will initialize all connectors \
/// as disabled on device creation. smithay enables connectors, when attached \
/// to a surface, and disables them, when detached. Setting this to `false` \
/// requires usage of `drm-rs` to disable unused connectors to prevent them \
/// showing garbage, but will also prevent flickering of already turned on \
/// connectors (assuming you won't change the resolution).
/// - `logger` - Optional [`slog::Logger`] to be used by this device.
///
/// # Return
///
/// Returns an error if the file is no valid drm node or the device is not accessible.
pub fn new<L>(fd: A, disable_connectors: bool, logger: L) -> Result<Self, Error>
where where
L: Into<Option<::slog::Logger>>, L: Into<Option<::slog::Logger>>,
{ {
@ -261,7 +274,49 @@ impl<A: AsRawFd + 'static> AtomicDrmDevice<A> {
dev.old_state = old_state; dev.old_state = old_state;
dev.prop_mapping = mapping; dev.prop_mapping = mapping;
debug!(log, "Mapping: {:#?}", dev.prop_mapping); trace!(log, "Mapping: {:#?}", dev.prop_mapping);
if disable_connectors {
// Disable all connectors as initial state
let mut req = AtomicModeReq::new();
for conn in res_handles.connectors() {
let prop = dev
.prop_mapping
.0
.get(&conn)
.expect("Unknown handle")
.get("CRTC_ID")
.expect("Unknown property CRTC_ID");
req.add_property(*conn, *prop, property::Value::CRTC(None));
}
// A crtc without a connector has no mode, we also need to reset that.
// Otherwise the commit will not be accepted.
for crtc in res_handles.crtcs() {
let active_prop = dev
.prop_mapping
.1
.get(&crtc)
.expect("Unknown handle")
.get("ACTIVE")
.expect("Unknown property ACTIVE");
let mode_prop = dev
.prop_mapping
.1
.get(&crtc)
.expect("Unknown handle")
.get("MODE_ID")
.expect("Unknown property MODE_ID");
req.add_property(*crtc, *mode_prop, property::Value::Unknown(0));
req.add_property(*crtc, *active_prop, property::Value::Boolean(false));
}
dev.atomic_commit(&[AtomicCommitFlags::AllowModeset], req)
.compat()
.map_err(|source| Error::Access {
errmsg: "Failed to disable connectors",
dev: dev.dev_path(),
source,
})?;
}
Ok(AtomicDrmDevice { Ok(AtomicDrmDevice {
dev: Rc::new(dev), dev: Rc::new(dev),
@ -298,7 +353,12 @@ impl<A: AsRawFd + 'static> Device for AtomicDrmDevice<A> {
let _ = self.handler.take(); let _ = self.handler.take();
} }
fn create_surface(&mut self, crtc: crtc::Handle) -> Result<AtomicDrmSurface<A>, Error> { fn create_surface(
&mut self,
crtc: crtc::Handle,
mode: Mode,
connectors: &[connector::Handle],
) -> Result<AtomicDrmSurface<A>, Error> {
if self.backends.borrow().contains_key(&crtc) { if self.backends.borrow().contains_key(&crtc) {
return Err(Error::CrtcAlreadyInUse(crtc)); return Err(Error::CrtcAlreadyInUse(crtc));
} }
@ -307,9 +367,15 @@ impl<A: AsRawFd + 'static> Device for AtomicDrmDevice<A> {
return Err(Error::DeviceInactive); return Err(Error::DeviceInactive);
} }
if connectors.is_empty() {
return Err(Error::SurfaceWithoutConnectors(crtc));
}
let backend = Rc::new(AtomicDrmSurfaceInternal::new( let backend = Rc::new(AtomicDrmSurfaceInternal::new(
self.dev.clone(), self.dev.clone(),
crtc, crtc,
mode,
connectors,
self.logger.new(o!("crtc" => format!("{:?}", crtc))), self.logger.new(o!("crtc" => format!("{:?}", crtc))),
)?); )?);

View File

@ -3,8 +3,9 @@
//! to an open [`Session`](::backend::session::Session). //! to an open [`Session`](::backend::session::Session).
//! //!
use drm::control::crtc; use drm::control::{atomic::AtomicModeReq, crtc, property, AtomicCommitFlags, Device as ControlDevice};
use drm::Device as BasicDevice; use drm::Device as BasicDevice;
use failure::ResultExt;
use nix::libc::dev_t; use nix::libc::dev_t;
use nix::sys::stat; use nix::sys::stat;
use std::cell::RefCell; use std::cell::RefCell;
@ -15,6 +16,7 @@ use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc; use std::sync::Arc;
use super::{AtomicDrmDevice, AtomicDrmSurfaceInternal, Dev}; use super::{AtomicDrmDevice, AtomicDrmSurfaceInternal, Dev};
use crate::backend::drm::{common::Error, DevPath};
use crate::backend::session::{AsSessionObserver, SessionObserver}; use crate::backend::session::{AsSessionObserver, SessionObserver};
/// [`SessionObserver`](SessionObserver) /// [`SessionObserver`](SessionObserver)
@ -59,7 +61,7 @@ impl<A: AsRawFd + 'static> SessionObserver for AtomicDrmDeviceObserver<A> {
if let Err(err) = surface.clear_plane(surface.planes.cursor) { if let Err(err) = surface.clear_plane(surface.planes.cursor) {
warn!( warn!(
self.logger, self.logger,
"Failed to clear cursor on {:?}: {}", surface.crtc, err "Failed to clear cursor on {:?}: {}", surface.planes.cursor, err
); );
} }
} }
@ -95,5 +97,76 @@ impl<A: AsRawFd + 'static> SessionObserver for AtomicDrmDeviceObserver<A> {
} }
} }
self.active.store(true, Ordering::SeqCst); self.active.store(true, Ordering::SeqCst);
// okay, the previous session/whatever might left the drm devices in any state...
// lets fix that
if let Err(err) = self.reset_state() {
warn!(self.logger, "Unable to reset state after tty switch: {}", err);
// TODO call drm-handler::error
}
}
}
impl<A: AsRawFd + 'static> AtomicDrmDeviceObserver<A> {
fn reset_state(&mut self) -> Result<(), Error> {
if let Some(dev) = self.dev.upgrade() {
let res_handles = ControlDevice::resource_handles(&*dev)
.compat()
.map_err(|source| Error::Access {
errmsg: "Error loading drm resources",
dev: dev.dev_path(),
source,
})?;
// Disable all connectors (otherwise we might run into conflicting commits when restarting the rendering loop)
let mut req = AtomicModeReq::new();
for conn in res_handles.connectors() {
let prop = dev
.prop_mapping
.0
.get(&conn)
.expect("Unknown handle")
.get("CRTC_ID")
.expect("Unknown property CRTC_ID");
req.add_property(*conn, *prop, property::Value::CRTC(None));
}
// A crtc without a connector has no mode, we also need to reset that.
// Otherwise the commit will not be accepted.
for crtc in res_handles.crtcs() {
let mode_prop = dev
.prop_mapping
.1
.get(&crtc)
.expect("Unknown handle")
.get("MODE_ID")
.expect("Unknown property MODE_ID");
let active_prop = dev
.prop_mapping
.1
.get(&crtc)
.expect("Unknown handle")
.get("ACTIVE")
.expect("Unknown property ACTIVE");
req.add_property(*crtc, *active_prop, property::Value::Boolean(false));
req.add_property(*crtc, *mode_prop, property::Value::Unknown(0));
}
dev.atomic_commit(&[AtomicCommitFlags::AllowModeset], req)
.compat()
.map_err(|source| Error::Access {
errmsg: "Failed to disable connectors",
dev: dev.dev_path(),
source,
})?;
if let Some(backends) = self.backends.upgrade() {
for surface in backends.borrow().values().filter_map(Weak::upgrade) {
let mut current = surface.state.write().unwrap();
// lets force a non matching state
current.connectors.clear();
current.mode = unsafe { std::mem::zeroed() };
}
}
}
Ok(())
} }
} }

View File

@ -1,21 +1,22 @@
use drm::buffer::Buffer; use drm::buffer::Buffer;
use drm::control::atomic::AtomicModeReq; use drm::control::atomic::AtomicModeReq;
use drm::control::Device as ControlDevice; use drm::control::Device as ControlDevice;
use drm::control::{connector, crtc, framebuffer, plane, property, AtomicCommitFlags, Mode, PlaneType}; use drm::control::{
connector, crtc, dumbbuffer::DumbBuffer, framebuffer, plane, property, AtomicCommitFlags, Mode, PlaneType,
};
use drm::Device as BasicDevice; use drm::Device as BasicDevice;
use std::cell::Cell; use std::cell::Cell;
use std::collections::HashSet; use std::collections::HashSet;
use std::os::unix::io::{AsRawFd, RawFd}; use std::os::unix::io::{AsRawFd, RawFd};
use std::rc::Rc; use std::rc::Rc;
use std::sync::RwLock; use std::sync::{atomic::Ordering, RwLock};
use failure::ResultExt as FailureResultExt; use failure::ResultExt as FailureResultExt;
use super::Dev; use super::Dev;
use crate::backend::drm::{common::Error, DevPath, RawSurface, Surface}; use crate::backend::drm::{common::Error, DevPath, RawSurface, Surface};
use crate::backend::graphics::CursorBackend; use crate::backend::graphics::CursorBackend;
use crate::backend::graphics::SwapBuffersError;
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
pub struct CursorState { pub struct CursorState {
@ -26,8 +27,8 @@ pub struct CursorState {
#[derive(Debug, PartialEq, Eq, Clone)] #[derive(Debug, PartialEq, Eq, Clone)]
pub struct State { pub struct State {
pub mode: Option<Mode>, pub mode: Mode,
pub blob: Option<property::Value<'static>>, pub blob: property::Value<'static>,
pub connectors: HashSet<connector::Handle>, pub connectors: HashSet<connector::Handle>,
} }
@ -45,6 +46,7 @@ pub(super) struct AtomicDrmSurfaceInternal<A: AsRawFd + 'static> {
pub(super) state: RwLock<State>, pub(super) state: RwLock<State>,
pub(super) pending: RwLock<State>, pub(super) pending: RwLock<State>,
pub(super) logger: ::slog::Logger, pub(super) logger: ::slog::Logger,
pub(super) test_buffer: Cell<Option<(DumbBuffer, framebuffer::Handle)>>,
} }
impl<A: AsRawFd + 'static> AsRawFd for AtomicDrmSurfaceInternal<A> { impl<A: AsRawFd + 'static> AsRawFd for AtomicDrmSurfaceInternal<A> {
@ -57,27 +59,45 @@ impl<A: AsRawFd + 'static> BasicDevice for AtomicDrmSurfaceInternal<A> {}
impl<A: AsRawFd + 'static> ControlDevice for AtomicDrmSurfaceInternal<A> {} impl<A: AsRawFd + 'static> ControlDevice for AtomicDrmSurfaceInternal<A> {}
impl<A: AsRawFd + 'static> AtomicDrmSurfaceInternal<A> { impl<A: AsRawFd + 'static> AtomicDrmSurfaceInternal<A> {
pub(crate) fn new(dev: Rc<Dev<A>>, crtc: crtc::Handle, logger: ::slog::Logger) -> Result<Self, Error> { pub(crate) fn new(
dev: Rc<Dev<A>>,
crtc: crtc::Handle,
mode: Mode,
connectors: &[connector::Handle],
logger: ::slog::Logger,
) -> Result<Self, Error> {
let crtc_info = dev.get_crtc(crtc).compat().map_err(|source| Error::Access { let crtc_info = dev.get_crtc(crtc).compat().map_err(|source| Error::Access {
errmsg: "Error loading crtc info", errmsg: "Error loading crtc info",
dev: dev.dev_path(), dev: dev.dev_path(),
source, source,
})?; })?;
let mode = crtc_info.mode(); // If we have no current mode, we create a fake one, which will not match (and thus gets overriden on the commit below).
let blob = match mode { // A better fix would probably be making mode an `Option`, but that would mean
Some(mode) => Some( // we need to be sure, we require a mode to always be set without relying on the compiler.
dev.create_property_blob(mode) // So we cheat, because it works and is easier to handle later.
.compat() let current_mode = crtc_info.mode().unwrap_or_else(|| unsafe { std::mem::zeroed() });
.map_err(|source| Error::Access { let current_blob = match crtc_info.mode() {
errmsg: "Failed to create Property Blob for mode", Some(mode) => dev
dev: dev.dev_path(), .create_property_blob(mode)
source, .compat()
})?, .map_err(|source| Error::Access {
), errmsg: "Failed to create Property Blob for mode",
None => None, dev: dev.dev_path(),
source,
})?,
None => property::Value::Unknown(0),
}; };
let blob = dev
.create_property_blob(mode)
.compat()
.map_err(|source| Error::Access {
errmsg: "Failed to create Property Blob for mode",
dev: dev.dev_path(),
source,
})?;
let res_handles = ControlDevice::resource_handles(&*dev) let res_handles = ControlDevice::resource_handles(&*dev)
.compat() .compat()
.map_err(|source| Error::Access { .map_err(|source| Error::Access {
@ -86,12 +106,7 @@ impl<A: AsRawFd + 'static> AtomicDrmSurfaceInternal<A> {
source, source,
})?; })?;
let mut state = State { let mut current_connectors = HashSet::new();
mode,
blob,
connectors: HashSet::new(),
};
for conn in res_handles.connectors() { for conn in res_handles.connectors() {
let crtc_prop = dev let crtc_prop = dev
.prop_mapping .prop_mapping
@ -113,7 +128,7 @@ impl<A: AsRawFd + 'static> AtomicDrmSurfaceInternal<A> {
crtc_prop_info.value_type().convert_value(val) crtc_prop_info.value_type().convert_value(val)
{ {
if conn_crtc == crtc { if conn_crtc == crtc {
state.connectors.insert(*conn); current_connectors.insert(*conn);
} }
} }
break; break;
@ -121,13 +136,23 @@ impl<A: AsRawFd + 'static> AtomicDrmSurfaceInternal<A> {
} }
} }
} }
let state = State {
mode: current_mode,
blob: current_blob,
connectors: current_connectors,
};
let pending = State {
mode,
blob,
connectors: connectors.iter().copied().collect(),
};
let (primary, cursor) = let (primary, cursor) =
AtomicDrmSurfaceInternal::find_planes(&dev, crtc).ok_or(Error::NoSuitablePlanes { AtomicDrmSurfaceInternal::find_planes(&dev, crtc).ok_or(Error::NoSuitablePlanes {
crtc, crtc,
dev: dev.dev_path(), dev: dev.dev_path(),
})?; })?;
Ok(AtomicDrmSurfaceInternal { let surface = AtomicDrmSurfaceInternal {
dev, dev,
crtc, crtc,
cursor: CursorState { cursor: CursorState {
@ -136,10 +161,102 @@ impl<A: AsRawFd + 'static> AtomicDrmSurfaceInternal<A> {
hotspot: Cell::new((0, 0)), hotspot: Cell::new((0, 0)),
}, },
planes: Planes { primary, cursor }, planes: Planes { primary, cursor },
state: RwLock::new(state.clone()), state: RwLock::new(state),
pending: RwLock::new(state), pending: RwLock::new(pending),
logger, logger,
}) test_buffer: Cell::new(None),
};
Ok(surface)
}
fn create_test_buffer(&self, mode: &Mode) -> Result<framebuffer::Handle, Error> {
let (w, h) = mode.size();
let db = self
.create_dumb_buffer((w as u32, h as u32), drm::buffer::format::PixelFormat::ARGB8888)
.compat()
.map_err(|source| Error::Access {
errmsg: "Failed to create dumb buffer",
dev: self.dev_path(),
source,
})?;
let fb = self
.add_framebuffer(&db)
.compat()
.map_err(|source| Error::Access {
errmsg: "Failed to create framebuffer",
dev: self.dev_path(),
source,
})?;
if let Some((old_db, old_fb)) = self.test_buffer.replace(Some((db, fb))) {
let _ = self.destroy_framebuffer(old_fb);
let _ = self.destroy_dumb_buffer(old_db);
};
Ok(fb)
}
}
impl<A: AsRawFd + 'static> Drop for AtomicDrmSurfaceInternal<A> {
fn drop(&mut self) {
if let Some((db, fb)) = self.test_buffer.take() {
let _ = self.destroy_framebuffer(fb);
let _ = self.destroy_dumb_buffer(db);
}
if !self.dev.active.load(Ordering::SeqCst) {
// the device is gone or we are on another tty
// old state has been restored, we shouldn't touch it.
// if we are on another tty the connectors will get disabled
// by the device, when switching back
return;
}
// other ttys that use no cursor, might not clear it themselves.
// This makes sure our cursor won't stay visible.
if let Err(err) = self.clear_plane(self.planes.cursor) {
warn!(
self.logger,
"Failed to clear cursor on {:?}: {}", self.planes.cursor, err
);
}
// disable connectors again
let current = self.state.read().unwrap();
let mut req = AtomicModeReq::new();
for conn in current.connectors.iter() {
let prop = self
.dev
.prop_mapping
.0
.get(&conn)
.expect("Unknown Handle")
.get("CRTC_ID")
.expect("Unknown property CRTC_ID");
req.add_property(*conn, *prop, property::Value::CRTC(None));
}
let active_prop = self
.dev
.prop_mapping
.1
.get(&self.crtc)
.expect("Unknown Handle")
.get("ACTIVE")
.expect("Unknown property ACTIVE");
let mode_prop = self
.dev
.prop_mapping
.1
.get(&self.crtc)
.expect("Unknown Handle")
.get("MODE_ID")
.expect("Unknown property MODE_ID");
req.add_property(self.crtc, *active_prop, property::Value::Boolean(false));
req.add_property(self.crtc, *mode_prop, property::Value::Unknown(0));
if let Err(err) = self.atomic_commit(&[AtomicCommitFlags::AllowModeset], req) {
warn!(self.logger, "Unable to disable connectors: {}", err);
}
} }
} }
@ -159,15 +276,19 @@ impl<A: AsRawFd + 'static> Surface for AtomicDrmSurfaceInternal<A> {
self.pending.read().unwrap().connectors.clone() self.pending.read().unwrap().connectors.clone()
} }
fn current_mode(&self) -> Option<Mode> { fn current_mode(&self) -> Mode {
self.state.read().unwrap().mode self.state.read().unwrap().mode
} }
fn pending_mode(&self) -> Option<Mode> { fn pending_mode(&self) -> Mode {
self.pending.read().unwrap().mode self.pending.read().unwrap().mode
} }
fn add_connector(&self, conn: connector::Handle) -> Result<(), Error> { fn add_connector(&self, conn: connector::Handle) -> Result<(), Error> {
if !self.dev.active.load(Ordering::SeqCst) {
return Err(Error::DeviceInactive);
}
let info = self let info = self
.get_connector(conn) .get_connector(conn)
.compat() .compat()
@ -180,15 +301,15 @@ impl<A: AsRawFd + 'static> Surface for AtomicDrmSurfaceInternal<A> {
let mut pending = self.pending.write().unwrap(); let mut pending = self.pending.write().unwrap();
// check if the connector can handle the current mode // check if the connector can handle the current mode
if info.modes().contains(pending.mode.as_ref().unwrap()) { if info.modes().contains(&pending.mode) {
// check if config is supported // check if config is supported
let req = self.build_request( let req = self.build_request(
&mut [conn].iter(), &mut [conn].iter(),
&mut [].iter(), &mut [].iter(),
&self.planes, &self.planes,
None, Some(self.create_test_buffer(&pending.mode)?),
pending.mode, Some(pending.mode),
pending.blob, Some(pending.blob),
)?; )?;
self.atomic_commit( self.atomic_commit(
&[AtomicCommitFlags::AllowModeset, AtomicCommitFlags::TestOnly], &[AtomicCommitFlags::AllowModeset, AtomicCommitFlags::TestOnly],
@ -202,21 +323,30 @@ impl<A: AsRawFd + 'static> Surface for AtomicDrmSurfaceInternal<A> {
Ok(()) Ok(())
} else { } else {
Err(Error::ModeNotSuitable(pending.mode.unwrap())) Err(Error::ModeNotSuitable(pending.mode))
} }
} }
fn remove_connector(&self, conn: connector::Handle) -> Result<(), Error> { fn remove_connector(&self, conn: connector::Handle) -> Result<(), Error> {
if !self.dev.active.load(Ordering::SeqCst) {
return Err(Error::DeviceInactive);
}
let mut pending = self.pending.write().unwrap(); let mut pending = self.pending.write().unwrap();
// the test would also prevent this, but the error message is far less helpful
if pending.connectors.contains(&conn) && pending.connectors.len() == 1 {
return Err(Error::SurfaceWithoutConnectors(self.crtc));
}
// check if new config is supported (should be) // check if new config is supported (should be)
let req = self.build_request( let req = self.build_request(
&mut [].iter(), &mut [].iter(),
&mut [conn].iter(), &mut [conn].iter(),
&self.planes, &self.planes,
None, Some(self.create_test_buffer(&pending.mode)?),
pending.mode, Some(pending.mode),
pending.blob, Some(pending.blob),
)?; )?;
self.atomic_commit( self.atomic_commit(
&[AtomicCommitFlags::AllowModeset, AtomicCommitFlags::TestOnly], &[AtomicCommitFlags::AllowModeset, AtomicCommitFlags::TestOnly],
@ -232,6 +362,14 @@ impl<A: AsRawFd + 'static> Surface for AtomicDrmSurfaceInternal<A> {
} }
fn set_connectors(&self, connectors: &[connector::Handle]) -> Result<(), Error> { fn set_connectors(&self, connectors: &[connector::Handle]) -> Result<(), Error> {
if connectors.is_empty() {
return Err(Error::SurfaceWithoutConnectors(self.crtc));
}
if !self.dev.active.load(Ordering::SeqCst) {
return Err(Error::DeviceInactive);
}
let current = self.state.write().unwrap(); let current = self.state.write().unwrap();
let mut pending = self.pending.write().unwrap(); let mut pending = self.pending.write().unwrap();
@ -243,9 +381,9 @@ impl<A: AsRawFd + 'static> Surface for AtomicDrmSurfaceInternal<A> {
&mut added, &mut added,
&mut removed, &mut removed,
&self.planes, &self.planes,
None, Some(self.create_test_buffer(&pending.mode)?),
pending.mode, Some(pending.mode),
pending.blob, Some(pending.blob),
)?; )?;
self.atomic_commit( self.atomic_commit(
@ -259,30 +397,31 @@ impl<A: AsRawFd + 'static> Surface for AtomicDrmSurfaceInternal<A> {
Ok(()) Ok(())
} }
fn use_mode(&self, mode: Option<Mode>) -> Result<(), Error> { fn use_mode(&self, mode: Mode) -> Result<(), Error> {
if !self.dev.active.load(Ordering::SeqCst) {
return Err(Error::DeviceInactive);
}
let mut pending = self.pending.write().unwrap(); let mut pending = self.pending.write().unwrap();
// check if new config is supported // check if new config is supported
let new_blob = Some(match mode { let new_blob = self
Some(mode) => self .create_property_blob(mode)
.dev .compat()
.create_property_blob(mode) .map_err(|source| Error::Access {
.compat() errmsg: "Failed to create Property Blob for mode",
.map_err(|source| Error::Access { dev: self.dev_path(),
errmsg: "Failed to create Property Blob for mode", source,
dev: self.dev_path(), })?;
source,
})?,
None => property::Value::Unknown(0),
});
let test_fb = Some(self.create_test_buffer(&pending.mode)?);
let req = self.build_request( let req = self.build_request(
&mut pending.connectors.iter(), &mut pending.connectors.iter(),
&mut [].iter(), &mut [].iter(),
&self.planes, &self.planes,
None, test_fb,
mode, Some(mode),
new_blob, Some(new_blob),
)?; )?;
if let Err(err) = self if let Err(err) = self
.atomic_commit( .atomic_commit(
@ -292,7 +431,7 @@ impl<A: AsRawFd + 'static> Surface for AtomicDrmSurfaceInternal<A> {
.compat() .compat()
.map_err(|_| Error::TestFailed(self.crtc)) .map_err(|_| Error::TestFailed(self.crtc))
{ {
let _ = self.dev.destroy_property_blob(new_blob.unwrap().into()); let _ = self.dev.destroy_property_blob(new_blob.into());
return Err(err); return Err(err);
} }
@ -310,6 +449,10 @@ impl<A: AsRawFd + 'static> RawSurface for AtomicDrmSurfaceInternal<A> {
} }
fn commit(&self, framebuffer: framebuffer::Handle) -> Result<(), Error> { fn commit(&self, framebuffer: framebuffer::Handle) -> Result<(), Error> {
if !self.dev.active.load(Ordering::SeqCst) {
return Err(Error::DeviceInactive);
}
let mut current = self.state.write().unwrap(); let mut current = self.state.write().unwrap();
let mut pending = self.pending.write().unwrap(); let mut pending = self.pending.write().unwrap();
@ -340,11 +483,7 @@ impl<A: AsRawFd + 'static> RawSurface for AtomicDrmSurfaceInternal<A> {
} }
if current.mode != pending.mode { if current.mode != pending.mode {
info!( info!(self.logger, "Setting new mode: {:?}", pending.mode.name());
self.logger,
"Setting new mode: {:?}",
pending.mode.as_ref().unwrap().name()
);
} }
trace!(self.logger, "Testing screen config"); trace!(self.logger, "Testing screen config");
@ -355,8 +494,8 @@ impl<A: AsRawFd + 'static> RawSurface for AtomicDrmSurfaceInternal<A> {
&mut removed, &mut removed,
&self.planes, &self.planes,
Some(framebuffer), Some(framebuffer),
pending.mode, Some(pending.mode),
pending.blob, Some(pending.blob),
)?; )?;
if let Err(err) = self if let Err(err) = self
@ -380,15 +519,13 @@ impl<A: AsRawFd + 'static> RawSurface for AtomicDrmSurfaceInternal<A> {
&mut [].iter(), &mut [].iter(),
&self.planes, &self.planes,
Some(framebuffer), Some(framebuffer),
current.mode, Some(current.mode),
current.blob, Some(current.blob),
)? )?
} else { } else {
if current.mode != pending.mode { if current.mode != pending.mode {
if let Some(blob) = current.blob { if let Err(err) = self.dev.destroy_property_blob(current.blob.into()) {
if let Err(err) = self.dev.destroy_property_blob(blob.into()) { warn!(self.logger, "Failed to destory old mode property blob: {}", err);
warn!(self.logger, "Failed to destory old mode property blob: {}", err);
}
} }
} }
*current = pending.clone(); *current = pending.clone();
@ -398,7 +535,7 @@ impl<A: AsRawFd + 'static> RawSurface for AtomicDrmSurfaceInternal<A> {
} }
}; };
debug!(self.logger, "Setting screen: {:#?}", req); debug!(self.logger, "Setting screen: {:?}", req);
self.atomic_commit( self.atomic_commit(
&[ &[
AtomicCommitFlags::PageFlipEvent, AtomicCommitFlags::PageFlipEvent,
@ -417,23 +554,31 @@ impl<A: AsRawFd + 'static> RawSurface for AtomicDrmSurfaceInternal<A> {
Ok(()) Ok(())
} }
fn page_flip(&self, framebuffer: framebuffer::Handle) -> Result<(), SwapBuffersError> { fn page_flip(&self, framebuffer: framebuffer::Handle) -> Result<(), Error> {
let req = self if !self.dev.active.load(Ordering::SeqCst) {
.build_request( return Err(Error::DeviceInactive);
&mut [].iter(), }
&mut [].iter(),
&self.planes, let req = self.build_request(
Some(framebuffer), &mut [].iter(),
None, &mut [].iter(),
None, &self.planes,
) //current.mode) Some(framebuffer),
.map_err(|_| SwapBuffersError::ContextLost)?; None,
None,
)?;
trace!(self.logger, "Queueing page flip: {:#?}", req); trace!(self.logger, "Queueing page flip: {:#?}", req);
self.atomic_commit( self.atomic_commit(
&[AtomicCommitFlags::PageFlipEvent, AtomicCommitFlags::Nonblock], &[AtomicCommitFlags::PageFlipEvent, AtomicCommitFlags::Nonblock],
req, req,
) )
.map_err(|_| SwapBuffersError::ContextLost)?; .compat()
.map_err(|source| Error::Access {
errmsg: "Page flip commit failed",
dev: self.dev_path(),
source,
})?;
Ok(()) Ok(())
} }
@ -444,6 +589,10 @@ impl<A: AsRawFd + 'static> CursorBackend for AtomicDrmSurfaceInternal<A> {
type Error = Error; type Error = Error;
fn set_cursor_position(&self, x: u32, y: u32) -> Result<(), Error> { fn set_cursor_position(&self, x: u32, y: u32) -> Result<(), Error> {
if !self.dev.active.load(Ordering::SeqCst) {
return Err(Error::DeviceInactive);
}
trace!(self.logger, "New cursor position ({},{}) pending", x, y); trace!(self.logger, "New cursor position ({},{}) pending", x, y);
self.cursor.position.set(Some((x, y))); self.cursor.position.set(Some((x, y)));
Ok(()) Ok(())
@ -454,6 +603,10 @@ impl<A: AsRawFd + 'static> CursorBackend for AtomicDrmSurfaceInternal<A> {
buffer: &Self::CursorFormat, buffer: &Self::CursorFormat,
hotspot: (u32, u32), hotspot: (u32, u32),
) -> Result<(), Error> { ) -> Result<(), Error> {
if !self.dev.active.load(Ordering::SeqCst) {
return Err(Error::DeviceInactive);
}
trace!(self.logger, "Setting the new imported cursor"); trace!(self.logger, "Setting the new imported cursor");
if let Some(fb) = self.cursor.framebuffer.get().take() { if let Some(fb) = self.cursor.framebuffer.get().take() {
@ -834,11 +987,11 @@ impl<A: AsRawFd + 'static> Surface for AtomicDrmSurface<A> {
self.0.pending_connectors() self.0.pending_connectors()
} }
fn current_mode(&self) -> Option<Mode> { fn current_mode(&self) -> Mode {
self.0.current_mode() self.0.current_mode()
} }
fn pending_mode(&self) -> Option<Mode> { fn pending_mode(&self) -> Mode {
self.0.pending_mode() self.0.pending_mode()
} }
@ -854,7 +1007,7 @@ impl<A: AsRawFd + 'static> Surface for AtomicDrmSurface<A> {
self.0.set_connectors(connectors) self.0.set_connectors(connectors)
} }
fn use_mode(&self, mode: Option<Mode>) -> Result<(), Error> { fn use_mode(&self, mode: Mode) -> Result<(), Error> {
self.0.use_mode(mode) self.0.use_mode(mode)
} }
} }
@ -868,7 +1021,7 @@ impl<A: AsRawFd + 'static> RawSurface for AtomicDrmSurface<A> {
self.0.commit(framebuffer) self.0.commit(framebuffer)
} }
fn page_flip(&self, framebuffer: framebuffer::Handle) -> Result<(), SwapBuffersError> { fn page_flip(&self, framebuffer: framebuffer::Handle) -> Result<(), Error> {
RawSurface::page_flip(&*self.0, framebuffer) RawSurface::page_flip(&*self.0, framebuffer)
} }
} }

View File

@ -22,6 +22,7 @@ use drm::{
#[cfg(feature = "renderer_gl")] #[cfg(feature = "renderer_gl")]
use nix::libc::c_void; use nix::libc::c_void;
use nix::libc::dev_t; use nix::libc::dev_t;
use std::env;
use std::os::unix::io::{AsRawFd, RawFd}; use std::os::unix::io::{AsRawFd, RawFd};
#[cfg(feature = "use_system_lib")] #[cfg(feature = "use_system_lib")]
use wayland_server::Display; use wayland_server::Display;
@ -150,19 +151,53 @@ pub enum FallbackSurface<S1: Surface, S2: Surface> {
impl<A: AsRawFd + Clone + 'static> FallbackDevice<AtomicDrmDevice<A>, LegacyDrmDevice<A>> { impl<A: AsRawFd + Clone + 'static> FallbackDevice<AtomicDrmDevice<A>, LegacyDrmDevice<A>> {
/// Try to initialize an [`AtomicDrmDevice`](::backend::drm:;atomic::AtomicDrmDevice) /// Try to initialize an [`AtomicDrmDevice`](::backend::drm:;atomic::AtomicDrmDevice)
/// and fall back to a [`LegacyDrmDevice`] if atomic-modesetting is not supported. /// and fall back to a [`LegacyDrmDevice`] if atomic-modesetting is not supported.
pub fn new<L>(fd: A, logger: L) -> Result<Self, Error> ///
/// # Arguments
///
/// - `fd` - Open drm node (needs to be clonable to be passed to multiple initializers)
/// - `disable_connectors` - Setting this to true will initialize all connectors \
/// as disabled on device creation. smithay enables connectors, when attached \
/// to a surface, and disables them, when detached. Setting this to `false` \
/// requires usage of `drm-rs` to disable unused connectors to prevent them \
/// showing garbage, but will also prevent flickering of already turned on \
/// connectors (assuming you won't change the resolution).
/// - `logger` - Optional [`slog::Logger`] to be used by the resulting device.
///
/// # Return
///
/// Returns an error, if both devices fail to initialize due to `fd` being no valid
/// drm node or the device being not accessible.
pub fn new<L>(fd: A, disable_connectors: bool, logger: L) -> Result<Self, Error>
where where
L: Into<Option<::slog::Logger>>, L: Into<Option<::slog::Logger>>,
{ {
let log = crate::slog_or_stdlog(logger).new(o!("smithay_module" => "backend_drm_fallback")); let log = crate::slog_or_stdlog(logger).new(o!("smithay_module" => "backend_drm_fallback"));
info!(log, "Trying to initialize AtomicDrmDevice"); info!(log, "Trying to initialize AtomicDrmDevice");
match AtomicDrmDevice::new(fd.clone(), log.clone()) { let force_legacy = env::var("SMITHAY_USE_LEGACY")
.map(|x| {
x == "1" || x.to_lowercase() == "true" || x.to_lowercase() == "yes" || x.to_lowercase() == "y"
})
.unwrap_or(false);
if force_legacy {
info!(log, "SMITHAY_USE_LEGACY is set. Forcing LegacyDrmDevice.");
return Ok(FallbackDevice::Fallback(LegacyDrmDevice::new(
fd,
disable_connectors,
log,
)?));
}
match AtomicDrmDevice::new(fd.clone(), disable_connectors, log.clone()) {
Ok(dev) => Ok(FallbackDevice::Preference(dev)), Ok(dev) => Ok(FallbackDevice::Preference(dev)),
Err(err) => { Err(err) => {
error!(log, "Failed to initialize preferred AtomicDrmDevice: {}", err); error!(log, "Failed to initialize preferred AtomicDrmDevice: {}", err);
info!(log, "Falling back to fallback LegacyyDrmDevice"); info!(log, "Falling back to fallback LegacyDrmDevice");
Ok(FallbackDevice::Fallback(LegacyDrmDevice::new(fd, log)?)) Ok(FallbackDevice::Fallback(LegacyDrmDevice::new(
fd,
disable_connectors,
log,
)?))
} }
} }
} }
@ -228,10 +263,19 @@ where
} }
} }
fallback_device_impl!(clear_handler, &mut Self); fallback_device_impl!(clear_handler, &mut Self);
fn create_surface(&mut self, crtc: crtc::Handle) -> Result<Self::Surface, E> { fn create_surface(
&mut self,
crtc: crtc::Handle,
mode: Mode,
connectors: &[connector::Handle],
) -> Result<Self::Surface, E> {
match self { match self {
FallbackDevice::Preference(dev) => Ok(FallbackSurface::Preference(dev.create_surface(crtc)?)), FallbackDevice::Preference(dev) => Ok(FallbackSurface::Preference(
FallbackDevice::Fallback(dev) => Ok(FallbackSurface::Fallback(dev.create_surface(crtc)?)), dev.create_surface(crtc, mode, connectors)?,
)),
FallbackDevice::Fallback(dev) => Ok(FallbackSurface::Fallback(
dev.create_surface(crtc, mode, connectors)?,
)),
} }
} }
fallback_device_impl!(process_events, &mut Self); fallback_device_impl!(process_events, &mut Self);
@ -281,9 +325,9 @@ where
fallback_surface_impl!(add_connector, &Self, Result<(), E>, conn: connector::Handle); fallback_surface_impl!(add_connector, &Self, Result<(), E>, conn: connector::Handle);
fallback_surface_impl!(remove_connector, &Self, Result<(), E>, conn: connector::Handle); fallback_surface_impl!(remove_connector, &Self, Result<(), E>, conn: connector::Handle);
fallback_surface_impl!(set_connectors, &Self, Result<(), E>, conns: &[connector::Handle]); fallback_surface_impl!(set_connectors, &Self, Result<(), E>, conns: &[connector::Handle]);
fallback_surface_impl!(current_mode, &Self, Option<Mode>); fallback_surface_impl!(current_mode, &Self, Mode);
fallback_surface_impl!(pending_mode, &Self, Option<Mode>); fallback_surface_impl!(pending_mode, &Self, Mode);
fallback_surface_impl!(use_mode, &Self, Result<(), E>, mode: Option<Mode>); fallback_surface_impl!(use_mode, &Self, Result<(), E>, mode: Mode);
} }
impl<E, C, S1, S2> RawSurface for FallbackSurface<S1, S2> impl<E, C, S1, S2> RawSurface for FallbackSurface<S1, S2>
@ -295,7 +339,7 @@ where
{ {
fallback_surface_impl!(commit_pending, &Self, bool); fallback_surface_impl!(commit_pending, &Self, bool);
fallback_surface_impl!(commit, &Self, Result<(), E>, fb: framebuffer::Handle); fallback_surface_impl!(commit, &Self, Result<(), E>, fb: framebuffer::Handle);
fn page_flip(&self, framebuffer: framebuffer::Handle) -> Result<(), SwapBuffersError> { fn page_flip(&self, framebuffer: framebuffer::Handle) -> Result<(), E> {
match self { match self {
FallbackSurface::Preference(dev) => RawSurface::page_flip(dev, framebuffer), FallbackSurface::Preference(dev) => RawSurface::page_flip(dev, framebuffer),
FallbackSurface::Fallback(dev) => RawSurface::page_flip(dev, framebuffer), FallbackSurface::Fallback(dev) => RawSurface::page_flip(dev, framebuffer),

View File

@ -3,8 +3,8 @@
//! and [`Surface`](::backend::drm::Surface) implementations of the `backend::drm` module. //! and [`Surface`](::backend::drm::Surface) implementations of the `backend::drm` module.
//! //!
use crate::backend::graphics::SwapBuffersError;
use drm::control::{connector, crtc, Mode, RawResourceHandle}; use drm::control::{connector, crtc, Mode, RawResourceHandle};
use std::path::PathBuf; use std::path::PathBuf;
pub mod fallback; pub mod fallback;
@ -19,7 +19,7 @@ pub enum Error {
#[error("Failed to aquire DRM master")] #[error("Failed to aquire DRM master")]
DrmMasterFailed, DrmMasterFailed,
/// The `DrmDevice` encountered an access error /// The `DrmDevice` encountered an access error
#[error("DRM access error: {errmsg} on device `{dev:?}`")] #[error("DRM access error: {errmsg} on device `{dev:?}` ({source:})")]
Access { Access {
/// Error message associated to the access error /// Error message associated to the access error
errmsg: &'static str, errmsg: &'static str,
@ -40,6 +40,9 @@ pub enum Error {
/// The given crtc is already in use by another backend /// The given crtc is already in use by another backend
#[error("Crtc `{0:?}` is already in use by another backend")] #[error("Crtc `{0:?}` is already in use by another backend")]
CrtcAlreadyInUse(crtc::Handle), CrtcAlreadyInUse(crtc::Handle),
/// This operation would result in a surface without connectors.
#[error("Surface of crtc `{0:?}` would have no connectors, which is not accepted")]
SurfaceWithoutConnectors(crtc::Handle),
/// No encoder was found for a given connector on the set crtc /// No encoder was found for a given connector on the set crtc
#[error("No encoder found for the given connector '{connector:?}' on crtc `{crtc:?}`")] #[error("No encoder found for the given connector '{connector:?}' on crtc `{crtc:?}`")]
NoSuitableEncoder { NoSuitableEncoder {
@ -68,3 +71,25 @@ pub enum Error {
#[error("Atomic Test failed for new properties on crtc ({0:?})")] #[error("Atomic Test failed for new properties on crtc ({0:?})")]
TestFailed(crtc::Handle), TestFailed(crtc::Handle),
} }
impl Into<SwapBuffersError> for Error {
fn into(self) -> SwapBuffersError {
match self {
x @ Error::DeviceInactive => SwapBuffersError::TemporaryFailure(Box::new(x)),
Error::Access { source, .. }
if match source.get_ref() {
drm::SystemError::Unknown {
errno: nix::errno::Errno::EBUSY,
} => true,
drm::SystemError::Unknown {
errno: nix::errno::Errno::EINTR,
} => true,
_ => false,
} =>
{
SwapBuffersError::TemporaryFailure(Box::new(source))
}
x => SwapBuffersError::ContextLost(Box::new(x)),
}
}
}

View File

@ -8,7 +8,7 @@
//! Take a look at `anvil`s source code for an example of this. //! Take a look at `anvil`s source code for an example of this.
//! //!
use drm::control::{connector, crtc, encoder, framebuffer, plane, ResourceHandles}; use drm::control::{connector, crtc, encoder, framebuffer, plane, Mode, ResourceHandles};
use drm::SystemError as DrmError; use drm::SystemError as DrmError;
use nix::libc::dev_t; use nix::libc::dev_t;
use std::os::unix::io::{AsRawFd, RawFd}; use std::os::unix::io::{AsRawFd, RawFd};
@ -17,9 +17,9 @@ use wayland_server::Display;
use super::{Device, DeviceHandler, Surface}; use super::{Device, DeviceHandler, Surface};
use crate::backend::egl::native::{Backend, NativeDisplay, NativeSurface}; use crate::backend::egl::native::{Backend, NativeDisplay, NativeSurface};
use crate::backend::egl::Error as EGLError;
#[cfg(feature = "use_system_lib")] #[cfg(feature = "use_system_lib")]
use crate::backend::egl::{display::EGLBufferReader, EGLGraphicsBackend}; use crate::backend::egl::{display::EGLBufferReader, EGLGraphicsBackend};
use crate::backend::egl::{EGLError as RawEGLError, Error as EGLError, SurfaceCreationError};
mod surface; mod surface;
pub use self::surface::*; pub use self::surface::*;
@ -33,18 +33,25 @@ pub mod session;
#[derive(thiserror::Error, Debug)] #[derive(thiserror::Error, Debug)]
pub enum Error<U: std::error::Error + std::fmt::Debug + std::fmt::Display + 'static> { pub enum Error<U: std::error::Error + std::fmt::Debug + std::fmt::Display + 'static> {
/// EGL Error /// EGL Error
#[error("EGL error: {0:?}")] #[error("EGL error: {0:}")]
EGL(#[source] EGLError), EGL(#[source] EGLError),
/// EGL Error
#[error("EGL error: {0:}")]
RawEGL(#[source] RawEGLError),
/// Underlying backend error /// Underlying backend error
#[error("Underlying backend error: {0:?}")] #[error("Underlying backend error: {0:?}")]
Underlying(#[source] U), Underlying(#[source] U),
} }
type Arguments = (crtc::Handle, Mode, Vec<connector::Handle>);
/// Representation of an egl device to create egl rendering surfaces /// Representation of an egl device to create egl rendering surfaces
pub struct EglDevice<B, D> pub struct EglDevice<B, D>
where where
B: Backend<Surface = <D as Device>::Surface> + 'static, B: Backend<Surface = <D as Device>::Surface> + 'static,
D: Device + NativeDisplay<B, Arguments = crtc::Handle> + 'static, D: Device
+ NativeDisplay<B, Arguments = Arguments, Error = <<D as Device>::Surface as Surface>::Error>
+ 'static,
<D as Device>::Surface: NativeSurface, <D as Device>::Surface: NativeSurface,
{ {
dev: EGLDisplay<B, D>, dev: EGLDisplay<B, D>,
@ -56,7 +63,9 @@ where
impl<B, D> AsRawFd for EglDevice<B, D> impl<B, D> AsRawFd for EglDevice<B, D>
where where
B: Backend<Surface = <D as Device>::Surface> + 'static, B: Backend<Surface = <D as Device>::Surface> + 'static,
D: Device + NativeDisplay<B, Arguments = crtc::Handle> + 'static, D: Device
+ NativeDisplay<B, Arguments = Arguments, Error = <<D as Device>::Surface as Surface>::Error>
+ 'static,
<D as Device>::Surface: NativeSurface, <D as Device>::Surface: NativeSurface,
{ {
fn as_raw_fd(&self) -> RawFd { fn as_raw_fd(&self) -> RawFd {
@ -67,7 +76,9 @@ where
impl<B, D> EglDevice<B, D> impl<B, D> EglDevice<B, D>
where where
B: Backend<Surface = <D as Device>::Surface> + 'static, B: Backend<Surface = <D as Device>::Surface> + 'static,
D: Device + NativeDisplay<B, Arguments = crtc::Handle> + 'static, D: Device
+ NativeDisplay<B, Arguments = Arguments, Error = <<D as Device>::Surface as Surface>::Error>
+ 'static,
<D as Device>::Surface: NativeSurface, <D as Device>::Surface: NativeSurface,
{ {
/// Try to create a new [`EglDevice`] from an open device. /// Try to create a new [`EglDevice`] from an open device.
@ -122,7 +133,9 @@ where
struct InternalDeviceHandler<B, D> struct InternalDeviceHandler<B, D>
where where
B: Backend<Surface = <D as Device>::Surface> + 'static, B: Backend<Surface = <D as Device>::Surface> + 'static,
D: Device + NativeDisplay<B, Arguments = crtc::Handle> + 'static, D: Device
+ NativeDisplay<B, Arguments = Arguments, Error = <<D as Device>::Surface as Surface>::Error>
+ 'static,
<D as Device>::Surface: NativeSurface, <D as Device>::Surface: NativeSurface,
{ {
handler: Box<dyn DeviceHandler<Device = EglDevice<B, D>> + 'static>, handler: Box<dyn DeviceHandler<Device = EglDevice<B, D>> + 'static>,
@ -131,7 +144,9 @@ where
impl<B, D> DeviceHandler for InternalDeviceHandler<B, D> impl<B, D> DeviceHandler for InternalDeviceHandler<B, D>
where where
B: Backend<Surface = <D as Device>::Surface> + 'static, B: Backend<Surface = <D as Device>::Surface> + 'static,
D: Device + NativeDisplay<B, Arguments = crtc::Handle> + 'static, D: Device
+ NativeDisplay<B, Arguments = Arguments, Error = <<D as Device>::Surface as Surface>::Error>
+ 'static,
<D as Device>::Surface: NativeSurface, <D as Device>::Surface: NativeSurface,
{ {
type Device = D; type Device = D;
@ -147,7 +162,9 @@ where
impl<B, D> Device for EglDevice<B, D> impl<B, D> Device for EglDevice<B, D>
where where
B: Backend<Surface = <D as Device>::Surface> + 'static, B: Backend<Surface = <D as Device>::Surface> + 'static,
D: Device + NativeDisplay<B, Arguments = crtc::Handle> + 'static, D: Device
+ NativeDisplay<B, Arguments = Arguments, Error = <<D as Device>::Surface as Surface>::Error>
+ 'static,
<D as Device>::Surface: NativeSurface, <D as Device>::Surface: NativeSurface,
{ {
type Surface = EglSurface<<D as Device>::Surface>; type Surface = EglSurface<<D as Device>::Surface>;
@ -169,6 +186,8 @@ where
fn create_surface( fn create_surface(
&mut self, &mut self,
crtc: crtc::Handle, crtc: crtc::Handle,
mode: Mode,
connectors: &[connector::Handle],
) -> Result<Self::Surface, <Self::Surface as Surface>::Error> { ) -> Result<Self::Surface, <Self::Surface as Surface>::Error> {
info!(self.logger, "Initializing EglSurface"); info!(self.logger, "Initializing EglSurface");
@ -182,9 +201,12 @@ where
context.get_pixel_format(), context.get_pixel_format(),
self.default_requirements.double_buffer, self.default_requirements.double_buffer,
context.get_config_id(), context.get_config_id(),
crtc, (crtc, mode, Vec::from(connectors)),
) )
.map_err(Error::EGL)?; .map_err(|err| match err {
SurfaceCreationError::EGLSurfaceCreationFailed(err) => Error::RawEGL(err),
SurfaceCreationError::NativeSurfaceCreationFailed(err) => Error::Underlying(err),
})?;
Ok(EglSurface { context, surface }) Ok(EglSurface { context, surface })
} }
@ -221,7 +243,9 @@ where
impl<B, D> EGLGraphicsBackend for EglDevice<B, D> impl<B, D> EGLGraphicsBackend for EglDevice<B, D>
where where
B: Backend<Surface = <D as Device>::Surface> + 'static, B: Backend<Surface = <D as Device>::Surface> + 'static,
D: Device + NativeDisplay<B, Arguments = crtc::Handle> + 'static, D: Device
+ NativeDisplay<B, Arguments = Arguments, Error = <<D as Device>::Surface as Surface>::Error>
+ 'static,
<D as Device>::Surface: NativeSurface, <D as Device>::Surface: NativeSurface,
{ {
fn bind_wl_display(&self, display: &Display) -> Result<EGLBufferReader, EGLError> { fn bind_wl_display(&self, display: &Display) -> Result<EGLBufferReader, EGLError> {
@ -232,7 +256,9 @@ where
impl<B, D> Drop for EglDevice<B, D> impl<B, D> Drop for EglDevice<B, D>
where where
B: Backend<Surface = <D as Device>::Surface> + 'static, B: Backend<Surface = <D as Device>::Surface> + 'static,
D: Device + NativeDisplay<B, Arguments = crtc::Handle> + 'static, D: Device
+ NativeDisplay<B, Arguments = Arguments, Error = <<D as Device>::Surface as Surface>::Error>
+ 'static,
<D as Device>::Surface: NativeSurface, <D as Device>::Surface: NativeSurface,
{ {
fn drop(&mut self) { fn drop(&mut self) {

View File

@ -3,11 +3,11 @@
//! to an open [`Session`](::backend::session::Session). //! to an open [`Session`](::backend::session::Session).
//! //!
use drm::control::crtc; use drm::control::{connector, crtc, Mode};
use std::os::unix::io::RawFd; use std::os::unix::io::RawFd;
use super::EglDevice; use super::EglDevice;
use crate::backend::drm::Device; use crate::backend::drm::{Device, Surface};
use crate::backend::egl::native::{Backend, NativeDisplay, NativeSurface}; use crate::backend::egl::native::{Backend, NativeDisplay, NativeSurface};
use crate::backend::session::{AsSessionObserver, SessionObserver}; use crate::backend::session::{AsSessionObserver, SessionObserver};
@ -22,7 +22,13 @@ impl<S, B, D> AsSessionObserver<EglDeviceObserver<S>> for EglDevice<B, D>
where where
S: SessionObserver + 'static, S: SessionObserver + 'static,
B: Backend<Surface = <D as Device>::Surface> + 'static, B: Backend<Surface = <D as Device>::Surface> + 'static,
D: Device + NativeDisplay<B, Arguments = crtc::Handle> + AsSessionObserver<S> + 'static, D: Device
+ NativeDisplay<
B,
Arguments = (crtc::Handle, Mode, Vec<connector::Handle>),
Error = <<D as Device>::Surface as Surface>::Error,
> + AsSessionObserver<S>
+ 'static,
<D as Device>::Surface: NativeSurface, <D as Device>::Surface: NativeSurface,
{ {
fn observer(&mut self) -> EglDeviceObserver<S> { fn observer(&mut self) -> EglDeviceObserver<S> {

View File

@ -1,5 +1,6 @@
use drm::control::{connector, crtc, Mode}; use drm::control::{connector, crtc, Mode};
use nix::libc::c_void; use nix::libc::c_void;
use std::convert::TryInto;
use super::Error; use super::Error;
use crate::backend::drm::Surface; use crate::backend::drm::Surface;
@ -22,7 +23,7 @@ where
impl<N> Surface for EglSurface<N> impl<N> Surface for EglSurface<N>
where where
N: NativeSurface + Surface, N: native::NativeSurface + Surface,
{ {
type Connectors = <N as Surface>::Connectors; type Connectors = <N as Surface>::Connectors;
type Error = Error<<N as Surface>::Error>; type Error = Error<<N as Surface>::Error>;
@ -53,15 +54,15 @@ where
self.surface.set_connectors(connectors).map_err(Error::Underlying) self.surface.set_connectors(connectors).map_err(Error::Underlying)
} }
fn current_mode(&self) -> Option<Mode> { fn current_mode(&self) -> Mode {
self.surface.current_mode() self.surface.current_mode()
} }
fn pending_mode(&self) -> Option<Mode> { fn pending_mode(&self) -> Mode {
self.surface.pending_mode() self.surface.pending_mode()
} }
fn use_mode(&self, mode: Option<Mode>) -> Result<(), Self::Error> { fn use_mode(&self, mode: Mode) -> Result<(), Self::Error> {
self.surface.use_mode(mode).map_err(Error::Underlying) self.surface.use_mode(mode).map_err(Error::Underlying)
} }
} }
@ -90,9 +91,17 @@ where
impl<N> GLGraphicsBackend for EglSurface<N> impl<N> GLGraphicsBackend for EglSurface<N>
where where
N: native::NativeSurface + Surface, N: native::NativeSurface + Surface,
<N as NativeSurface>::Error: Into<SwapBuffersError> + 'static,
{ {
fn swap_buffers(&self) -> ::std::result::Result<(), SwapBuffersError> { fn swap_buffers(&self) -> ::std::result::Result<(), SwapBuffersError> {
self.surface.swap_buffers() if let Err(err) = self.surface.swap_buffers() {
Err(match err.try_into() {
Ok(x) => x,
Err(x) => x.into(),
})
} else {
Ok(())
}
} }
fn get_proc_address(&self, symbol: &str) -> *const c_void { fn get_proc_address(&self, symbol: &str) -> *const c_void {
@ -100,7 +109,7 @@ where
} }
fn get_framebuffer_dimensions(&self) -> (u32, u32) { fn get_framebuffer_dimensions(&self) -> (u32, u32) {
let (w, h) = self.pending_mode().map(|mode| mode.size()).unwrap_or((1, 1)); let (w, h) = self.pending_mode().size();
(w as u32, h as u32) (w as u32, h as u32)
} }
@ -109,7 +118,9 @@ where
} }
unsafe fn make_current(&self) -> ::std::result::Result<(), SwapBuffersError> { unsafe fn make_current(&self) -> ::std::result::Result<(), SwapBuffersError> {
self.context.make_current_with_surface(&self.surface) self.context
.make_current_with_surface(&self.surface)
.map_err(Into::into)
} }
fn get_pixel_format(&self) -> PixelFormat { fn get_pixel_format(&self) -> PixelFormat {

View File

@ -7,12 +7,11 @@
use crate::backend::drm::{Device, RawDevice, Surface}; use crate::backend::drm::{Device, RawDevice, Surface};
use crate::backend::egl::ffi; use crate::backend::egl::ffi;
use crate::backend::egl::native::{Backend, NativeDisplay, NativeSurface}; use crate::backend::egl::native::{Backend, NativeDisplay, NativeSurface};
use crate::backend::egl::Error as EglError; use crate::backend::egl::{wrap_egl_call, EGLError, Error as EglBackendError};
use crate::backend::graphics::SwapBuffersError;
use super::{Error, GbmDevice, GbmSurface}; use super::{Error, GbmDevice, GbmSurface};
use drm::control::{crtc, Device as ControlDevice}; use drm::control::{connector, crtc, Device as ControlDevice, Mode};
use gbm::AsRaw; use gbm::AsRaw;
use std::marker::PhantomData; use std::marker::PhantomData;
use std::ptr; use std::ptr;
@ -31,44 +30,52 @@ impl<D: RawDevice + 'static> Backend for Gbm<D> {
display: ffi::NativeDisplayType, display: ffi::NativeDisplayType,
has_dp_extension: F, has_dp_extension: F,
log: ::slog::Logger, log: ::slog::Logger,
) -> ffi::egl::types::EGLDisplay ) -> Result<ffi::egl::types::EGLDisplay, EGLError>
where where
F: Fn(&str) -> bool, F: Fn(&str) -> bool,
{ {
if has_dp_extension("EGL_KHR_platform_gbm") && ffi::egl::GetPlatformDisplay::is_loaded() { if has_dp_extension("EGL_KHR_platform_gbm") && ffi::egl::GetPlatformDisplay::is_loaded() {
trace!(log, "EGL Display Initialization via EGL_KHR_platform_gbm"); trace!(log, "EGL Display Initialization via EGL_KHR_platform_gbm");
ffi::egl::GetPlatformDisplay(ffi::egl::PLATFORM_GBM_KHR, display as *mut _, ptr::null()) wrap_egl_call(|| {
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() { } else if has_dp_extension("EGL_MESA_platform_gbm") && ffi::egl::GetPlatformDisplayEXT::is_loaded() {
trace!(log, "EGL Display Initialization via EGL_MESA_platform_gbm"); trace!(log, "EGL Display Initialization via EGL_MESA_platform_gbm");
ffi::egl::GetPlatformDisplayEXT(ffi::egl::PLATFORM_GBM_MESA, display as *mut _, ptr::null()) wrap_egl_call(|| {
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() { } else if has_dp_extension("EGL_MESA_platform_gbm") && ffi::egl::GetPlatformDisplay::is_loaded() {
trace!(log, "EGL Display Initialization via EGL_MESA_platform_gbm"); trace!(log, "EGL Display Initialization via EGL_MESA_platform_gbm");
ffi::egl::GetPlatformDisplay(ffi::egl::PLATFORM_GBM_MESA, display as *mut _, ptr::null()) wrap_egl_call(|| {
ffi::egl::GetPlatformDisplay(ffi::egl::PLATFORM_GBM_MESA, display as *mut _, ptr::null())
})
} else { } else {
trace!(log, "Default EGL Display Initialization via GetDisplay"); trace!(log, "Default EGL Display Initialization via GetDisplay");
ffi::egl::GetDisplay(display as *mut _) wrap_egl_call(|| ffi::egl::GetDisplay(display as *mut _))
} }
} }
} }
unsafe impl<D: RawDevice + ControlDevice + 'static> NativeDisplay<Gbm<D>> for GbmDevice<D> { unsafe impl<D: RawDevice + ControlDevice + 'static> NativeDisplay<Gbm<D>> for GbmDevice<D> {
type Arguments = crtc::Handle; type Arguments = (crtc::Handle, Mode, Vec<connector::Handle>);
type Error = Error<<<D as Device>::Surface as Surface>::Error>; type Error = Error<<<D as Device>::Surface as Surface>::Error>;
fn is_backend(&self) -> bool { fn is_backend(&self) -> bool {
true true
} }
fn ptr(&self) -> Result<ffi::NativeDisplayType, EglError> { fn ptr(&self) -> Result<ffi::NativeDisplayType, EglBackendError> {
Ok(self.dev.borrow().as_raw() as *const _) Ok(self.dev.borrow().as_raw() as *const _)
} }
fn create_surface(&mut self, crtc: crtc::Handle) -> Result<GbmSurface<D>, Self::Error> { fn create_surface(&mut self, args: Self::Arguments) -> Result<GbmSurface<D>, Self::Error> {
Device::create_surface(self, crtc) Device::create_surface(self, args.0, args.1, &args.2)
} }
} }
unsafe impl<D: RawDevice + 'static> NativeSurface for GbmSurface<D> { unsafe impl<D: RawDevice + 'static> NativeSurface for GbmSurface<D> {
type Error = Error<<<D as RawDevice>::Surface as Surface>::Error>;
fn ptr(&self) -> ffi::NativeWindowType { fn ptr(&self) -> ffi::NativeWindowType {
self.0.surface.borrow().as_raw() as *const _ self.0.surface.borrow().as_raw() as *const _
} }
@ -77,16 +84,11 @@ unsafe impl<D: RawDevice + 'static> NativeSurface for GbmSurface<D> {
self.needs_recreation() self.needs_recreation()
} }
fn recreate(&self) -> bool { fn recreate(&self) -> Result<(), Self::Error> {
if let Err(err) = GbmSurface::recreate(self) { GbmSurface::recreate(self)
error!(self.0.logger, "Failure recreating internal resources: {}", err);
false
} else {
true
}
} }
fn swap_buffers(&self) -> ::std::result::Result<(), SwapBuffersError> { fn swap_buffers(&self) -> Result<(), Self::Error> {
// this is safe since `eglSwapBuffers` will have been called exactly once // this is safe since `eglSwapBuffers` will have been called exactly once
// if this is used by our egl module, which is why this trait is unsafe. // if this is used by our egl module, which is why this trait is unsafe.
unsafe { self.page_flip() } unsafe { self.page_flip() }

View File

@ -10,8 +10,9 @@
//! //!
use super::{Device, DeviceHandler, RawDevice, ResourceHandles, Surface}; use super::{Device, DeviceHandler, RawDevice, ResourceHandles, Surface};
use crate::backend::graphics::SwapBuffersError;
use drm::control::{connector, crtc, encoder, framebuffer, plane, Device as ControlDevice}; use drm::control::{connector, crtc, encoder, framebuffer, plane, Device as ControlDevice, Mode};
use drm::SystemError as DrmError; use drm::SystemError as DrmError;
use gbm::{self, BufferObjectFlags, Format as GbmFormat}; use gbm::{self, BufferObjectFlags, Format as GbmFormat};
use nix::libc::dev_t; use nix::libc::dev_t;
@ -26,25 +27,31 @@ use std::sync::Once;
/// Errors thrown by the [`GbmDevice`](::backend::drm::gbm::GbmDevice) /// Errors thrown by the [`GbmDevice`](::backend::drm::gbm::GbmDevice)
/// and [`GbmSurface`](::backend::drm::gbm::GbmSurface). /// and [`GbmSurface`](::backend::drm::gbm::GbmSurface).
#[derive(thiserror::Error, Debug)] #[derive(thiserror::Error, Debug)]
pub enum Error<U: std::error::Error + std::fmt::Debug + std::fmt::Display + 'static> { pub enum Error<U: std::error::Error + 'static> {
/// Creation of GBM device failed /// Creation of GBM device failed
#[error("Creation of GBM device failed")] #[error("Creation of GBM device failed")]
InitFailed(#[source] io::Error), InitFailed(#[source] io::Error),
/// Creation of GBM surface failed /// Creation of GBM surface failed
#[error("Creation of GBM surface failed")] #[error("Creation of GBM surface failed")]
SurfaceCreationFailed(#[source] io::Error), SurfaceCreationFailed(#[source] io::Error),
/// No mode is set, blocking the current operation
#[error("No mode is currently set")]
NoModeSet,
/// Creation of GBM buffer object failed /// Creation of GBM buffer object failed
#[error("Creation of GBM buffer object failed")] #[error("Creation of GBM buffer object failed")]
BufferCreationFailed(#[source] io::Error), BufferCreationFailed(#[source] io::Error),
/// Writing to GBM buffer failed /// Writing to GBM buffer failed
#[error("Writing to GBM buffer failed")] #[error("Writing to GBM buffer failed")]
BufferWriteFailed(#[source] io::Error), BufferWriteFailed(#[source] io::Error),
/// Creation of drm framebuffer failed
#[error("Creation of drm framebuffer failed")]
FramebufferCreationFailed(#[source] failure::Compat<drm::SystemError>),
/// Lock of GBM surface front buffer failed /// Lock of GBM surface front buffer failed
#[error("Lock of GBM surface font buffer failed")] #[error("Lock of GBM surface font buffer failed")]
FrontBufferLockFailed, FrontBufferLockFailed,
/// No additional buffers are available
#[error("No additional buffers are available. Did you swap twice?")]
FrontBuffersExhausted,
/// Internal state was modified
#[error("Internal state was modified. Did you change gbm userdata?")]
InvalidInternalState,
/// The GBM device was destroyed /// The GBM device was destroyed
#[error("The GBM device was destroyed")] #[error("The GBM device was destroyed")]
DeviceDestroyed, DeviceDestroyed,
@ -159,17 +166,16 @@ impl<D: RawDevice + ControlDevice + 'static> Device for GbmDevice<D> {
fn create_surface( fn create_surface(
&mut self, &mut self,
crtc: crtc::Handle, crtc: crtc::Handle,
mode: Mode,
connectors: &[connector::Handle],
) -> Result<GbmSurface<D>, Error<<<D as Device>::Surface as Surface>::Error>> { ) -> Result<GbmSurface<D>, Error<<<D as Device>::Surface as Surface>::Error>> {
info!(self.logger, "Initializing GbmSurface"); info!(self.logger, "Initializing GbmSurface");
let drm_surface = let drm_surface = Device::create_surface(&mut **self.dev.borrow_mut(), crtc, mode, connectors)
Device::create_surface(&mut **self.dev.borrow_mut(), crtc).map_err(Error::Underlying)?; .map_err(Error::Underlying)?;
// initialize the surface // initialize the surface
let (w, h) = drm_surface let (w, h) = drm_surface.pending_mode().size();
.pending_mode()
.map(|mode| mode.size())
.unwrap_or((1, 1));
let surface = self let surface = self
.dev .dev
.borrow() .borrow()
@ -249,3 +255,29 @@ impl<D: RawDevice + ControlDevice + 'static> Drop for GbmDevice<D> {
self.clear_handler(); self.clear_handler();
} }
} }
impl<E> Into<SwapBuffersError> for Error<E>
where
E: std::error::Error + Into<SwapBuffersError> + 'static,
{
fn into(self) -> SwapBuffersError {
match self {
Error::FrontBuffersExhausted => SwapBuffersError::AlreadySwapped,
Error::FramebufferCreationFailed(x)
if match x.get_ref() {
drm::SystemError::Unknown {
errno: nix::errno::Errno::EBUSY,
} => true,
drm::SystemError::Unknown {
errno: nix::errno::Errno::EINTR,
} => true,
_ => false,
} =>
{
SwapBuffersError::TemporaryFailure(Box::new(x))
}
Error::Underlying(x) => x.into(),
x => SwapBuffersError::ContextLost(Box::new(x)),
}
}
}

View File

@ -12,6 +12,7 @@ use std::rc::{Rc, Weak};
use super::{GbmDevice, GbmSurfaceInternal}; use super::{GbmDevice, GbmSurfaceInternal};
use crate::backend::drm::{RawDevice, RawSurface}; use crate::backend::drm::{RawDevice, RawSurface};
use crate::backend::graphics::CursorBackend;
use crate::backend::session::{AsSessionObserver, SessionObserver}; use crate::backend::session::{AsSessionObserver, SessionObserver};
/// [`SessionObserver`](SessionObserver) /// [`SessionObserver`](SessionObserver)
@ -27,11 +28,12 @@ pub struct GbmDeviceObserver<
} }
impl< impl<
S: SessionObserver + 'static, O: SessionObserver + 'static,
D: RawDevice + ::drm::control::Device + AsSessionObserver<S> + 'static, S: CursorBackend<CursorFormat = dyn drm::buffer::Buffer> + RawSurface + 'static,
> AsSessionObserver<GbmDeviceObserver<S, D>> for GbmDevice<D> D: RawDevice<Surface = S> + drm::control::Device + AsSessionObserver<O> + 'static,
> AsSessionObserver<GbmDeviceObserver<O, D>> for GbmDevice<D>
{ {
fn observer(&mut self) -> GbmDeviceObserver<S, D> { fn observer(&mut self) -> GbmDeviceObserver<O, D> {
GbmDeviceObserver { GbmDeviceObserver {
observer: (**self.dev.borrow_mut()).observer(), observer: (**self.dev.borrow_mut()).observer(),
backends: Rc::downgrade(&self.backends), backends: Rc::downgrade(&self.backends),
@ -41,9 +43,10 @@ impl<
} }
impl< impl<
S: SessionObserver + 'static, O: SessionObserver + 'static,
D: RawDevice + ::drm::control::Device + AsSessionObserver<S> + 'static, S: CursorBackend<CursorFormat = dyn drm::buffer::Buffer> + RawSurface + 'static,
> SessionObserver for GbmDeviceObserver<S, D> D: RawDevice<Surface = S> + drm::control::Device + AsSessionObserver<O> + 'static,
> SessionObserver for GbmDeviceObserver<O, D>
{ {
fn pause(&mut self, devnum: Option<(u32, u32)>) { fn pause(&mut self, devnum: Option<(u32, u32)>) {
self.observer.pause(devnum); self.observer.pause(devnum);
@ -56,20 +59,19 @@ impl<
for (crtc, backend) in backends.borrow().iter() { for (crtc, backend) in backends.borrow().iter() {
if let Some(backend) = backend.upgrade() { if let Some(backend) = backend.upgrade() {
// restart rendering loop, if it was previously running // restart rendering loop, if it was previously running
if let Some(fb) = backend.current_frame_buffer.get() { if let Some(current_fb) = backend.current_frame_buffer.get() {
if backend.crtc.page_flip(fb).is_err() { let result = if backend.crtc.commit_pending() {
// Try more! backend.crtc.commit(current_fb)
if let Err(err) = backend.recreate() { } else {
error!( RawSurface::page_flip(&backend.crtc, current_fb)
self.logger, };
"Failed to re-create gbm surface, is the device gone?\n\t{}", err
); if let Err(err) = result {
} warn!(
if let Err(err) = unsafe { backend.page_flip() } { self.logger,
warn!(self.logger, "Failed to restart rendering loop. Error: {}", err); "Failed to restart rendering loop. Re-creating resources. Error: {}", err
// TODO bubble this up the user somehow );
// maybe expose a "running" state from a surface? // TODO bubble up
}
} }
} }
@ -79,12 +81,7 @@ impl<
let &(ref cursor, ref hotspot): &(BufferObject<()>, (u32, u32)) = let &(ref cursor, ref hotspot): &(BufferObject<()>, (u32, u32)) =
unsafe { &*backend.cursor.as_ptr() }; unsafe { &*backend.cursor.as_ptr() };
if backend if backend.crtc.set_cursor_representation(cursor, *hotspot).is_err() {
.dev
.borrow()
.set_cursor2(*crtc, Some(cursor), ((*hotspot).0 as i32, (*hotspot).1 as i32))
.is_err()
{
if let Err(err) = backend.dev.borrow().set_cursor(*crtc, Some(cursor)) { if let Err(err) = backend.dev.borrow().set_cursor(*crtc, Some(cursor)) {
error!(self.logger, "Failed to reset cursor. Error: {}", err); error!(self.logger, "Failed to reset cursor. Error: {}", err);
} }

View File

@ -2,6 +2,7 @@ use super::super::{Device, RawDevice, RawSurface, Surface};
use super::Error; use super::Error;
use drm::control::{connector, crtc, framebuffer, Device as ControlDevice, Mode}; use drm::control::{connector, crtc, framebuffer, Device as ControlDevice, Mode};
use failure::ResultExt;
use gbm::{self, BufferObject, BufferObjectFlags, Format as GbmFormat, SurfaceBufferHandle}; use gbm::{self, BufferObject, BufferObjectFlags, Format as GbmFormat, SurfaceBufferHandle};
use image::{ImageBuffer, Rgba}; use image::{ImageBuffer, Rgba};
@ -9,7 +10,6 @@ use std::cell::{Cell, RefCell};
use std::rc::Rc; use std::rc::Rc;
use crate::backend::graphics::CursorBackend; use crate::backend::graphics::CursorBackend;
use crate::backend::graphics::SwapBuffersError;
pub(super) struct GbmSurfaceInternal<D: RawDevice + 'static> { pub(super) struct GbmSurfaceInternal<D: RawDevice + 'static> {
pub(super) dev: Rc<RefCell<gbm::Device<D>>>, pub(super) dev: Rc<RefCell<gbm::Device<D>>>,
@ -32,7 +32,7 @@ impl<D: RawDevice + 'static> GbmSurfaceInternal<D> {
// drop and release the old buffer // drop and release the old buffer
} }
pub unsafe fn page_flip(&self) -> ::std::result::Result<(), SwapBuffersError> { pub unsafe fn page_flip(&self) -> Result<(), Error<<<D as Device>::Surface as Surface>::Error>> {
let res = { let res = {
let nb = self.next_buffer.take(); let nb = self.next_buffer.take();
let res = nb.is_some(); let res = nb.is_some();
@ -42,7 +42,7 @@ impl<D: RawDevice + 'static> GbmSurfaceInternal<D> {
if res { if res {
// We cannot call lock_front_buffer anymore without releasing the previous buffer, which will happen when the page flip is done // 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"); warn!(self.logger, "Tried to swap with an already queued flip");
return Err(SwapBuffersError::AlreadySwapped); return Err(Error::FrontBuffersExhausted);
} }
// supporting only one buffer would cause a lot of inconvinience and // supporting only one buffer would cause a lot of inconvinience and
@ -53,13 +53,13 @@ impl<D: RawDevice + 'static> GbmSurfaceInternal<D> {
.surface .surface
.borrow() .borrow()
.lock_front_buffer() .lock_front_buffer()
.expect("Surface only has one front buffer. Not supported by smithay"); .map_err(|_| Error::FrontBufferLockFailed)?;
// create a framebuffer if the front buffer does not have one already // create a framebuffer if the front buffer does not have one already
// (they are reused by gbm) // (they are reused by gbm)
let maybe_fb = next_bo let maybe_fb = next_bo
.userdata() .userdata()
.map_err(|_| SwapBuffersError::ContextLost)? .map_err(|_| Error::InvalidInternalState)?
.cloned(); .cloned();
let fb = if let Some(info) = maybe_fb { let fb = if let Some(info) = maybe_fb {
info info
@ -67,7 +67,8 @@ impl<D: RawDevice + 'static> GbmSurfaceInternal<D> {
let fb = self let fb = self
.crtc .crtc
.add_planar_framebuffer(&*next_bo, &[0; 4], 0) .add_planar_framebuffer(&*next_bo, &[0; 4], 0)
.map_err(|_| SwapBuffersError::ContextLost)?; .compat()
.map_err(Error::FramebufferCreationFailed)?;
next_bo.set_userdata(fb).unwrap(); next_bo.set_userdata(fb).unwrap();
fb fb
}; };
@ -75,14 +76,11 @@ impl<D: RawDevice + 'static> GbmSurfaceInternal<D> {
if self.recreated.get() { if self.recreated.get() {
debug!(self.logger, "Commiting new state"); debug!(self.logger, "Commiting new state");
if let Err(err) = self.crtc.commit(fb) { self.crtc.commit(fb).map_err(Error::Underlying)?;
error!(self.logger, "Error commiting crtc: {}", err);
return Err(SwapBuffersError::ContextLost);
}
self.recreated.set(false); self.recreated.set(false);
} else { } else {
trace!(self.logger, "Queueing Page flip"); trace!(self.logger, "Queueing Page flip");
RawSurface::page_flip(&self.crtc, fb)?; RawSurface::page_flip(&self.crtc, fb).map_err(Error::Underlying)?;
} }
self.current_frame_buffer.set(Some(fb)); self.current_frame_buffer.set(Some(fb));
@ -91,11 +89,7 @@ impl<D: RawDevice + 'static> GbmSurfaceInternal<D> {
} }
pub fn recreate(&self) -> Result<(), Error<<<D as Device>::Surface as Surface>::Error>> { pub fn recreate(&self) -> Result<(), Error<<<D as Device>::Surface as Surface>::Error>> {
let (w, h) = self let (w, h) = self.pending_mode().size();
.pending_mode()
.or_else(|| self.current_mode())
.ok_or(Error::NoModeSet)?
.size();
// Recreate the surface and the related resources to match the new // Recreate the surface and the related resources to match the new
// resolution. // resolution.
@ -167,15 +161,15 @@ impl<D: RawDevice + 'static> Surface for GbmSurfaceInternal<D> {
self.crtc.set_connectors(connectors).map_err(Error::Underlying) self.crtc.set_connectors(connectors).map_err(Error::Underlying)
} }
fn current_mode(&self) -> Option<Mode> { fn current_mode(&self) -> Mode {
self.crtc.current_mode() self.crtc.current_mode()
} }
fn pending_mode(&self) -> Option<Mode> { fn pending_mode(&self) -> Mode {
self.crtc.pending_mode() self.crtc.pending_mode()
} }
fn use_mode(&self, mode: Option<Mode>) -> Result<(), Self::Error> { fn use_mode(&self, mode: Mode) -> Result<(), Self::Error> {
self.crtc.use_mode(mode).map_err(Error::Underlying) self.crtc.use_mode(mode).map_err(Error::Underlying)
} }
} }
@ -277,7 +271,7 @@ impl<D: RawDevice + 'static> GbmSurface<D> {
/// ///
/// When used in conjunction with an EGL context, this must be called exactly once /// When used in conjunction with an EGL context, this must be called exactly once
/// after page-flipping the associated context. /// after page-flipping the associated context.
pub unsafe fn page_flip(&self) -> ::std::result::Result<(), SwapBuffersError> { pub unsafe fn page_flip(&self) -> ::std::result::Result<(), <Self as Surface>::Error> {
self.0.page_flip() self.0.page_flip()
} }
@ -325,15 +319,15 @@ impl<D: RawDevice + 'static> Surface for GbmSurface<D> {
self.0.set_connectors(connectors) self.0.set_connectors(connectors)
} }
fn current_mode(&self) -> Option<Mode> { fn current_mode(&self) -> Mode {
self.0.current_mode() self.0.current_mode()
} }
fn pending_mode(&self) -> Option<Mode> { fn pending_mode(&self) -> Mode {
self.0.pending_mode() self.0.pending_mode()
} }
fn use_mode(&self, mode: Option<Mode>) -> Result<(), Self::Error> { fn use_mode(&self, mode: Mode) -> Result<(), Self::Error> {
self.0.use_mode(mode) self.0.use_mode(mode)
} }
} }

View File

@ -11,24 +11,24 @@
use super::{common::Error, DevPath, Device, DeviceHandler, RawDevice}; use super::{common::Error, DevPath, Device, DeviceHandler, RawDevice};
use drm::control::{ use drm::control::{
connector, crtc, encoder, framebuffer, plane, Device as ControlDevice, Event, ResourceHandles, connector, crtc, encoder, framebuffer, plane, Device as ControlDevice, Event, Mode, ResourceHandles,
}; };
use drm::{Device as BasicDevice, SystemError as DrmError}; use drm::{Device as BasicDevice, SystemError as DrmError};
use nix::libc::dev_t; use nix::libc::dev_t;
use nix::sys::stat::fstat; use nix::sys::stat::fstat;
use std::cell::RefCell; use std::cell::RefCell;
use std::collections::{HashMap, HashSet}; use std::collections::HashMap;
use std::os::unix::io::{AsRawFd, RawFd}; use std::os::unix::io::{AsRawFd, RawFd};
use std::rc::{Rc, Weak}; use std::rc::{Rc, Weak};
use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, RwLock}; use std::sync::Arc;
use failure::{Fail, ResultExt}; use failure::{Fail, ResultExt};
mod surface; mod surface;
pub use self::surface::LegacyDrmSurface; pub use self::surface::LegacyDrmSurface;
use self::surface::{LegacyDrmSurfaceInternal, State}; use self::surface::LegacyDrmSurfaceInternal;
#[cfg(feature = "backend_session")] #[cfg(feature = "backend_session")]
pub mod session; pub mod session;
@ -88,9 +88,21 @@ impl<A: AsRawFd + 'static> Drop for Dev<A> {
impl<A: AsRawFd + 'static> LegacyDrmDevice<A> { impl<A: AsRawFd + 'static> LegacyDrmDevice<A> {
/// Create a new [`LegacyDrmDevice`] from an open drm node /// 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 /// # Arguments
/// successful. ///
pub fn new<L>(dev: A, logger: L) -> Result<Self, Error> /// - `fd` - Open drm node
/// - `disable_connectors` - Setting this to true will initialize all connectors \
/// as disabled on device creation. smithay enables connectors, when attached \
/// to a surface, and disables them, when detached. Setting this to `false` \
/// requires usage of `drm-rs` to disable unused connectors to prevent them \
/// showing garbage, but will also prevent flickering of already turned on \
/// connectors (assuming you won't change the resolution).
/// - `logger` - Optional [`slog::Logger`] to be used by this device.
///
/// # Return
///
/// Returns an error if the file is no valid drm node or the device is not accessible.
pub fn new<L>(dev: A, disable_connectors: bool, logger: L) -> Result<Self, Error>
where where
L: Into<Option<::slog::Logger>>, L: Into<Option<::slog::Logger>>,
{ {
@ -151,6 +163,21 @@ impl<A: AsRawFd + 'static> LegacyDrmDevice<A> {
} }
} }
if disable_connectors {
dev.set_connector_state(res_handles.connectors().iter().copied(), false)?;
for crtc in res_handles.crtcs() {
// null commit
dev.set_crtc(*crtc, None, (0, 0), &[], None)
.compat()
.map_err(|source| Error::Access {
errmsg: "Error setting crtc",
dev: dev.dev_path(),
source,
})?;
}
}
Ok(LegacyDrmDevice { Ok(LegacyDrmDevice {
dev: Rc::new(dev), dev: Rc::new(dev),
dev_id, dev_id,
@ -162,6 +189,64 @@ impl<A: AsRawFd + 'static> LegacyDrmDevice<A> {
} }
} }
impl<A: AsRawFd + 'static> Dev<A> {
pub(in crate::backend::drm::legacy) fn set_connector_state(
&self,
connectors: impl Iterator<Item = connector::Handle>,
enabled: bool,
) -> Result<(), Error> {
for conn in connectors {
let info = self
.get_connector(conn)
.compat()
.map_err(|source| Error::Access {
errmsg: "Failed to get connector infos",
dev: self.dev_path(),
source,
})?;
if info.state() == connector::State::Connected {
let props = self
.get_properties(conn)
.compat()
.map_err(|source| Error::Access {
errmsg: "Failed to get properties for connector",
dev: self.dev_path(),
source,
})?;
let (handles, _) = props.as_props_and_values();
for handle in handles {
let info = self
.get_property(*handle)
.compat()
.map_err(|source| Error::Access {
errmsg: "Failed to get property of connector",
dev: self.dev_path(),
source,
})?;
if info.name().to_str().map(|x| x == "DPMS").unwrap_or(false) {
self.set_property(
conn,
*handle,
if enabled {
0 /*DRM_MODE_DPMS_ON*/
} else {
3 /*DRM_MODE_DPMS_OFF*/
},
)
.compat()
.map_err(|source| Error::Access {
errmsg: "Failed to set property of connector",
dev: self.dev_path(),
source,
})?;
}
}
}
}
Ok(())
}
}
impl<A: AsRawFd + 'static> AsRawFd for LegacyDrmDevice<A> { impl<A: AsRawFd + 'static> AsRawFd for LegacyDrmDevice<A> {
fn as_raw_fd(&self) -> RawFd { fn as_raw_fd(&self) -> RawFd {
self.dev.as_raw_fd() self.dev.as_raw_fd()
@ -186,7 +271,12 @@ impl<A: AsRawFd + 'static> Device for LegacyDrmDevice<A> {
let _ = self.handler.take(); let _ = self.handler.take();
} }
fn create_surface(&mut self, crtc: crtc::Handle) -> Result<LegacyDrmSurface<A>, Error> { fn create_surface(
&mut self,
crtc: crtc::Handle,
mode: Mode,
connectors: &[connector::Handle],
) -> Result<LegacyDrmSurface<A>, Error> {
if self.backends.borrow().contains_key(&crtc) { if self.backends.borrow().contains_key(&crtc) {
return Err(Error::CrtcAlreadyInUse(crtc)); return Err(Error::CrtcAlreadyInUse(crtc));
} }
@ -195,52 +285,17 @@ impl<A: AsRawFd + 'static> Device for LegacyDrmDevice<A> {
return Err(Error::DeviceInactive); return Err(Error::DeviceInactive);
} }
// Try to enumarate the current state to set the initial state variable correctly if connectors.is_empty() {
return Err(Error::SurfaceWithoutConnectors(crtc));
let crtc_info = self.get_crtc(crtc).compat().map_err(|source| Error::Access {
errmsg: "Error loading crtc info",
dev: self.dev_path(),
source,
})?;
let mode = crtc_info.mode();
let mut connectors = HashSet::new();
let res_handles = ControlDevice::resource_handles(self)
.compat()
.map_err(|source| Error::Access {
errmsg: "Error loading drm resources",
dev: self.dev_path(),
source,
})?;
for &con in res_handles.connectors() {
let con_info = self.get_connector(con).compat().map_err(|source| Error::Access {
errmsg: "Error loading connector info",
dev: self.dev_path(),
source,
})?;
if let Some(enc) = con_info.current_encoder() {
let enc_info = self.get_encoder(enc).compat().map_err(|source| Error::Access {
errmsg: "Error loading encoder info",
dev: self.dev_path(),
source,
})?;
if let Some(current_crtc) = enc_info.crtc() {
if crtc == current_crtc {
connectors.insert(con);
}
}
}
} }
let state = State { mode, connectors }; let backend = Rc::new(LegacyDrmSurfaceInternal::new(
let backend = Rc::new(LegacyDrmSurfaceInternal { self.dev.clone(),
dev: self.dev.clone(),
crtc, crtc,
state: RwLock::new(state.clone()), mode,
pending: RwLock::new(state), connectors,
logger: self.logger.new(o!("crtc" => format!("{:?}", crtc))), self.logger.new(o!("crtc" => format!("{:?}", crtc))),
}); )?);
self.backends.borrow_mut().insert(crtc, Rc::downgrade(&backend)); self.backends.borrow_mut().insert(crtc, Rc::downgrade(&backend));
Ok(LegacyDrmSurface(backend)) Ok(LegacyDrmSurface(backend))

View File

@ -5,16 +5,17 @@
use drm::control::{crtc, Device as ControlDevice}; use drm::control::{crtc, Device as ControlDevice};
use drm::Device as BasicDevice; use drm::Device as BasicDevice;
use failure::ResultExt;
use nix::libc::dev_t; use nix::libc::dev_t;
use nix::sys::stat; use nix::sys::stat;
use std::cell::RefCell; use std::cell::RefCell;
use std::collections::HashMap; use std::collections::{HashMap, HashSet};
use std::os::unix::io::{AsRawFd, RawFd}; use std::os::unix::io::{AsRawFd, RawFd};
use std::rc::{Rc, Weak}; use std::rc::{Rc, Weak};
use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc; use std::sync::Arc;
use super::{Dev, LegacyDrmDevice, LegacyDrmSurfaceInternal}; use super::{Dev, DevPath, Error, LegacyDrmDevice, LegacyDrmSurfaceInternal};
use crate::backend::session::{AsSessionObserver, SessionObserver}; use crate::backend::session::{AsSessionObserver, SessionObserver};
/// [`SessionObserver`](SessionObserver) /// [`SessionObserver`](SessionObserver)
@ -91,5 +92,83 @@ impl<A: AsRawFd + 'static> SessionObserver for LegacyDrmDeviceObserver<A> {
} }
} }
} }
// okay, the previous session/whatever might left the drm devices in any state...
// lets fix that
if let Err(err) = self.reset_state() {
warn!(self.logger, "Unable to reset state after tty switch: {}", err);
// TODO call drm-handler::error
}
}
}
impl<A: AsRawFd + 'static> LegacyDrmDeviceObserver<A> {
fn reset_state(&mut self) -> Result<(), Error> {
// lets enumerate it the current state
if let Some(dev) = self.dev.upgrade() {
let res_handles = ControlDevice::resource_handles(&*dev)
.compat()
.map_err(|source| Error::Access {
errmsg: "Error loading drm resources",
dev: dev.dev_path(),
source,
})?;
let mut used_connectors = HashSet::new();
if let Some(backends) = self.backends.upgrade() {
for surface in backends.borrow().values().filter_map(Weak::upgrade) {
let mut current = surface.state.write().unwrap();
let pending = surface.pending.read().unwrap();
// store (soon to be) used connectors
used_connectors.extend(pending.connectors.clone());
// set current connectors
current.connectors.clear();
for conn in res_handles.connectors() {
let conn_info =
dev.get_connector(*conn)
.compat()
.map_err(|source| Error::Access {
errmsg: "Could not load connector info",
dev: dev.dev_path(),
source,
})?;
if let Some(enc) = conn_info.current_encoder() {
let enc_info = dev.get_encoder(enc).compat().map_err(|source| Error::Access {
errmsg: "Could not load encoder info",
dev: dev.dev_path(),
source,
})?;
if enc_info.crtc().map(|crtc| crtc == surface.crtc).unwrap_or(false) {
current.connectors.insert(*conn);
}
}
}
// set current mode
let crtc_info = dev
.get_crtc(surface.crtc)
.compat()
.map_err(|source| Error::Access {
errmsg: "Could not load crtc info",
dev: dev.dev_path(),
source,
})?;
// If we have no current mode, we create a fake one, which will not match (and thus gets overriden on the commit below).
// A better fix would probably be making mode an `Option`, but that would mean
// we need to be sure, we require a mode to always be set without relying on the compiler.
// So we cheat, because it works and is easier to handle later.
current.mode = crtc_info.mode().unwrap_or_else(|| unsafe { std::mem::zeroed() });
}
}
// Disable unused connectors
let all_set = res_handles.connectors().iter().copied().collect::<HashSet<_>>();
let unused = used_connectors.difference(&all_set);
dev.set_connector_state(unused.copied(), false)?;
}
Ok(())
} }
} }

View File

@ -8,11 +8,10 @@ use drm::Device as BasicDevice;
use std::collections::HashSet; use std::collections::HashSet;
use std::os::unix::io::{AsRawFd, RawFd}; use std::os::unix::io::{AsRawFd, RawFd};
use std::rc::Rc; use std::rc::Rc;
use std::sync::RwLock; use std::sync::{atomic::Ordering, RwLock};
use crate::backend::drm::{common::Error, DevPath, RawSurface, Surface}; use crate::backend::drm::{common::Error, DevPath, RawSurface, Surface};
use crate::backend::graphics::CursorBackend; use crate::backend::graphics::CursorBackend;
use crate::backend::graphics::SwapBuffersError;
use super::Dev; use super::Dev;
@ -20,7 +19,7 @@ use failure::{Fail, ResultExt};
#[derive(Debug, PartialEq, Eq, Clone)] #[derive(Debug, PartialEq, Eq, Clone)]
pub struct State { pub struct State {
pub mode: Option<Mode>, pub mode: Mode,
pub connectors: HashSet<connector::Handle>, pub connectors: HashSet<connector::Handle>,
} }
@ -46,6 +45,10 @@ impl<A: AsRawFd + 'static> CursorBackend for LegacyDrmSurfaceInternal<A> {
type Error = Error; type Error = Error;
fn set_cursor_position(&self, x: u32, y: u32) -> Result<(), Error> { fn set_cursor_position(&self, x: u32, y: u32) -> Result<(), Error> {
if !self.dev.active.load(Ordering::SeqCst) {
return Err(Error::DeviceInactive);
}
trace!(self.logger, "Move the cursor to {},{}", x, y); trace!(self.logger, "Move the cursor to {},{}", x, y);
self.move_cursor(self.crtc, (x as i32, y as i32)) self.move_cursor(self.crtc, (x as i32, y as i32))
.compat() .compat()
@ -61,6 +64,10 @@ impl<A: AsRawFd + 'static> CursorBackend for LegacyDrmSurfaceInternal<A> {
buffer: &Self::CursorFormat, buffer: &Self::CursorFormat,
hotspot: (u32, u32), hotspot: (u32, u32),
) -> Result<(), Error> { ) -> Result<(), Error> {
if !self.dev.active.load(Ordering::SeqCst) {
return Err(Error::DeviceInactive);
}
trace!(self.logger, "Setting the new imported cursor"); trace!(self.logger, "Setting the new imported cursor");
if self if self
@ -96,18 +103,22 @@ impl<A: AsRawFd + 'static> Surface for LegacyDrmSurfaceInternal<A> {
self.pending.read().unwrap().connectors.clone() self.pending.read().unwrap().connectors.clone()
} }
fn current_mode(&self) -> Option<Mode> { fn current_mode(&self) -> Mode {
self.state.read().unwrap().mode self.state.read().unwrap().mode
} }
fn pending_mode(&self) -> Option<Mode> { fn pending_mode(&self) -> Mode {
self.pending.read().unwrap().mode self.pending.read().unwrap().mode
} }
fn add_connector(&self, conn: connector::Handle) -> Result<(), Error> { fn add_connector(&self, conn: connector::Handle) -> Result<(), Error> {
if !self.dev.active.load(Ordering::SeqCst) {
return Err(Error::DeviceInactive);
}
let mut pending = self.pending.write().unwrap(); let mut pending = self.pending.write().unwrap();
if self.check_connector(conn, pending.mode.as_ref().unwrap())? { if self.check_connector(conn, &pending.mode)? {
pending.connectors.insert(conn); pending.connectors.insert(conn);
} }
@ -115,16 +126,30 @@ impl<A: AsRawFd + 'static> Surface for LegacyDrmSurfaceInternal<A> {
} }
fn remove_connector(&self, connector: connector::Handle) -> Result<(), Error> { fn remove_connector(&self, connector: connector::Handle) -> Result<(), Error> {
self.pending.write().unwrap().connectors.remove(&connector); let mut pending = self.pending.write().unwrap();
if pending.connectors.contains(&connector) && pending.connectors.len() == 1 {
return Err(Error::SurfaceWithoutConnectors(self.crtc));
}
pending.connectors.remove(&connector);
Ok(()) Ok(())
} }
fn set_connectors(&self, connectors: &[connector::Handle]) -> Result<(), Self::Error> { fn set_connectors(&self, connectors: &[connector::Handle]) -> Result<(), Self::Error> {
if connectors.is_empty() {
return Err(Error::SurfaceWithoutConnectors(self.crtc));
}
if !self.dev.active.load(Ordering::SeqCst) {
return Err(Error::DeviceInactive);
}
let mut pending = self.pending.write().unwrap(); let mut pending = self.pending.write().unwrap();
if connectors if connectors
.iter() .iter()
.map(|conn| self.check_connector(*conn, pending.mode.as_ref().unwrap())) .map(|conn| self.check_connector(*conn, &pending.mode))
.collect::<Result<Vec<bool>, _>>()? .collect::<Result<Vec<bool>, _>>()?
.iter() .iter()
.all(|v| *v) .all(|v| *v)
@ -135,25 +160,27 @@ impl<A: AsRawFd + 'static> Surface for LegacyDrmSurfaceInternal<A> {
Ok(()) Ok(())
} }
fn use_mode(&self, mode: Option<Mode>) -> Result<(), Error> { fn use_mode(&self, mode: Mode) -> Result<(), Error> {
if !self.dev.active.load(Ordering::SeqCst) {
return Err(Error::DeviceInactive);
}
let mut pending = self.pending.write().unwrap(); let mut pending = self.pending.write().unwrap();
// check the connectors to see if this mode is supported // check the connectors to see if this mode is supported
if let Some(mode) = mode { for connector in &pending.connectors {
for connector in &pending.connectors { if !self
if !self .get_connector(*connector)
.get_connector(*connector) .compat()
.compat() .map_err(|source| Error::Access {
.map_err(|source| Error::Access { errmsg: "Error loading connector info",
errmsg: "Error loading connector info", dev: self.dev_path(),
dev: self.dev_path(), source,
source, })?
})? .modes()
.modes() .contains(&mode)
.contains(&mode) {
{ return Err(Error::ModeNotSuitable(mode));
return Err(Error::ModeNotSuitable(mode));
}
} }
} }
@ -169,6 +196,10 @@ impl<A: AsRawFd + 'static> RawSurface for LegacyDrmSurfaceInternal<A> {
} }
fn commit(&self, framebuffer: framebuffer::Handle) -> Result<(), Error> { fn commit(&self, framebuffer: framebuffer::Handle) -> Result<(), Error> {
if !self.dev.active.load(Ordering::SeqCst) {
return Err(Error::DeviceInactive);
}
let mut current = self.state.write().unwrap(); let mut current = self.state.write().unwrap();
let pending = self.pending.read().unwrap(); let pending = self.pending.read().unwrap();
@ -177,7 +208,7 @@ impl<A: AsRawFd + 'static> RawSurface for LegacyDrmSurfaceInternal<A> {
let added = pending.connectors.difference(&current.connectors); let added = pending.connectors.difference(&current.connectors);
let mut conn_removed = false; let mut conn_removed = false;
for conn in removed { for conn in removed.clone() {
if let Ok(info) = self.get_connector(*conn) { if let Ok(info) = self.get_connector(*conn) {
info!(self.logger, "Removing connector: {:?}", info.interface()); info!(self.logger, "Removing connector: {:?}", info.interface());
} else { } else {
@ -187,6 +218,7 @@ impl<A: AsRawFd + 'static> RawSurface for LegacyDrmSurfaceInternal<A> {
// the graphics pipeline will not be freed otherwise // the graphics pipeline will not be freed otherwise
conn_removed = true; conn_removed = true;
} }
self.dev.set_connector_state(removed.copied(), false)?;
if conn_removed { if conn_removed {
// We need to do a null commit to free graphics pipelines // We need to do a null commit to free graphics pipelines
@ -199,20 +231,17 @@ impl<A: AsRawFd + 'static> RawSurface for LegacyDrmSurfaceInternal<A> {
})?; })?;
} }
for conn in added { for conn in added.clone() {
if let Ok(info) = self.get_connector(*conn) { if let Ok(info) = self.get_connector(*conn) {
info!(self.logger, "Adding connector: {:?}", info.interface()); info!(self.logger, "Adding connector: {:?}", info.interface());
} else { } else {
info!(self.logger, "Adding unknown connector"); info!(self.logger, "Adding unknown connector");
} }
} }
self.dev.set_connector_state(added.copied(), true)?;
if current.mode != pending.mode { if current.mode != pending.mode {
info!( info!(self.logger, "Setting new mode: {:?}", pending.mode.name());
self.logger,
"Setting new mode: {:?}",
pending.mode.as_ref().unwrap().name()
);
} }
} }
@ -226,7 +255,7 @@ impl<A: AsRawFd + 'static> RawSurface for LegacyDrmSurfaceInternal<A> {
.iter() .iter()
.copied() .copied()
.collect::<Vec<connector::Handle>>(), .collect::<Vec<connector::Handle>>(),
pending.mode, Some(pending.mode),
) )
.compat() .compat()
.map_err(|source| Error::Access { .map_err(|source| Error::Access {
@ -251,9 +280,13 @@ impl<A: AsRawFd + 'static> RawSurface for LegacyDrmSurfaceInternal<A> {
}) })
} }
fn page_flip(&self, framebuffer: framebuffer::Handle) -> ::std::result::Result<(), SwapBuffersError> { fn page_flip(&self, framebuffer: framebuffer::Handle) -> Result<(), Error> {
trace!(self.logger, "Queueing Page flip"); trace!(self.logger, "Queueing Page flip");
if !self.dev.active.load(Ordering::SeqCst) {
return Err(Error::DeviceInactive);
}
ControlDevice::page_flip( ControlDevice::page_flip(
self, self,
self.crtc, self.crtc,
@ -261,11 +294,84 @@ impl<A: AsRawFd + 'static> RawSurface for LegacyDrmSurfaceInternal<A> {
&[PageFlipFlags::PageFlipEvent], &[PageFlipFlags::PageFlipEvent],
None, None,
) )
.map_err(|_| SwapBuffersError::ContextLost) .compat()
.map_err(|source| Error::Access {
errmsg: "Failed to page flip",
dev: self.dev_path(),
source,
})
} }
} }
impl<A: AsRawFd + 'static> LegacyDrmSurfaceInternal<A> { impl<A: AsRawFd + 'static> LegacyDrmSurfaceInternal<A> {
pub(crate) fn new(
dev: Rc<Dev<A>>,
crtc: crtc::Handle,
mode: Mode,
connectors: &[connector::Handle],
logger: ::slog::Logger,
) -> Result<LegacyDrmSurfaceInternal<A>, Error> {
// Try to enumarate the current state to set the initial state variable correctly
let crtc_info = dev.get_crtc(crtc).compat().map_err(|source| Error::Access {
errmsg: "Error loading crtc info",
dev: dev.dev_path(),
source,
})?;
let current_mode = crtc_info.mode();
let mut current_connectors = HashSet::new();
let res_handles = ControlDevice::resource_handles(&*dev)
.compat()
.map_err(|source| Error::Access {
errmsg: "Error loading drm resources",
dev: dev.dev_path(),
source,
})?;
for &con in res_handles.connectors() {
let con_info = dev.get_connector(con).compat().map_err(|source| Error::Access {
errmsg: "Error loading connector info",
dev: dev.dev_path(),
source,
})?;
if let Some(enc) = con_info.current_encoder() {
let enc_info = dev.get_encoder(enc).compat().map_err(|source| Error::Access {
errmsg: "Error loading encoder info",
dev: dev.dev_path(),
source,
})?;
if let Some(current_crtc) = enc_info.crtc() {
if crtc == current_crtc {
current_connectors.insert(con);
}
}
}
}
// If we have no current mode, we create a fake one, which will not match (and thus gets overriden on the commit below).
// A better fix would probably be making mode an `Option`, but that would mean
// we need to be sure, we require a mode to always be set without relying on the compiler.
// So we cheat, because it works and is easier to handle later.
let state = State {
mode: current_mode.unwrap_or_else(|| unsafe { std::mem::zeroed() }),
connectors: current_connectors,
};
let pending = State {
mode,
connectors: connectors.iter().copied().collect(),
};
let surface = LegacyDrmSurfaceInternal {
dev,
crtc,
state: RwLock::new(state),
pending: RwLock::new(pending),
logger,
};
Ok(surface)
}
fn check_connector(&self, conn: connector::Handle, mode: &Mode) -> Result<bool, Error> { fn check_connector(&self, conn: connector::Handle, mode: &Mode) -> Result<bool, Error> {
let info = self let info = self
.get_connector(conn) .get_connector(conn)
@ -319,7 +425,26 @@ impl<A: AsRawFd + 'static> LegacyDrmSurfaceInternal<A> {
impl<A: AsRawFd + 'static> Drop for LegacyDrmSurfaceInternal<A> { impl<A: AsRawFd + 'static> Drop for LegacyDrmSurfaceInternal<A> {
fn drop(&mut self) { fn drop(&mut self) {
// ignore failure at this point // ignore failure at this point
if !self.dev.active.load(Ordering::SeqCst) {
// the device is gone or we are on another tty
// old state has been restored, we shouldn't touch it.
// if we are on another tty the connectors will get disabled
// by the device, when switching back
return;
}
let _ = self.set_cursor(self.crtc, Option::<&DumbBuffer>::None); let _ = self.set_cursor(self.crtc, Option::<&DumbBuffer>::None);
// disable connectors again
let current = self.state.read().unwrap();
if self
.dev
.set_connector_state(current.connectors.iter().copied(), false)
.is_ok()
{
// null commit
let _ = self.set_crtc(self.crtc, None, (0, 0), &[], None);
}
} }
} }
@ -368,11 +493,11 @@ impl<A: AsRawFd + 'static> Surface for LegacyDrmSurface<A> {
self.0.pending_connectors() self.0.pending_connectors()
} }
fn current_mode(&self) -> Option<Mode> { fn current_mode(&self) -> Mode {
self.0.current_mode() self.0.current_mode()
} }
fn pending_mode(&self) -> Option<Mode> { fn pending_mode(&self) -> Mode {
self.0.pending_mode() self.0.pending_mode()
} }
@ -388,7 +513,7 @@ impl<A: AsRawFd + 'static> Surface for LegacyDrmSurface<A> {
self.0.set_connectors(connectors) self.0.set_connectors(connectors)
} }
fn use_mode(&self, mode: Option<Mode>) -> Result<(), Error> { fn use_mode(&self, mode: Mode) -> Result<(), Error> {
self.0.use_mode(mode) self.0.use_mode(mode)
} }
} }
@ -402,7 +527,7 @@ impl<A: AsRawFd + 'static> RawSurface for LegacyDrmSurface<A> {
self.0.commit(framebuffer) self.0.commit(framebuffer)
} }
fn page_flip(&self, framebuffer: framebuffer::Handle) -> ::std::result::Result<(), SwapBuffersError> { fn page_flip(&self, framebuffer: framebuffer::Handle) -> Result<(), Error> {
RawSurface::page_flip(&*self.0, framebuffer) RawSurface::page_flip(&*self.0, framebuffer)
} }
} }

View File

@ -48,8 +48,6 @@ use std::path::PathBuf;
use calloop::{generic::Generic, InsertError, LoopHandle, Source}; use calloop::{generic::Generic, InsertError, LoopHandle, Source};
use super::graphics::SwapBuffersError;
#[cfg(feature = "backend_drm_atomic")] #[cfg(feature = "backend_drm_atomic")]
pub mod atomic; pub mod atomic;
#[cfg(feature = "backend_drm")] #[cfg(feature = "backend_drm")]
@ -91,16 +89,23 @@ pub trait Device: AsRawFd + DevPath {
/// Creates a new rendering surface. /// Creates a new rendering surface.
/// ///
/// # Arguments
///
/// Initialization of surfaces happens through the types provided by /// Initialization of surfaces happens through the types provided by
/// [`drm-rs`](drm). /// [`drm-rs`](drm).
/// ///
/// [`crtc`](drm::control::crtc)s represent scanout engines /// - [`crtc`](drm::control::crtc)s represent scanout engines of the device pointing to one framebuffer. \
/// of the device pointer to one framebuffer. /// Their responsibility is to read the data of the framebuffer and export it into an "Encoder". \
/// Their responsibility is to read the data of the framebuffer and export it into an "Encoder". /// The number of crtc's represent the number of independant output devices the hardware may handle.
/// The number of crtc's represent the number of independant output devices the hardware may handle. /// - [`mode`](drm::control::Mode) describes the resolution and rate of images produced by the crtc and \
/// has to be compatible with the provided `connectors`.
/// - [`connectors`] - List of connectors driven by the crtc. At least one(!) connector needs to be \
/// attached to a crtc in smithay.
fn create_surface( fn create_surface(
&mut self, &mut self,
crtc: crtc::Handle, crtc: crtc::Handle,
mode: Mode,
connectors: &[connector::Handle],
) -> Result<Self::Surface, <Self::Surface as Surface>::Error>; ) -> Result<Self::Surface, <Self::Surface as Surface>::Error>;
/// Processes any open events of the underlying file descriptor. /// Processes any open events of the underlying file descriptor.
@ -161,6 +166,11 @@ pub trait Surface {
/// Tries to add a new [`connector`](drm::control::connector) /// Tries to add a new [`connector`](drm::control::connector)
/// to be used after the next commit. /// to be used after the next commit.
/// ///
/// **Warning**: You need to make sure, that the connector is not used with another surface
/// or was properly removed via `remove_connector` + `commit` before adding it to another surface.
/// Behavior if failing to do so is undefined, but might result in rendering errors or the connector
/// getting removed from the other surface without updating it's internal state.
///
/// Fails if the `connector` is not compatible with the underlying [`crtc`](drm::control::crtc) /// Fails if the `connector` is not compatible with the underlying [`crtc`](drm::control::crtc)
/// (e.g. no suitable [`encoder`](drm::control::encoder) may be found) /// (e.g. no suitable [`encoder`](drm::control::encoder) may be found)
/// or is not compatible with the currently pending /// or is not compatible with the currently pending
@ -178,11 +188,10 @@ pub trait Surface {
fn set_connectors(&self, connectors: &[connector::Handle]) -> Result<(), Self::Error>; fn set_connectors(&self, connectors: &[connector::Handle]) -> Result<(), Self::Error>;
/// Returns the currently active [`Mode`](drm::control::Mode) /// Returns the currently active [`Mode`](drm::control::Mode)
/// of the underlying [`crtc`](drm::control::crtc) /// of the underlying [`crtc`](drm::control::crtc)
/// if any. fn current_mode(&self) -> Mode;
fn current_mode(&self) -> Option<Mode>;
/// Returns the currently pending [`Mode`](drm::control::Mode) /// Returns the currently pending [`Mode`](drm::control::Mode)
/// to be used after the next commit, if any. /// to be used after the next commit.
fn pending_mode(&self) -> Option<Mode>; fn pending_mode(&self) -> Mode;
/// Tries to set a new [`Mode`](drm::control::Mode) /// Tries to set a new [`Mode`](drm::control::Mode)
/// to be used after the next commit. /// to be used after the next commit.
/// ///
@ -193,7 +202,7 @@ pub trait Surface {
/// *Note*: Only on a [`RawSurface`] you may directly trigger /// *Note*: Only on a [`RawSurface`] you may directly trigger
/// a [`commit`](RawSurface::commit). Other [`Surface`]s provide their /// a [`commit`](RawSurface::commit). Other [`Surface`]s provide their
/// own methods that *may* trigger a commit, you will need to read their docs. /// own methods that *may* trigger a commit, you will need to read their docs.
fn use_mode(&self, mode: Option<Mode>) -> Result<(), Self::Error>; fn use_mode(&self, mode: Mode) -> Result<(), Self::Error>;
} }
/// An open bare crtc without any rendering abstractions /// An open bare crtc without any rendering abstractions
@ -224,7 +233,7 @@ pub trait RawSurface: Surface + ControlDevice + BasicDevice {
/// This operation is not blocking and will produce a `vblank` event once swapping is done. /// This operation is not blocking and will produce a `vblank` event once swapping is done.
/// Make sure to [set a `DeviceHandler`](Device::set_handler) and /// Make sure to [set a `DeviceHandler`](Device::set_handler) and
/// [register the belonging `Device`](device_bind) before to receive the event in time. /// [register the belonging `Device`](device_bind) before to receive the event in time.
fn page_flip(&self, framebuffer: framebuffer::Handle) -> Result<(), SwapBuffersError>; fn page_flip(&self, framebuffer: framebuffer::Handle) -> Result<(), <Self as Surface>::Error>;
} }
/// Trait representing open devices that *may* return a `Path` /// Trait representing open devices that *may* return a `Path`

View File

@ -1,10 +1,10 @@
//! EGL context related structs //! EGL context related structs
use super::{ffi, Error}; use super::{ffi, wrap_egl_call, Error, MakeCurrentError};
use crate::backend::egl::display::{EGLDisplay, EGLDisplayHandle}; use crate::backend::egl::display::{EGLDisplay, EGLDisplayHandle};
use crate::backend::egl::native::NativeSurface; use crate::backend::egl::native::NativeSurface;
use crate::backend::egl::{native, EGLSurface}; use crate::backend::egl::{native, EGLSurface};
use crate::backend::graphics::{PixelFormat, SwapBuffersError}; use crate::backend::graphics::PixelFormat;
use std::os::raw::c_int; use std::os::raw::c_int;
use std::ptr; use std::ptr;
use std::sync::Arc; use std::sync::Arc;
@ -93,21 +93,15 @@ impl EGLContext {
trace!(log, "Creating EGL context..."); trace!(log, "Creating EGL context...");
// TODO: Support shared contexts // TODO: Support shared contexts
let context = unsafe { let context = wrap_egl_call(|| unsafe {
ffi::egl::CreateContext( ffi::egl::CreateContext(
**display.display, **display.display,
config_id, config_id,
ptr::null(), ptr::null(),
context_attributes.as_ptr(), context_attributes.as_ptr(),
) )
}; })
.map_err(Error::CreationFailed)?;
if context.is_null() {
match unsafe { ffi::egl::GetError() } as u32 {
ffi::egl::BAD_ATTRIBUTE => return Err(Error::CreationFailed),
err_no => return Err(Error::Unknown(err_no)),
}
}
info!(log, "EGL context created"); info!(log, "EGL context created");
@ -126,25 +120,15 @@ impl EGLContext {
/// ///
/// This function is marked unsafe, because the context cannot be made current /// This function is marked unsafe, because the context cannot be made current
/// on multiple threads. /// on multiple threads.
pub unsafe fn make_current_with_surface<N>( pub unsafe fn make_current_with_surface<N>(&self, surface: &EGLSurface<N>) -> Result<(), MakeCurrentError>
&self,
surface: &EGLSurface<N>,
) -> ::std::result::Result<(), SwapBuffersError>
where where
N: NativeSurface, N: NativeSurface,
{ {
let surface_ptr = surface.surface.get(); let surface_ptr = surface.surface.get();
let ret = ffi::egl::MakeCurrent(**self.display, surface_ptr, surface_ptr, self.context); wrap_egl_call(|| ffi::egl::MakeCurrent(**self.display, surface_ptr, surface_ptr, self.context))
.map(|_| ())
if ret == 0 { .map_err(Into::into)
match ffi::egl::GetError() as u32 {
ffi::egl::CONTEXT_LOST => Err(SwapBuffersError::ContextLost),
err => panic!("eglMakeCurrent failed (eglGetError returned 0x{:x})", err),
}
} else {
Ok(())
}
} }
/// Makes the OpenGL context the current context in the current thread with no surface bound. /// Makes the OpenGL context the current context in the current thread with no surface bound.
@ -152,18 +136,18 @@ impl EGLContext {
/// # Safety /// # Safety
/// ///
/// This function is marked unsafe, because the context cannot be made current /// This function is marked unsafe, because the context cannot be made current
/// on multiple threads. /// on multiple threads without being unbound again (see `unbind`)
pub unsafe fn make_current(&self) -> ::std::result::Result<(), SwapBuffersError> { pub unsafe fn make_current(&self) -> Result<(), MakeCurrentError> {
let ret = ffi::egl::MakeCurrent(**self.display, ptr::null(), ptr::null(), self.context); wrap_egl_call(|| {
ffi::egl::MakeCurrent(
if ret == 0 { **self.display,
match ffi::egl::GetError() as u32 { ffi::egl::NO_SURFACE,
ffi::egl::CONTEXT_LOST => Err(SwapBuffersError::ContextLost), ffi::egl::NO_SURFACE,
err => panic!("eglMakeCurrent failed (eglGetError returned 0x{:x})", err), self.context,
} )
} else { })
Ok(()) .map(|_| ())
} .map_err(Into::into)
} }
/// Returns true if the OpenGL context is the current one in the thread. /// Returns true if the OpenGL context is the current one in the thread.
@ -180,16 +164,31 @@ impl EGLContext {
pub fn get_pixel_format(&self) -> PixelFormat { pub fn get_pixel_format(&self) -> PixelFormat {
self.pixel_format self.pixel_format
} }
/// Unbinds this context from the current thread, if set.
///
/// This does nothing if this context is not the current context
pub fn unbind(&self) -> Result<(), MakeCurrentError> {
if self.is_current() {
wrap_egl_call(|| unsafe {
ffi::egl::MakeCurrent(
**self.display,
ffi::egl::NO_SURFACE,
ffi::egl::NO_SURFACE,
ffi::egl::NO_CONTEXT,
)
})?;
}
Ok(())
}
} }
impl Drop for EGLContext { impl Drop for EGLContext {
fn drop(&mut self) { fn drop(&mut self) {
unsafe { unsafe {
// We need to ensure the context is unbound, otherwise it egl stalls the destroy call // We need to ensure the context is unbound, otherwise it egl stalls the destroy call
if ffi::egl::GetCurrentContext() == self.context as *const _ { // ignore failures at this point
ffi::egl::MakeCurrent(ptr::null(), ptr::null(), ptr::null(), ptr::null()); let _ = self.unbind();
}
ffi::egl::DestroyContext(**self.display, self.context); ffi::egl::DestroyContext(**self.display, self.context);
} }
} }
@ -270,7 +269,7 @@ impl Default for PixelFormatRequirements {
impl PixelFormatRequirements { impl PixelFormatRequirements {
/// Append the requirements to the given attribute list /// Append the requirements to the given attribute list
pub fn create_attributes(&self, out: &mut Vec<c_int>, logger: &slog::Logger) -> Result<(), Error> { pub fn create_attributes(&self, out: &mut Vec<c_int>, logger: &slog::Logger) -> Result<(), ()> {
if let Some(hardware_accelerated) = self.hardware_accelerated { if let Some(hardware_accelerated) = self.hardware_accelerated {
out.push(ffi::egl::CONFIG_CAVEAT as c_int); out.push(ffi::egl::CONFIG_CAVEAT as c_int);
out.push(if hardware_accelerated { out.push(if hardware_accelerated {
@ -328,7 +327,7 @@ impl PixelFormatRequirements {
if self.stereoscopy { if self.stereoscopy {
error!(logger, "Stereoscopy is currently unsupported (sorry!)"); error!(logger, "Stereoscopy is currently unsupported (sorry!)");
return Err(Error::NoAvailablePixelFormat); return Err(());
} }
Ok(()) Ok(())

View File

@ -3,7 +3,8 @@
#[cfg(feature = "use_system_lib")] #[cfg(feature = "use_system_lib")]
use crate::backend::egl::EGLGraphicsBackend; use crate::backend::egl::EGLGraphicsBackend;
use crate::backend::egl::{ use crate::backend::egl::{
ffi, get_proc_address, native, BufferAccessError, EGLContext, EGLImages, EGLSurface, Error, Format, ffi, get_proc_address, native, wrap_egl_call, BufferAccessError, EGLContext, EGLError, EGLImages,
EGLSurface, Error, Format, SurfaceCreationError,
}; };
use std::sync::Arc; use std::sync::Arc;
@ -44,6 +45,7 @@ impl Deref for EGLDisplayHandle {
impl Drop for EGLDisplayHandle { impl Drop for EGLDisplayHandle {
fn drop(&mut self) { fn drop(&mut self) {
unsafe { unsafe {
// ignore errors on drop
ffi::egl::Terminate(self.handle); ffi::egl::Terminate(self.handle);
} }
} }
@ -93,7 +95,9 @@ impl<B: native::Backend, N: native::NativeDisplay<B>> EGLDisplay<B, N> {
// the first step is to query the list of extensions without any display, if supported // the first step is to query the list of extensions without any display, if supported
let dp_extensions = unsafe { let dp_extensions = unsafe {
let p = ffi::egl::QueryString(ffi::egl::NO_DISPLAY, ffi::egl::EXTENSIONS as i32); let p =
wrap_egl_call(|| ffi::egl::QueryString(ffi::egl::NO_DISPLAY, ffi::egl::EXTENSIONS as i32))
.map_err(Error::InitFailed)?;
// this possibility is available only with EGL 1.5 or EGL_EXT_platform_base, otherwise // this possibility is available only with EGL 1.5 or EGL_EXT_platform_base, otherwise
// `eglQueryString` returns an error // `eglQueryString` returns an error
@ -105,23 +109,22 @@ impl<B: native::Backend, N: native::NativeDisplay<B>> EGLDisplay<B, N> {
list.split(' ').map(|e| e.to_string()).collect::<Vec<_>>() list.split(' ').map(|e| e.to_string()).collect::<Vec<_>>()
} }
}; };
debug!(log, "EGL No-Display Extensions: {:?}", dp_extensions); debug!(log, "EGL No-Display Extensions: {:?}", dp_extensions);
let display = let display = unsafe {
unsafe { B::get_display(ptr, |e: &str| dp_extensions.iter().any(|s| s == e), log.clone()) }; B::get_display(ptr, |e: &str| dp_extensions.iter().any(|s| s == e), log.clone())
if display == ffi::egl::NO_DISPLAY { .map_err(Error::DisplayNotSupported)?
error!(log, "EGL Display is not valid"); };
return Err(Error::DisplayNotSupported);
}
let egl_version = { let egl_version = {
let mut major: MaybeUninit<ffi::egl::types::EGLint> = MaybeUninit::uninit(); let mut major: MaybeUninit<ffi::egl::types::EGLint> = MaybeUninit::uninit();
let mut minor: MaybeUninit<ffi::egl::types::EGLint> = MaybeUninit::uninit(); let mut minor: MaybeUninit<ffi::egl::types::EGLint> = MaybeUninit::uninit();
if unsafe { ffi::egl::Initialize(display, major.as_mut_ptr(), minor.as_mut_ptr()) } == 0 { wrap_egl_call(|| unsafe {
return Err(Error::InitFailed); ffi::egl::Initialize(display, major.as_mut_ptr(), minor.as_mut_ptr())
} })
.map_err(Error::InitFailed)?;
let major = unsafe { major.assume_init() }; let major = unsafe { major.assume_init() };
let minor = unsafe { minor.assume_init() }; let minor = unsafe { minor.assume_init() };
@ -134,19 +137,24 @@ impl<B: native::Backend, N: native::NativeDisplay<B>> EGLDisplay<B, N> {
// the list of extensions supported by the client once initialized is different from the // the list of extensions supported by the client once initialized is different from the
// list of extensions obtained earlier // list of extensions obtained earlier
let extensions = if egl_version >= (1, 2) { let extensions = if egl_version >= (1, 2) {
let p = unsafe { CStr::from_ptr(ffi::egl::QueryString(display, ffi::egl::EXTENSIONS as i32)) }; let p = unsafe {
CStr::from_ptr(
wrap_egl_call(|| ffi::egl::QueryString(display, ffi::egl::EXTENSIONS as i32))
.map_err(Error::InitFailed)?,
)
};
let list = String::from_utf8(p.to_bytes().to_vec()).unwrap_or_else(|_| String::new()); let list = String::from_utf8(p.to_bytes().to_vec()).unwrap_or_else(|_| String::new());
list.split(' ').map(|e| e.to_string()).collect::<Vec<_>>() list.split(' ').map(|e| e.to_string()).collect::<Vec<_>>()
} else { } else {
vec![] vec![]
}; };
info!(log, "EGL Extensions: {:?}", extensions); info!(log, "EGL Extensions: {:?}", extensions);
if egl_version >= (1, 2) && unsafe { ffi::egl::BindAPI(ffi::egl::OPENGL_ES_API) } == 0 { if egl_version >= (1, 2) {
error!(log, "OpenGLES not supported by the underlying EGL implementation"); return Err(Error::OpenGlesNotSupported(None));
return Err(Error::OpenGlesNotSupported);
} }
wrap_egl_call(|| unsafe { ffi::egl::BindAPI(ffi::egl::OPENGL_ES_API) })
.map_err(|source| Error::OpenGlesNotSupported(Some(source)))?;
Ok(EGLDisplay { Ok(EGLDisplay {
native: RefCell::new(native), native: RefCell::new(native),
@ -219,15 +227,16 @@ impl<B: native::Backend, N: native::NativeDisplay<B>> EGLDisplay<B, N> {
} }
}; };
reqs.create_attributes(&mut out, &self.logger)?; reqs.create_attributes(&mut out, &self.logger)
.map_err(|()| Error::NoAvailablePixelFormat)?;
out.push(ffi::egl::NONE as c_int); out.push(ffi::egl::NONE as c_int);
out out
}; };
// calling `eglChooseConfig` // calling `eglChooseConfig`
let mut num_configs = unsafe { std::mem::zeroed() }; let mut num_configs = 0;
if unsafe { wrap_egl_call(|| unsafe {
ffi::egl::ChooseConfig( ffi::egl::ChooseConfig(
**self.display, **self.display,
descriptor.as_ptr(), descriptor.as_ptr(),
@ -235,18 +244,14 @@ impl<B: native::Backend, N: native::NativeDisplay<B>> EGLDisplay<B, N> {
0, 0,
&mut num_configs, &mut num_configs,
) )
} == 0 })
{ .map_err(Error::ConfigFailed)?;
return Err(Error::ConfigFailed);
}
if num_configs == 0 { if num_configs == 0 {
return Err(Error::NoAvailablePixelFormat); return Err(Error::NoAvailablePixelFormat);
} }
let mut config_ids = Vec::with_capacity(num_configs as usize); let mut config_ids: Vec<ffi::egl::types::EGLConfig> = Vec::with_capacity(num_configs as usize);
config_ids.resize_with(num_configs as usize, || unsafe { std::mem::zeroed() }); wrap_egl_call(|| unsafe {
if unsafe {
ffi::egl::ChooseConfig( ffi::egl::ChooseConfig(
**self.display, **self.display,
descriptor.as_ptr(), descriptor.as_ptr(),
@ -254,9 +259,10 @@ impl<B: native::Backend, N: native::NativeDisplay<B>> EGLDisplay<B, N> {
num_configs, num_configs,
&mut num_configs, &mut num_configs,
) )
} == 0 })
{ .map_err(Error::ConfigFailed)?;
return Err(Error::ConfigFailed); unsafe {
config_ids.set_len(num_configs as usize);
} }
// TODO: Deeper swap intervals might have some uses // TODO: Deeper swap intervals might have some uses
@ -264,33 +270,41 @@ impl<B: native::Backend, N: native::NativeDisplay<B>> EGLDisplay<B, N> {
let config_ids = config_ids let config_ids = config_ids
.into_iter() .into_iter()
.filter(|&config| unsafe { .map(|config| unsafe {
let mut min_swap_interval = 0; let mut min_swap_interval = 0;
ffi::egl::GetConfigAttrib( wrap_egl_call(|| {
**self.display, ffi::egl::GetConfigAttrib(
config, **self.display,
ffi::egl::MIN_SWAP_INTERVAL as ffi::egl::types::EGLint, config,
&mut min_swap_interval, ffi::egl::MIN_SWAP_INTERVAL as ffi::egl::types::EGLint,
); &mut min_swap_interval,
)
})?;
if desired_swap_interval < min_swap_interval { if desired_swap_interval < min_swap_interval {
return false; return Ok(None);
} }
let mut max_swap_interval = 0; let mut max_swap_interval = 0;
ffi::egl::GetConfigAttrib( wrap_egl_call(|| {
**self.display, ffi::egl::GetConfigAttrib(
config, **self.display,
ffi::egl::MAX_SWAP_INTERVAL as ffi::egl::types::EGLint, config,
&mut max_swap_interval, ffi::egl::MAX_SWAP_INTERVAL as ffi::egl::types::EGLint,
); &mut max_swap_interval,
)
})?;
if desired_swap_interval > max_swap_interval { if desired_swap_interval > max_swap_interval {
return false; return Ok(None);
} }
true Ok(Some(config))
}) })
.collect::<Result<Vec<Option<ffi::egl::types::EGLConfig>>, EGLError>>()
.map_err(Error::ConfigFailed)?
.into_iter()
.flatten()
.collect::<Vec<_>>(); .collect::<Vec<_>>();
if config_ids.is_empty() { if config_ids.is_empty() {
@ -304,15 +318,15 @@ impl<B: native::Backend, N: native::NativeDisplay<B>> EGLDisplay<B, N> {
macro_rules! attrib { macro_rules! attrib {
($display:expr, $config:expr, $attr:expr) => {{ ($display:expr, $config:expr, $attr:expr) => {{
let mut value = MaybeUninit::uninit(); let mut value = MaybeUninit::uninit();
let res = ffi::egl::GetConfigAttrib( wrap_egl_call(|| {
**$display, ffi::egl::GetConfigAttrib(
$config, **$display,
$attr as ffi::egl::types::EGLint, $config,
value.as_mut_ptr(), $attr as ffi::egl::types::EGLint,
); value.as_mut_ptr(),
if res == 0 { )
return Err(Error::ConfigFailed); })
} .map_err(Error::ConfigFailed)?;
value.assume_init() value.assume_init()
}}; }};
}; };
@ -357,12 +371,13 @@ impl<B: native::Backend, N: native::NativeDisplay<B>> EGLDisplay<B, N> {
double_buffer: Option<bool>, double_buffer: Option<bool>,
config: ffi::egl::types::EGLConfig, config: ffi::egl::types::EGLConfig,
args: N::Arguments, args: N::Arguments,
) -> Result<EGLSurface<B::Surface>, Error> { ) -> Result<EGLSurface<B::Surface>, SurfaceCreationError<N::Error>> {
trace!(self.logger, "Creating EGL window surface."); trace!(self.logger, "Creating EGL window surface.");
let surface = self.native.borrow_mut().create_surface(args).map_err(|e| { let surface = self
error!(self.logger, "EGL surface creation failed: {}", e); .native
Error::SurfaceCreationFailed .borrow_mut()
})?; .create_surface(args)
.map_err(SurfaceCreationError::NativeSurfaceCreationFailed)?;
EGLSurface::new( EGLSurface::new(
self.display.clone(), self.display.clone(),
@ -376,6 +391,7 @@ impl<B: native::Backend, N: native::NativeDisplay<B>> EGLDisplay<B, N> {
debug!(self.logger, "EGL surface successfully created"); debug!(self.logger, "EGL surface successfully created");
x x
}) })
.map_err(SurfaceCreationError::EGLSurfaceCreationFailed)
} }
/// Returns the runtime egl version of this display /// Returns the runtime egl version of this display
@ -426,10 +442,10 @@ impl<B: native::Backend, N: native::NativeDisplay<B>> EGLGraphicsBackend for EGL
if !self.extensions.iter().any(|s| s == "EGL_WL_bind_wayland_display") { if !self.extensions.iter().any(|s| s == "EGL_WL_bind_wayland_display") {
return Err(Error::EglExtensionNotSupported(&["EGL_WL_bind_wayland_display"])); return Err(Error::EglExtensionNotSupported(&["EGL_WL_bind_wayland_display"]));
} }
let res = unsafe { ffi::egl::BindWaylandDisplayWL(**self.display, display.c_ptr() as *mut _) }; wrap_egl_call(|| unsafe {
if res == 0 { ffi::egl::BindWaylandDisplayWL(**self.display, display.c_ptr() as *mut _)
return Err(Error::OtherEGLDisplayAlreadyBound); })
} .map_err(Error::OtherEGLDisplayAlreadyBound)?;
Ok(EGLBufferReader::new(self.display.clone(), display.c_ptr())) Ok(EGLBufferReader::new(self.display.clone(), display.c_ptr()))
} }
} }
@ -469,16 +485,16 @@ impl EGLBufferReader {
buffer: WlBuffer, buffer: WlBuffer,
) -> ::std::result::Result<EGLImages, BufferAccessError> { ) -> ::std::result::Result<EGLImages, BufferAccessError> {
let mut format: i32 = 0; let mut format: i32 = 0;
if unsafe { wrap_egl_call(|| unsafe {
ffi::egl::QueryWaylandBufferWL( ffi::egl::QueryWaylandBufferWL(
**self.display, **self.display,
buffer.as_ref().c_ptr() as _, buffer.as_ref().c_ptr() as _,
ffi::egl::EGL_TEXTURE_FORMAT, ffi::egl::EGL_TEXTURE_FORMAT,
&mut format, &mut format,
) == 0 )
} { })
return Err(BufferAccessError::NotManaged(buffer)); .map_err(|source| BufferAccessError::NotManaged(buffer.clone(), source))?;
}
let format = match format { let format = match format {
x if x == ffi::egl::TEXTURE_RGB as i32 => Format::RGB, x if x == ffi::egl::TEXTURE_RGB as i32 => Format::RGB,
x if x == ffi::egl::TEXTURE_RGBA as i32 => Format::RGBA, x if x == ffi::egl::TEXTURE_RGBA as i32 => Format::RGBA,
@ -490,40 +506,37 @@ impl EGLBufferReader {
}; };
let mut width: i32 = 0; let mut width: i32 = 0;
if unsafe { wrap_egl_call(|| unsafe {
ffi::egl::QueryWaylandBufferWL( ffi::egl::QueryWaylandBufferWL(
**self.display, **self.display,
buffer.as_ref().c_ptr() as _, buffer.as_ref().c_ptr() as _,
ffi::egl::WIDTH as i32, ffi::egl::WIDTH as i32,
&mut width, &mut width,
) == 0 )
} { })
return Err(BufferAccessError::NotManaged(buffer)); .map_err(|source| BufferAccessError::NotManaged(buffer.clone(), source))?;
}
let mut height: i32 = 0; let mut height: i32 = 0;
if unsafe { wrap_egl_call(|| unsafe {
ffi::egl::QueryWaylandBufferWL( ffi::egl::QueryWaylandBufferWL(
**self.display, **self.display,
buffer.as_ref().c_ptr() as _, buffer.as_ref().c_ptr() as _,
ffi::egl::HEIGHT as i32, ffi::egl::HEIGHT as i32,
&mut height, &mut height,
) == 0 )
} { })
return Err(BufferAccessError::NotManaged(buffer)); .map_err(|source| BufferAccessError::NotManaged(buffer.clone(), source))?;
}
let mut inverted: i32 = 0; let mut inverted: i32 = 0;
if unsafe { wrap_egl_call(|| unsafe {
ffi::egl::QueryWaylandBufferWL( ffi::egl::QueryWaylandBufferWL(
**self.display, **self.display,
buffer.as_ref().c_ptr() as _, buffer.as_ref().c_ptr() as _,
ffi::egl::WAYLAND_Y_INVERTED_WL, ffi::egl::WAYLAND_Y_INVERTED_WL,
&mut inverted, &mut inverted,
) != 0 )
} { })
inverted = 1; .map_err(|source| BufferAccessError::NotManaged(buffer.clone(), source))?;
}
let mut images = Vec::with_capacity(format.num_planes()); let mut images = Vec::with_capacity(format.num_planes());
for i in 0..format.num_planes() { for i in 0..format.num_planes() {
@ -533,7 +546,7 @@ impl EGLBufferReader {
out.push(ffi::egl::NONE as i32); out.push(ffi::egl::NONE as i32);
images.push({ images.push({
let image = unsafe { wrap_egl_call(|| unsafe {
ffi::egl::CreateImageKHR( ffi::egl::CreateImageKHR(
**self.display, **self.display,
ffi::egl::NO_CONTEXT, ffi::egl::NO_CONTEXT,
@ -541,12 +554,8 @@ impl EGLBufferReader {
buffer.as_ref().c_ptr() as *mut _, buffer.as_ref().c_ptr() as *mut _,
out.as_ptr(), out.as_ptr(),
) )
}; })
if image == ffi::egl::NO_IMAGE_KHR { .map_err(BufferAccessError::EGLImageCreationFailed)?
return Err(BufferAccessError::EGLImageCreationFailed);
} else {
image
}
}); });
} }
@ -601,6 +610,7 @@ impl Drop for EGLBufferReader {
fn drop(&mut self) { fn drop(&mut self) {
if !self.wayland.is_null() { if !self.wayland.is_null() {
unsafe { unsafe {
// ignore errors on drop
ffi::egl::UnbindWaylandDisplayWL(**self.display, self.wayland as _); ffi::egl::UnbindWaylandDisplayWL(**self.display, self.wayland as _);
} }
} }

View File

@ -1,3 +1,5 @@
use super::ffi;
#[derive(thiserror::Error, Debug)] #[derive(thiserror::Error, Debug)]
/// EGL errors /// EGL errors
pub enum Error { pub enum Error {
@ -5,8 +7,8 @@ pub enum Error {
#[error("The requested OpenGL version {0:?} is not supported")] #[error("The requested OpenGL version {0:?} is not supported")]
OpenGlVersionNotSupported((u8, u8)), OpenGlVersionNotSupported((u8, u8)),
/// The EGL implementation does not support creating OpenGL ES contexts /// The EGL implementation does not support creating OpenGL ES contexts
#[error("The EGL implementation does not support creating OpenGL ES contexts")] #[error("The EGL implementation does not support creating OpenGL ES contexts. Err: {0:?}")]
OpenGlesNotSupported, OpenGlesNotSupported(#[source] Option<EGLError>),
/// No available pixel format matched the criteria /// No available pixel format matched the criteria
#[error("No available pixel format matched the criteria")] #[error("No available pixel format matched the criteria")]
NoAvailablePixelFormat, NoAvailablePixelFormat,
@ -14,26 +16,23 @@ pub enum Error {
#[error("The expected backend '{0:?}' does not match the runtime")] #[error("The expected backend '{0:?}' does not match the runtime")]
NonMatchingBackend(&'static str), NonMatchingBackend(&'static str),
/// Unable to obtain a valid EGL Display /// Unable to obtain a valid EGL Display
#[error("Unable to obtain a valid EGL Display")] #[error("Unable to obtain a valid EGL Display. Err: {0:}")]
DisplayNotSupported, DisplayNotSupported(#[source] EGLError),
/// `eglInitialize` returned an error /// `eglInitialize` returned an error
#[error("Failed to initialize EGL")] #[error("Failed to initialize EGL. Err: {0:}")]
InitFailed, InitFailed(#[source] EGLError),
/// Failed to configure the EGL context /// Failed to configure the EGL context
#[error("Failed to configure the EGL context")] #[error("Failed to configure the EGL context")]
ConfigFailed, ConfigFailed(#[source] EGLError),
/// Context creation failed as one or more requirements could not be met. Try removing some gl attributes or pixel format requirements /// Context creation failed as one or more requirements could not be met. Try removing some gl attributes or pixel format requirements
#[error("Context creation failed as one or more requirements could not be met. Try removing some gl attributes or pixel format requirements")] #[error("Context creation failed as one or more requirements could not be met. Try removing some gl attributes or pixel format requirements. Err: {0:}")]
CreationFailed, CreationFailed(#[source] EGLError),
/// `eglCreateWindowSurface` failed
#[error("`eglCreateWindowSurface` failed")]
SurfaceCreationFailed,
/// The required EGL extension is not supported by the underlying EGL implementation /// The required EGL extension is not supported by the underlying EGL implementation
#[error("None of the following EGL extensions is supported by the underlying EGL implementation, at least one is required: {0:?}")] #[error("None of the following EGL extensions is supported by the underlying EGL implementation, at least one is required: {0:?}")]
EglExtensionNotSupported(&'static [&'static str]), EglExtensionNotSupported(&'static [&'static str]),
/// Only one EGLDisplay may be bound to a given `WlDisplay` at any time /// Only one EGLDisplay may be bound to a given `WlDisplay` at any time
#[error("Only one EGLDisplay may be bound to a given `WlDisplay` at any time")] #[error("Only one EGLDisplay may be bound to a given `WlDisplay` at any time")]
OtherEGLDisplayAlreadyBound, OtherEGLDisplayAlreadyBound(#[source] EGLError),
/// No EGLDisplay is currently bound to this `WlDisplay` /// No EGLDisplay is currently bound to this `WlDisplay`
#[error("No EGLDisplay is currently bound to this `WlDisplay`")] #[error("No EGLDisplay is currently bound to this `WlDisplay`")]
NoEGLDisplayBound, NoEGLDisplayBound,
@ -43,7 +42,91 @@ pub enum Error {
/// Failed to create `EGLImages` from the buffer /// Failed to create `EGLImages` from the buffer
#[error("Failed to create `EGLImages` from the buffer")] #[error("Failed to create `EGLImages` from the buffer")]
EGLImageCreationFailed, EGLImageCreationFailed,
/// The reason of failure could not be determined }
#[error("Unknown error: {0}")]
/// Raw EGL error
#[derive(thiserror::Error, Debug)]
pub enum EGLError {
/// EGL is not initialized, or could not be initialized, for the specified EGL display connection.
#[error(
"EGL is not initialized, or could not be initialized, for the specified EGL display connection."
)]
NotInitialized,
/// EGL cannot access a requested resource (for example a context is bound in another thread).
#[error("EGL cannot access a requested resource (for example a context is bound in another thread).")]
BadAccess,
/// EGL failed to allocate resources for the requested operation.
#[error("EGL failed to allocate resources for the requested operation.")]
BadAlloc,
/// An unrecognized attribute or attribute value was passed in the attribute list.
#[error("An unrecognized attribute or attribute value was passed in the attribute list.")]
BadAttribute,
/// An EGLContext argument does not name a valid EGL rendering context.
#[error("An EGLContext argument does not name a valid EGL rendering context.")]
BadContext,
/// An EGLConfig argument does not name a valid EGL frame buffer configuration.
#[error("An EGLConfig argument does not name a valid EGL frame buffer configuration.")]
BadConfig,
/// The current surface of the calling thread is a window, pixel buffer or pixmap that is no longer valid.
#[error("The current surface of the calling thread is a window, pixel buffer or pixmap that is no longer valid.")]
BadCurrentSurface,
/// An EGLDisplay argument does not name a valid EGL display connection.
#[error("An EGLDisplay argument does not name a valid EGL display connection.")]
BadDisplay,
/// An EGLSurface argument does not name a valid surface (window, pixel buffer or pixmap) configured for GL rendering.
#[error("An EGLSurface argument does not name a valid surface (window, pixel buffer or pixmap) configured for GL rendering.")]
BadSurface,
/// Arguments are inconsistent (for example, a valid context requires buffers not supplied by a valid surface).
#[error("Arguments are inconsistent (for example, a valid context requires buffers not supplied by a valid surface).")]
BadMatch,
/// One or more argument values are invalid.
#[error("One or more argument values are invalid.")]
BadParameter,
/// A NativePixmapType argument does not refer to a valid native pixmap.
#[error("A NativePixmapType argument does not refer to a valid native pixmap.")]
BadNativePixmap,
/// A NativeWindowType argument does not refer to a valid native window.
#[error("A NativeWindowType argument does not refer to a valid native window.")]
BadNativeWindow,
/// A power management event has occurred. The application must destroy all contexts and reinitialise OpenGL ES state and objects to continue rendering.
#[error("A power management event has occurred. The application must destroy all contexts and reinitialise OpenGL ES state and objects to continue rendering.")]
ContextLost,
/// An unknown error
#[error("An unknown error ({0:x})")]
Unknown(u32), Unknown(u32),
} }
impl From<u32> for EGLError {
fn from(value: u32) -> Self {
match value {
ffi::egl::NOT_INITIALIZED => EGLError::NotInitialized,
ffi::egl::BAD_ACCESS => EGLError::BadAccess,
ffi::egl::BAD_ALLOC => EGLError::BadAlloc,
ffi::egl::BAD_ATTRIBUTE => EGLError::BadAttribute,
ffi::egl::BAD_CONTEXT => EGLError::BadContext,
ffi::egl::BAD_CURRENT_SURFACE => EGLError::BadCurrentSurface,
ffi::egl::BAD_DISPLAY => EGLError::BadDisplay,
ffi::egl::BAD_SURFACE => EGLError::BadSurface,
ffi::egl::BAD_MATCH => EGLError::BadMatch,
ffi::egl::BAD_PARAMETER => EGLError::BadParameter,
ffi::egl::BAD_NATIVE_PIXMAP => EGLError::BadNativePixmap,
ffi::egl::BAD_NATIVE_WINDOW => EGLError::BadNativeWindow,
ffi::egl::CONTEXT_LOST => EGLError::ContextLost,
x => EGLError::Unknown(x),
}
}
}
impl EGLError {
fn from_last_call() -> Result<(), EGLError> {
match unsafe { ffi::egl::GetError() as u32 } {
ffi::egl::SUCCESS => Ok(()),
x => Err(EGLError::from(x)),
}
}
}
pub(crate) fn wrap_egl_call<R, F: FnOnce() -> R>(call: F) -> Result<R, EGLError> {
let res = call();
EGLError::from_last_call().map(|()| res)
}

View File

@ -19,7 +19,10 @@
//! of an EGL-based [`WlBuffer`](wayland_server::protocol::wl_buffer::WlBuffer) for rendering. //! of an EGL-based [`WlBuffer`](wayland_server::protocol::wl_buffer::WlBuffer) for rendering.
#[cfg(feature = "renderer_gl")] #[cfg(feature = "renderer_gl")]
use crate::backend::graphics::gl::{ffi as gl_ffi, GLGraphicsBackend}; use crate::backend::graphics::{
gl::{ffi as gl_ffi, GLGraphicsBackend},
SwapBuffersError as GraphicsSwapBuffersError,
};
use nix::libc::c_uint; use nix::libc::c_uint;
use std::fmt; use std::fmt;
#[cfg(feature = "wayland_frontend")] #[cfg(feature = "wayland_frontend")]
@ -28,7 +31,7 @@ use wayland_server::{protocol::wl_buffer::WlBuffer, Display};
pub mod context; pub mod context;
pub use self::context::EGLContext; pub use self::context::EGLContext;
mod error; mod error;
pub use self::error::Error; pub use self::error::*;
use nix::libc::c_void; use nix::libc::c_void;
@ -84,11 +87,11 @@ pub enum BufferAccessError {
#[error("The corresponding context was lost")] #[error("The corresponding context was lost")]
ContextLost, ContextLost,
/// This buffer is not managed by the EGL buffer /// This buffer is not managed by the EGL buffer
#[error("This buffer is not managed by EGL")] #[error("This buffer is not managed by EGL. Err: {1:}")]
NotManaged(WlBuffer), NotManaged(WlBuffer, #[source] EGLError),
/// Failed to create `EGLImages` from the buffer /// Failed to create `EGLImages` from the buffer
#[error("Failed to create EGLImages from the buffer")] #[error("Failed to create EGLImages from the buffer. Err: {0:}")]
EGLImageCreationFailed, EGLImageCreationFailed(#[source] EGLError),
/// The required EGL extension is not supported by the underlying EGL implementation /// The required EGL extension is not supported by the underlying EGL implementation
#[error("{0}")] #[error("{0}")]
EglExtensionNotSupported(#[from] EglExtensionNotSupportedError), EglExtensionNotSupported(#[from] EglExtensionNotSupportedError),
@ -99,8 +102,8 @@ impl fmt::Debug for BufferAccessError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> ::std::result::Result<(), fmt::Error> { fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> ::std::result::Result<(), fmt::Error> {
match *self { match *self {
BufferAccessError::ContextLost => write!(formatter, "BufferAccessError::ContextLost"), BufferAccessError::ContextLost => write!(formatter, "BufferAccessError::ContextLost"),
BufferAccessError::NotManaged(_) => write!(formatter, "BufferAccessError::NotManaged"), BufferAccessError::NotManaged(_, _) => write!(formatter, "BufferAccessError::NotManaged"),
BufferAccessError::EGLImageCreationFailed => { BufferAccessError::EGLImageCreationFailed(_) => {
write!(formatter, "BufferAccessError::EGLImageCreationFailed") write!(formatter, "BufferAccessError::EGLImageCreationFailed")
} }
BufferAccessError::EglExtensionNotSupported(ref err) => write!(formatter, "{:?}", err), BufferAccessError::EglExtensionNotSupported(ref err) => write!(formatter, "{:?}", err),
@ -108,6 +111,88 @@ impl fmt::Debug for BufferAccessError {
} }
} }
/// Error that can occur when creating a surface.
#[derive(Debug, thiserror::Error)]
pub enum SurfaceCreationError<E: std::error::Error + 'static> {
/// Native Surface creation failed
#[error("Surface creation failed. Err: {0:}")]
NativeSurfaceCreationFailed(#[source] E),
/// EGL surface creation failed
#[error("EGL surface creation failed. Err: {0:}")]
EGLSurfaceCreationFailed(#[source] EGLError),
}
/// Error that can happen when swapping buffers.
#[derive(Debug, thiserror::Error)]
pub enum SwapBuffersError<E: std::error::Error + 'static> {
/// Error of the underlying native surface
#[error("Underlying error: {0:?}")]
Underlying(#[source] E),
/// EGL error during `eglSwapBuffers`
#[error("{0:}")]
EGLSwapBuffers(#[source] EGLError),
/// EGL error during `eglCreateWindowSurface`
#[error("{0:}")]
EGLCreateWindowSurface(#[source] EGLError),
}
impl<E: std::error::Error> std::convert::TryFrom<SwapBuffersError<E>> for GraphicsSwapBuffersError {
type Error = E;
fn try_from(value: SwapBuffersError<E>) -> Result<Self, Self::Error> {
match value {
// bad surface is answered with a surface recreation in `swap_buffers`
x @ SwapBuffersError::EGLSwapBuffers(EGLError::BadSurface) => {
Ok(GraphicsSwapBuffersError::TemporaryFailure(Box::new(x)))
}
// the rest is either never happening or are unrecoverable
x @ SwapBuffersError::EGLSwapBuffers(_) => Ok(GraphicsSwapBuffersError::ContextLost(Box::new(x))),
x @ SwapBuffersError::EGLCreateWindowSurface(_) => {
Ok(GraphicsSwapBuffersError::ContextLost(Box::new(x)))
}
SwapBuffersError::Underlying(e) => Err(e),
}
}
}
/// Error that can happen when making a context (and surface) current on the active thread.
#[derive(thiserror::Error, Debug)]
#[error("`eglMakeCurrent` failed: {0}")]
pub struct MakeCurrentError(#[from] EGLError);
impl From<MakeCurrentError> for GraphicsSwapBuffersError {
fn from(err: MakeCurrentError) -> GraphicsSwapBuffersError {
match err {
/*
From khronos docs:
If draw or read are not compatible with context, then an EGL_BAD_MATCH error is generated.
If context is current to some other thread, or if either draw or read are bound to contexts in another thread, an EGL_BAD_ACCESS error is generated.
If binding context would exceed the number of current contexts of that client API type supported by the implementation, an EGL_BAD_ACCESS error is generated.
If either draw or read are pbuffers created with eglCreatePbufferFromClientBuffer, and the underlying bound client API buffers are in use by the client API that created them, an EGL_BAD_ACCESS error is generated.
Except for the first case all of these recoverable. This conversation is mostly used in winit & EglSurface, where compatible context and surfaces are build.
*/
x @ MakeCurrentError(EGLError::BadAccess) => {
GraphicsSwapBuffersError::TemporaryFailure(Box::new(x))
}
// BadSurface would result in a recreation in `eglSwapBuffers` -> recoverable
x @ MakeCurrentError(EGLError::BadSurface) => {
GraphicsSwapBuffersError::TemporaryFailure(Box::new(x))
}
/*
From khronos docs:
If the previous context of the calling thread has unflushed commands, and the previous surface is no longer valid, an EGL_BAD_CURRENT_SURFACE error is generated.
This does not consern this or future `makeCurrent`-calls.
*/
x @ MakeCurrentError(EGLError::BadCurrentSurface) => {
GraphicsSwapBuffersError::TemporaryFailure(Box::new(x))
}
// the rest is either never happening or are unrecoverable
x => GraphicsSwapBuffersError::ContextLost(Box::new(x)),
}
}
}
/// Error that might happen when binding an `EGLImage` to a GL texture /// Error that might happen when binding an `EGLImage` to a GL texture
#[derive(Debug, Clone, PartialEq, thiserror::Error)] #[derive(Debug, Clone, PartialEq, thiserror::Error)]
pub enum TextureCreationError { pub enum TextureCreationError {
@ -249,6 +334,7 @@ impl EGLImages {
impl Drop for EGLImages { impl Drop for EGLImages {
fn drop(&mut self) { fn drop(&mut self) {
for image in self.images.drain(..) { for image in self.images.drain(..) {
// ignore result on drop
unsafe { unsafe {
ffi::egl::DestroyImageKHR(**self.display, image); ffi::egl::DestroyImageKHR(**self.display, image);
} }

View File

@ -1,7 +1,6 @@
//! Type safe native types for safe context/surface creation //! Type safe native types for safe context/surface creation
use super::{ffi, Error}; use super::{ffi, wrap_egl_call, EGLError, Error};
use crate::backend::graphics::SwapBuffersError;
#[cfg(feature = "backend_winit")] #[cfg(feature = "backend_winit")]
use std::ptr; use std::ptr;
@ -28,7 +27,7 @@ pub trait Backend {
display: ffi::NativeDisplayType, display: ffi::NativeDisplayType,
has_dp_extension: F, has_dp_extension: F,
log: ::slog::Logger, log: ::slog::Logger,
) -> ffi::egl::types::EGLDisplay; ) -> Result<ffi::egl::types::EGLDisplay, EGLError>;
} }
#[cfg(feature = "backend_winit")] #[cfg(feature = "backend_winit")]
@ -42,20 +41,28 @@ impl Backend for Wayland {
display: ffi::NativeDisplayType, display: ffi::NativeDisplayType,
has_dp_extension: F, has_dp_extension: F,
log: ::slog::Logger, log: ::slog::Logger,
) -> ffi::egl::types::EGLDisplay ) -> Result<ffi::egl::types::EGLDisplay, EGLError>
where where
F: Fn(&str) -> bool, F: Fn(&str) -> bool,
{ {
if has_dp_extension("EGL_KHR_platform_wayland") && ffi::egl::GetPlatformDisplay::is_loaded() { if has_dp_extension("EGL_KHR_platform_wayland") && ffi::egl::GetPlatformDisplay::is_loaded() {
trace!(log, "EGL Display Initialization via EGL_KHR_platform_wayland"); trace!(log, "EGL Display Initialization via EGL_KHR_platform_wayland");
ffi::egl::GetPlatformDisplay(ffi::egl::PLATFORM_WAYLAND_KHR, display as *mut _, ptr::null()) wrap_egl_call(|| {
ffi::egl::GetPlatformDisplay(ffi::egl::PLATFORM_WAYLAND_KHR, display as *mut _, ptr::null())
})
} else if has_dp_extension("EGL_EXT_platform_wayland") && ffi::egl::GetPlatformDisplayEXT::is_loaded() } else if has_dp_extension("EGL_EXT_platform_wayland") && ffi::egl::GetPlatformDisplayEXT::is_loaded()
{ {
trace!(log, "EGL Display Initialization via EGL_EXT_platform_wayland"); trace!(log, "EGL Display Initialization via EGL_EXT_platform_wayland");
ffi::egl::GetPlatformDisplayEXT(ffi::egl::PLATFORM_WAYLAND_EXT, display as *mut _, ptr::null()) wrap_egl_call(|| {
ffi::egl::GetPlatformDisplayEXT(
ffi::egl::PLATFORM_WAYLAND_EXT,
display as *mut _,
ptr::null(),
)
})
} else { } else {
trace!(log, "Default EGL Display Initialization via GetDisplay"); trace!(log, "Default EGL Display Initialization via GetDisplay");
ffi::egl::GetDisplay(display as *mut _) wrap_egl_call(|| ffi::egl::GetDisplay(display as *mut _))
} }
} }
} }
@ -74,19 +81,23 @@ impl Backend for X11 {
display: ffi::NativeDisplayType, display: ffi::NativeDisplayType,
has_dp_extension: F, has_dp_extension: F,
log: ::slog::Logger, log: ::slog::Logger,
) -> ffi::egl::types::EGLDisplay ) -> Result<ffi::egl::types::EGLDisplay, EGLError>
where where
F: Fn(&str) -> bool, F: Fn(&str) -> bool,
{ {
if has_dp_extension("EGL_KHR_platform_x11") && ffi::egl::GetPlatformDisplay::is_loaded() { if has_dp_extension("EGL_KHR_platform_x11") && ffi::egl::GetPlatformDisplay::is_loaded() {
trace!(log, "EGL Display Initialization via EGL_KHR_platform_x11"); trace!(log, "EGL Display Initialization via EGL_KHR_platform_x11");
ffi::egl::GetPlatformDisplay(ffi::egl::PLATFORM_X11_KHR, display as *mut _, ptr::null()) wrap_egl_call(|| {
ffi::egl::GetPlatformDisplay(ffi::egl::PLATFORM_X11_KHR, display as *mut _, ptr::null())
})
} else if has_dp_extension("EGL_EXT_platform_x11") && ffi::egl::GetPlatformDisplayEXT::is_loaded() { } else if has_dp_extension("EGL_EXT_platform_x11") && ffi::egl::GetPlatformDisplayEXT::is_loaded() {
trace!(log, "EGL Display Initialization via EGL_EXT_platform_x11"); trace!(log, "EGL Display Initialization via EGL_EXT_platform_x11");
ffi::egl::GetPlatformDisplayEXT(ffi::egl::PLATFORM_X11_EXT, display as *mut _, ptr::null()) wrap_egl_call(|| {
ffi::egl::GetPlatformDisplayEXT(ffi::egl::PLATFORM_X11_EXT, display as *mut _, ptr::null())
})
} else { } else {
trace!(log, "Default EGL Display Initialization via GetDisplay"); trace!(log, "Default EGL Display Initialization via GetDisplay");
ffi::egl::GetDisplay(display as *mut _) wrap_egl_call(|| ffi::egl::GetDisplay(display as *mut _))
} }
} }
} }
@ -107,7 +118,7 @@ pub unsafe trait NativeDisplay<B: Backend> {
/// Return a raw pointer EGL will accept for context creation. /// Return a raw pointer EGL will accept for context creation.
fn ptr(&self) -> Result<ffi::NativeDisplayType, Error>; fn ptr(&self) -> Result<ffi::NativeDisplayType, Error>;
/// Create a surface /// Create a surface
fn create_surface(&mut self, args: Self::Arguments) -> ::std::result::Result<B::Surface, Self::Error>; fn create_surface(&mut self, args: Self::Arguments) -> Result<B::Surface, Self::Error>;
} }
#[cfg(feature = "backend_winit")] #[cfg(feature = "backend_winit")]
@ -166,6 +177,9 @@ unsafe impl NativeDisplay<Wayland> for WinitWindow {
/// The returned [`NativeWindowType`](ffi::NativeWindowType) must be valid for EGL /// The returned [`NativeWindowType`](ffi::NativeWindowType) must be valid for EGL
/// and there is no way to test that. /// and there is no way to test that.
pub unsafe trait NativeSurface { pub unsafe trait NativeSurface {
/// Error of the underlying surface
type Error: std::error::Error;
/// Return a raw pointer egl will accept for surface creation. /// Return a raw pointer egl will accept for surface creation.
fn ptr(&self) -> ffi::NativeWindowType; fn ptr(&self) -> ffi::NativeWindowType;
@ -184,21 +198,35 @@ pub unsafe trait NativeSurface {
/// Must only be implemented if `needs_recreation` can return `true`. /// Must only be implemented if `needs_recreation` can return `true`.
/// Returns true on success. /// Returns true on success.
/// If this call was successful `ptr()` *should* return something different. /// If this call was successful `ptr()` *should* return something different.
fn recreate(&self) -> bool { fn recreate(&self) -> Result<(), Self::Error> {
true Ok(())
} }
/// Adds additional semantics when calling /// Adds additional semantics when calling
/// [EGLSurface::swap_buffers](::backend::egl::surface::EGLSurface::swap_buffers) /// [EGLSurface::swap_buffers](::backend::egl::surface::EGLSurface::swap_buffers)
/// ///
/// Only implement if required by the backend. /// Only implement if required by the backend.
fn swap_buffers(&self) -> ::std::result::Result<(), SwapBuffersError> { fn swap_buffers(&self) -> Result<(), Self::Error> {
Ok(()) Ok(())
} }
} }
/// Hack until ! gets stablized
#[derive(Debug)]
pub enum Never {}
impl std::fmt::Display for Never {
fn fmt(&self, _f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
unreachable!()
}
}
impl std::error::Error for Never {}
#[cfg(feature = "backend_winit")] #[cfg(feature = "backend_winit")]
unsafe impl NativeSurface for XlibWindow { unsafe impl NativeSurface for XlibWindow {
// this would really be a case for this:
// type Error = !; (https://github.com/rust-lang/rust/issues/35121)
type Error = Never;
fn ptr(&self) -> ffi::NativeWindowType { fn ptr(&self) -> ffi::NativeWindowType {
self.0 as *const _ self.0 as *const _
} }
@ -206,6 +234,9 @@ unsafe impl NativeSurface for XlibWindow {
#[cfg(feature = "backend_winit")] #[cfg(feature = "backend_winit")]
unsafe impl NativeSurface for wegl::WlEglSurface { unsafe impl NativeSurface for wegl::WlEglSurface {
// type Error = !;
type Error = Never;
fn ptr(&self) -> ffi::NativeWindowType { fn ptr(&self) -> ffi::NativeWindowType {
self.ptr() as *const _ self.ptr() as *const _
} }

View File

@ -1,8 +1,8 @@
//! EGL surface related structs //! EGL surface related structs
use super::{ffi, native, Error}; use super::{ffi, native, wrap_egl_call, EGLError, SwapBuffersError};
use crate::backend::egl::display::EGLDisplayHandle; use crate::backend::egl::display::EGLDisplayHandle;
use crate::backend::graphics::{PixelFormat, SwapBuffersError}; use crate::backend::graphics::PixelFormat;
use nix::libc::c_int; use nix::libc::c_int;
use std::sync::Arc; use std::sync::Arc;
use std::{ use std::{
@ -41,7 +41,7 @@ impl<N: native::NativeSurface> EGLSurface<N> {
config: ffi::egl::types::EGLConfig, config: ffi::egl::types::EGLConfig,
native: N, native: N,
log: L, log: L,
) -> Result<EGLSurface<N>, Error> ) -> Result<EGLSurface<N>, EGLError>
where where
L: Into<Option<::slog::Logger>>, L: Into<Option<::slog::Logger>>,
{ {
@ -68,13 +68,9 @@ impl<N: native::NativeSurface> EGLSurface<N> {
out out
}; };
let surface = unsafe { let surface = wrap_egl_call(|| unsafe {
ffi::egl::CreateWindowSurface(**display, config, native.ptr(), surface_attributes.as_ptr()) ffi::egl::CreateWindowSurface(**display, config, native.ptr(), surface_attributes.as_ptr())
}; })?;
if surface.is_null() {
return Err(Error::SurfaceCreationFailed);
}
Ok(EGLSurface { Ok(EGLSurface {
display, display,
@ -87,35 +83,43 @@ impl<N: native::NativeSurface> EGLSurface<N> {
} }
/// Swaps buffers at the end of a frame. /// Swaps buffers at the end of a frame.
pub fn swap_buffers(&self) -> ::std::result::Result<(), SwapBuffersError> { pub fn swap_buffers(&self) -> ::std::result::Result<(), SwapBuffersError<N::Error>> {
let surface = self.surface.get(); let surface = self.surface.get();
if !surface.is_null() { let result = if !surface.is_null() {
let ret = unsafe { ffi::egl::SwapBuffers(**self.display, surface as *const _) }; wrap_egl_call(|| unsafe { ffi::egl::SwapBuffers(**self.display, surface as *const _) })
.map_err(SwapBuffersError::EGLSwapBuffers)
if ret == 0 { .and_then(|_| self.native.swap_buffers().map_err(SwapBuffersError::Underlying))
match unsafe { ffi::egl::GetError() } as u32 { } else {
ffi::egl::CONTEXT_LOST => return Err(SwapBuffersError::ContextLost), Ok(())
err => return Err(SwapBuffersError::Unknown(err)),
};
} else {
self.native.swap_buffers()?;
}
}; };
if self.native.needs_recreation() || surface.is_null() { // workaround for missing `PartialEq` impl
self.native.recreate(); let is_bad_surface = if let Err(SwapBuffersError::EGLSwapBuffers(EGLError::BadSurface)) = result {
true
} else {
false
};
if self.native.needs_recreation() || surface.is_null() || is_bad_surface {
self.native.recreate().map_err(SwapBuffersError::Underlying)?;
if !surface.is_null() {
let _ = unsafe { ffi::egl::DestroySurface(**self.display, surface as *const _) };
}
self.surface.set(unsafe { self.surface.set(unsafe {
ffi::egl::CreateWindowSurface( wrap_egl_call(|| {
**self.display, ffi::egl::CreateWindowSurface(
self.config_id, **self.display,
self.native.ptr(), self.config_id,
self.surface_attributes.as_ptr(), self.native.ptr(),
) self.surface_attributes.as_ptr(),
)
})
.map_err(SwapBuffersError::EGLCreateWindowSurface)?
}); });
} }
Ok(()) result
} }
/// Returns true if the OpenGL surface is the current one in the thread. /// Returns true if the OpenGL surface is the current one in the thread.

View File

@ -4,35 +4,30 @@ use crate::backend::graphics::{gl::GLGraphicsBackend, SwapBuffersError};
use glium::{ use glium::{
backend::{Backend, Context, Facade}, backend::{Backend, Context, Facade},
debug::DebugCallbackBehavior, debug::DebugCallbackBehavior,
Frame, SwapBuffersError as GliumSwapBuffersError, SwapBuffersError as GliumSwapBuffersError,
}; };
use std::{ use std::{
cell::{Ref, RefCell, RefMut}, cell::{Cell, Ref, RefCell, RefMut},
os::raw::c_void, os::raw::c_void,
rc::Rc, rc::Rc,
}; };
impl From<SwapBuffersError> for GliumSwapBuffersError {
fn from(error: SwapBuffersError) -> Self {
match error {
SwapBuffersError::ContextLost => GliumSwapBuffersError::ContextLost,
SwapBuffersError::AlreadySwapped => GliumSwapBuffersError::AlreadySwapped,
SwapBuffersError::Unknown(_) => GliumSwapBuffersError::ContextLost, // TODO
}
}
}
/// Wrapper to expose `Glium` compatibility /// Wrapper to expose `Glium` compatibility
pub struct GliumGraphicsBackend<T: GLGraphicsBackend> { pub struct GliumGraphicsBackend<T: GLGraphicsBackend> {
context: Rc<Context>, context: Rc<Context>,
backend: Rc<InternalBackend<T>>, backend: Rc<InternalBackend<T>>,
// at least this type is not `Send` or even `Sync`.
// while there can be multiple Frames, they cannot in parallel call `set_finish`.
// so a buffer of the last error is sufficient, if always cleared...
error_channel: Rc<Cell<Option<Box<dyn std::error::Error>>>>,
} }
struct InternalBackend<T: GLGraphicsBackend>(RefCell<T>); struct InternalBackend<T: GLGraphicsBackend>(RefCell<T>, Rc<Cell<Option<Box<dyn std::error::Error>>>>);
impl<T: GLGraphicsBackend + 'static> GliumGraphicsBackend<T> { impl<T: GLGraphicsBackend + 'static> GliumGraphicsBackend<T> {
fn new(backend: T) -> GliumGraphicsBackend<T> { fn new(backend: T) -> GliumGraphicsBackend<T> {
let internal = Rc::new(InternalBackend(RefCell::new(backend))); let error_channel = Rc::new(Cell::new(None));
let internal = Rc::new(InternalBackend(RefCell::new(backend), error_channel.clone()));
GliumGraphicsBackend { GliumGraphicsBackend {
// cannot fail // cannot fail
@ -40,6 +35,7 @@ impl<T: GLGraphicsBackend + 'static> GliumGraphicsBackend<T> {
Context::new(internal.clone(), true, DebugCallbackBehavior::default()).unwrap() Context::new(internal.clone(), true, DebugCallbackBehavior::default()).unwrap()
}, },
backend: internal, backend: internal,
error_channel,
} }
} }
@ -51,7 +47,10 @@ impl<T: GLGraphicsBackend + 'static> GliumGraphicsBackend<T> {
/// Note that destroying a [`Frame`] is immediate, even if vsync is enabled. /// Note that destroying a [`Frame`] is immediate, even if vsync is enabled.
#[inline] #[inline]
pub fn draw(&self) -> Frame { pub fn draw(&self) -> Frame {
Frame::new(self.context.clone(), self.backend.get_framebuffer_dimensions()) Frame(
glium::Frame::new(self.context.clone(), self.backend.get_framebuffer_dimensions()),
self.error_channel.clone(),
)
} }
/// Borrow the underlying backend. /// Borrow the underlying backend.
@ -88,7 +87,22 @@ impl<T: GLGraphicsBackend + 'static> From<T> for GliumGraphicsBackend<T> {
unsafe impl<T: GLGraphicsBackend> Backend for InternalBackend<T> { unsafe impl<T: GLGraphicsBackend> Backend for InternalBackend<T> {
fn swap_buffers(&self) -> Result<(), GliumSwapBuffersError> { fn swap_buffers(&self) -> Result<(), GliumSwapBuffersError> {
self.0.borrow().swap_buffers().map_err(Into::into) if let Err(err) = self.0.borrow().swap_buffers() {
Err(match err {
SwapBuffersError::ContextLost(err) => {
self.1.set(Some(err));
GliumSwapBuffersError::ContextLost
}
SwapBuffersError::TemporaryFailure(err) => {
self.1.set(Some(err));
GliumSwapBuffersError::AlreadySwapped
}
// I do not think, this may happen, but why not
SwapBuffersError::AlreadySwapped => GliumSwapBuffersError::AlreadySwapped,
})
} else {
Ok(())
}
} }
unsafe fn get_proc_address(&self, symbol: &str) -> *const c_void { unsafe fn get_proc_address(&self, symbol: &str) -> *const c_void {
@ -104,6 +118,126 @@ unsafe impl<T: GLGraphicsBackend> Backend for InternalBackend<T> {
} }
unsafe fn make_current(&self) { unsafe fn make_current(&self) {
// TODO, if this ever blows up anvil, we should probably silently ignore this.
// But I have no idea, if that may happen or what glium does, if the context is not current...
// So lets leave this in to do some real world testing
self.0.borrow().make_current().expect("Context was lost") self.0.borrow().make_current().expect("Context was lost")
} }
} }
/// Omplementation of `glium::Surface`, targeting the default framebuffer.
///
/// The back- and front-buffers are swapped when you call `finish`.
///
/// You **must** call either `finish` or `set_finish` or else the destructor will panic.
pub struct Frame(glium::Frame, Rc<Cell<Option<Box<dyn std::error::Error>>>>);
impl Frame {
/// Stop drawing, swap the buffers, and consume the Frame.
///
/// See the documentation of [`SwapBuffersError`] about what is being returned.
pub fn finish(mut self) -> Result<(), SwapBuffersError> {
self.set_finish()
}
/// Stop drawing, swap the buffers.
///
/// The Frame can now be dropped regularly. Calling `finish()` or `set_finish()` again will cause `Err(SwapBuffersError::AlreadySwapped)` to be returned.
pub fn set_finish(&mut self) -> Result<(), SwapBuffersError> {
let res = self.0.set_finish();
let err = self.1.take();
match (res, err) {
(Ok(()), _) => Ok(()),
(Err(GliumSwapBuffersError::AlreadySwapped), Some(err)) => {
Err(SwapBuffersError::TemporaryFailure(err))
}
(Err(GliumSwapBuffersError::AlreadySwapped), None) => Err(SwapBuffersError::AlreadySwapped),
(Err(GliumSwapBuffersError::ContextLost), Some(err)) => Err(SwapBuffersError::ContextLost(err)),
_ => unreachable!(),
}
}
}
impl glium::Surface for Frame {
fn clear(
&mut self,
rect: Option<&glium::Rect>,
color: Option<(f32, f32, f32, f32)>,
color_srgb: bool,
depth: Option<f32>,
stencil: Option<i32>,
) {
self.0.clear(rect, color, color_srgb, depth, stencil)
}
fn get_dimensions(&self) -> (u32, u32) {
self.0.get_dimensions()
}
fn get_depth_buffer_bits(&self) -> Option<u16> {
self.0.get_depth_buffer_bits()
}
fn get_stencil_buffer_bits(&self) -> Option<u16> {
self.0.get_stencil_buffer_bits()
}
fn draw<'a, 'b, V, I, U>(
&mut self,
v: V,
i: I,
program: &glium::Program,
uniforms: &U,
draw_parameters: &glium::draw_parameters::DrawParameters<'_>,
) -> Result<(), glium::DrawError>
where
V: glium::vertex::MultiVerticesSource<'b>,
I: Into<glium::index::IndicesSource<'a>>,
U: glium::uniforms::Uniforms,
{
self.0.draw(v, i, program, uniforms, draw_parameters)
}
fn blit_from_frame(
&self,
source_rect: &glium::Rect,
target_rect: &glium::BlitTarget,
filter: glium::uniforms::MagnifySamplerFilter,
) {
self.0.blit_from_frame(source_rect, target_rect, filter);
}
fn blit_from_simple_framebuffer(
&self,
source: &glium::framebuffer::SimpleFrameBuffer<'_>,
source_rect: &glium::Rect,
target_rect: &glium::BlitTarget,
filter: glium::uniforms::MagnifySamplerFilter,
) {
self.0
.blit_from_simple_framebuffer(source, source_rect, target_rect, filter)
}
fn blit_from_multioutput_framebuffer(
&self,
source: &glium::framebuffer::MultiOutputFrameBuffer<'_>,
source_rect: &glium::Rect,
target_rect: &glium::BlitTarget,
filter: glium::uniforms::MagnifySamplerFilter,
) {
self.0
.blit_from_multioutput_framebuffer(source, source_rect, target_rect, filter)
}
fn blit_color<S>(
&self,
source_rect: &glium::Rect,
target: &S,
target_rect: &glium::BlitTarget,
filter: glium::uniforms::MagnifySamplerFilter,
) where
S: glium::Surface,
{
self.0.blit_color(source_rect, target, target_rect, filter)
}
}

View File

@ -16,24 +16,33 @@ pub mod glium;
pub mod software; pub mod software;
/// Error that can happen when swapping buffers. /// Error that can happen when swapping buffers.
#[derive(Debug, Clone, PartialEq, thiserror::Error)] #[derive(Debug, thiserror::Error)]
pub enum SwapBuffersError { pub enum SwapBuffersError {
/// The corresponding context has been lost and needs to be recreated.
///
/// All the objects associated to it (textures, buffers, programs, etc.)
/// need to be recreated from scratch.
///
/// Operations will have no effect. Functions that read textures, buffers, etc.
/// will return uninitialized data instead.
#[error("The context has been lost, it needs to be recreated")]
ContextLost,
/// The buffers have already been swapped. /// The buffers have already been swapped.
/// ///
/// This error can be returned when `swap_buffers` has been called multiple times /// This error can be returned when `swap_buffers` has been called multiple times
/// without any modification in between. /// without any modification in between.
#[error("Buffers are already swapped, swap_buffers was called too many times")] #[error("Buffers are already swapped, swap_buffers was called too many times")]
AlreadySwapped, AlreadySwapped,
/// Unknown error /// The corresponding context has been lost and needs to be recreated.
#[error("Unknown error: {0:x}")] ///
Unknown(u32), /// All the objects associated to it (textures, buffers, programs, etc.)
/// need to be recreated from scratch. Underlying resources like native surfaces
/// might also need to be recreated.
///
/// Operations will have no effect. Functions that read textures, buffers, etc.
/// will return uninitialized data instead.
#[error("The context has been lost, it needs to be recreated")]
ContextLost(Box<dyn std::error::Error>),
/// A temporary condition caused to rendering to fail.
///
/// Depending on the underlying error this *might* require fixing internal state of the rendering backend,
/// but failures mapped to `TemporaryFailure` are always recoverable without re-creating the entire stack,
/// as is represented by `ContextLost`.
///
/// Proceed after investigating the source to reschedule another full rendering step or just this page_flip at a later time.
/// If the root cause cannot be discovered and subsequent renderings also fail, it is advised to fallback to
/// recreation.
#[error("A temporary condition caused the page flip to fail.")]
TemporaryFailure(Box<dyn std::error::Error>),
} }

View File

@ -3,7 +3,7 @@
use crate::backend::egl::display::EGLDisplay; use crate::backend::egl::display::EGLDisplay;
use crate::backend::egl::get_proc_address; use crate::backend::egl::get_proc_address;
use crate::backend::{ use crate::backend::{
egl::{context::GlAttributes, native, EGLContext, EGLSurface, Error as EGLError}, egl::{context::GlAttributes, native, EGLContext, EGLSurface, Error as EGLError, SurfaceCreationError},
graphics::{gl::GLGraphicsBackend, CursorBackend, PixelFormat, SwapBuffersError}, graphics::{gl::GLGraphicsBackend, CursorBackend, PixelFormat, SwapBuffersError},
input::{ input::{
Axis, AxisSource, Event as BackendEvent, InputBackend, InputHandler, KeyState, KeyboardKeyEvent, Axis, AxisSource, Event as BackendEvent, InputBackend, InputHandler, KeyState, KeyboardKeyEvent,
@ -16,6 +16,7 @@ use nix::libc::c_void;
use std::{ use std::{
cell::{Ref, RefCell}, cell::{Ref, RefCell},
cmp, cmp,
convert::TryInto,
rc::Rc, rc::Rc,
time::Instant, time::Instant,
}; };
@ -47,6 +48,9 @@ pub enum Error {
/// EGL error /// EGL error
#[error("EGL error: {0}")] #[error("EGL error: {0}")]
EGL(#[from] EGLError), EGL(#[from] EGLError),
/// Surface Creation failed
#[error("Surface creation failed: {0}")]
SurfaceCreationError(#[from] SurfaceCreationError<EGLError>),
} }
enum Window { enum Window {
@ -277,8 +281,10 @@ impl GLGraphicsBackend for WinitGraphicsBackend {
fn swap_buffers(&self) -> ::std::result::Result<(), SwapBuffersError> { fn swap_buffers(&self) -> ::std::result::Result<(), SwapBuffersError> {
trace!(self.logger, "Swapping buffers"); trace!(self.logger, "Swapping buffers");
match *self.window { match *self.window {
Window::Wayland { ref surface, .. } => surface.swap_buffers(), Window::Wayland { ref surface, .. } => {
Window::X11 { ref surface, .. } => surface.swap_buffers(), surface.swap_buffers().map_err(|err| err.try_into().unwrap())
}
Window::X11 { ref surface, .. } => surface.swap_buffers().map_err(|err| err.try_into().unwrap()),
} }
} }
@ -314,12 +320,12 @@ impl GLGraphicsBackend for WinitGraphicsBackend {
ref surface, ref surface,
ref context, ref context,
.. ..
} => context.make_current_with_surface(surface), } => context.make_current_with_surface(surface).map_err(Into::into),
Window::X11 { Window::X11 {
ref surface, ref surface,
ref context, ref context,
.. ..
} => context.make_current_with_surface(surface), } => context.make_current_with_surface(surface).map_err(Into::into),
} }
} }