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,
index::PrimitiveType,
texture::{MipmapsOption, Texture2d, UncompressedFloatFormat},
Frame, GlObject, Surface,
GlObject, Surface,
};
use slog::Logger;
@ -16,7 +16,10 @@ use smithay::backend::egl::display::EGLBufferReader;
use smithay::{
backend::{
egl::{BufferAccessError, EGLImages, Format},
graphics::{gl::GLGraphicsBackend, glium::GliumGraphicsBackend},
graphics::{
gl::GLGraphicsBackend,
glium::{Frame, GliumGraphicsBackend},
},
},
reexports::wayland_server::protocol::{wl_buffer, wl_surface},
wayland::{
@ -159,7 +162,10 @@ impl<F: GLGraphicsBackend + 'static> GliumDrawer<F> {
let images = if let Some(display) = &self.egl_buffer_reader.borrow().as_ref() {
display.egl_buffer_contents(buffer)
} else {
Err(BufferAccessError::NotManaged(buffer))
Err(BufferAccessError::NotManaged(
buffer,
smithay::backend::egl::EGLError::BadDisplay,
))
};
match images {
Ok(images) => {
@ -193,7 +199,7 @@ impl<F: GLGraphicsBackend + 'static> GliumDrawer<F> {
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
self.texture_from_shm_buffer(buffer)
}
@ -235,7 +241,7 @@ impl<F: GLGraphicsBackend + 'static> GliumDrawer<F> {
pub fn render_texture(
&self,
target: &mut glium::Frame,
target: &mut Frame,
texture: &Texture2d,
texture_kind: usize,
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()) {
if let Entry::Vacant(entry) = backends.entry(crtc) {
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(),
logger.clone(),
);
@ -394,7 +396,7 @@ impl<S: SessionNotifier, Data: 'static> UdevHandlerImpl<S, Data> {
)
.ok()
.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),
Err(err) => {
error!(self.logger, "Skipping drm device, because of error: {}", err);

View File

@ -56,7 +56,8 @@ fn main() {
let mut options = OpenOptions::new();
options.read(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):
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.)
// Initialize the hardware backend
let surface = Rc::new(device.create_surface(crtc).unwrap());
surface.set_connectors(&[connector_info.handle()]).unwrap();
surface.use_mode(Some(mode)).unwrap();
let surface = Rc::new(
device
.create_surface(crtc, mode, &[connector_info.handle()])
.unwrap(),
);
for conn in surface.current_connectors().into_iter() {
if conn != connector_info.handle() {

View File

@ -9,7 +9,7 @@ use smithay::{
common::Error,
device_bind,
legacy::{LegacyDrmDevice, LegacyDrmSurface},
Device, DeviceHandler, RawSurface, Surface,
Device, DeviceHandler, RawSurface,
},
reexports::{
calloop::EventLoop,
@ -40,7 +40,8 @@ fn main() {
let mut options = OpenOptions::new();
options.read(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):
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.)
// 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.
* 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::{
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::{ClientCapability, Device as BasicDevice};
@ -187,9 +188,21 @@ impl<A: AsRawFd + 'static> Dev<A> {
impl<A: AsRawFd + 'static> AtomicDrmDevice<A> {
/// 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
/// successful.
pub fn new<L>(fd: A, logger: L) -> Result<Self, Error>
/// # Arguments
///
/// - `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
L: Into<Option<::slog::Logger>>,
{
@ -261,7 +274,49 @@ impl<A: AsRawFd + 'static> AtomicDrmDevice<A> {
dev.old_state = old_state;
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 {
dev: Rc::new(dev),
@ -298,7 +353,12 @@ impl<A: AsRawFd + 'static> Device for AtomicDrmDevice<A> {
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) {
return Err(Error::CrtcAlreadyInUse(crtc));
}
@ -307,9 +367,15 @@ impl<A: AsRawFd + 'static> Device for AtomicDrmDevice<A> {
return Err(Error::DeviceInactive);
}
if connectors.is_empty() {
return Err(Error::SurfaceWithoutConnectors(crtc));
}
let backend = Rc::new(AtomicDrmSurfaceInternal::new(
self.dev.clone(),
crtc,
mode,
connectors,
self.logger.new(o!("crtc" => format!("{:?}", crtc))),
)?);

View File

@ -3,8 +3,9 @@
//! 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 failure::ResultExt;
use nix::libc::dev_t;
use nix::sys::stat;
use std::cell::RefCell;
@ -15,6 +16,7 @@ use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use super::{AtomicDrmDevice, AtomicDrmSurfaceInternal, Dev};
use crate::backend::drm::{common::Error, DevPath};
use crate::backend::session::{AsSessionObserver, 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) {
warn!(
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);
// 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::control::atomic::AtomicModeReq;
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 std::cell::Cell;
use std::collections::HashSet;
use std::os::unix::io::{AsRawFd, RawFd};
use std::rc::Rc;
use std::sync::RwLock;
use std::sync::{atomic::Ordering, RwLock};
use failure::ResultExt as FailureResultExt;
use super::Dev;
use crate::backend::drm::{common::Error, DevPath, RawSurface, Surface};
use crate::backend::graphics::CursorBackend;
use crate::backend::graphics::SwapBuffersError;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CursorState {
@ -26,8 +27,8 @@ pub struct CursorState {
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct State {
pub mode: Option<Mode>,
pub blob: Option<property::Value<'static>>,
pub mode: Mode,
pub blob: property::Value<'static>,
pub connectors: HashSet<connector::Handle>,
}
@ -45,6 +46,7 @@ pub(super) struct AtomicDrmSurfaceInternal<A: AsRawFd + 'static> {
pub(super) state: RwLock<State>,
pub(super) pending: RwLock<State>,
pub(super) logger: ::slog::Logger,
pub(super) test_buffer: Cell<Option<(DumbBuffer, framebuffer::Handle)>>,
}
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> 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 {
errmsg: "Error loading crtc info",
dev: dev.dev_path(),
source,
})?;
let mode = crtc_info.mode();
let blob = match mode {
Some(mode) => Some(
dev.create_property_blob(mode)
.compat()
.map_err(|source| Error::Access {
errmsg: "Failed to create Property Blob for mode",
dev: dev.dev_path(),
source,
})?,
),
None => None,
// 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 current_mode = crtc_info.mode().unwrap_or_else(|| unsafe { std::mem::zeroed() });
let current_blob = match crtc_info.mode() {
Some(mode) => dev
.create_property_blob(mode)
.compat()
.map_err(|source| Error::Access {
errmsg: "Failed to create Property Blob for mode",
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)
.compat()
.map_err(|source| Error::Access {
@ -86,12 +106,7 @@ impl<A: AsRawFd + 'static> AtomicDrmSurfaceInternal<A> {
source,
})?;
let mut state = State {
mode,
blob,
connectors: HashSet::new(),
};
let mut current_connectors = HashSet::new();
for conn in res_handles.connectors() {
let crtc_prop = dev
.prop_mapping
@ -113,7 +128,7 @@ impl<A: AsRawFd + 'static> AtomicDrmSurfaceInternal<A> {
crtc_prop_info.value_type().convert_value(val)
{
if conn_crtc == crtc {
state.connectors.insert(*conn);
current_connectors.insert(*conn);
}
}
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) =
AtomicDrmSurfaceInternal::find_planes(&dev, crtc).ok_or(Error::NoSuitablePlanes {
crtc,
dev: dev.dev_path(),
})?;
Ok(AtomicDrmSurfaceInternal {
let surface = AtomicDrmSurfaceInternal {
dev,
crtc,
cursor: CursorState {
@ -136,10 +161,102 @@ impl<A: AsRawFd + 'static> AtomicDrmSurfaceInternal<A> {
hotspot: Cell::new((0, 0)),
},
planes: Planes { primary, cursor },
state: RwLock::new(state.clone()),
pending: RwLock::new(state),
state: RwLock::new(state),
pending: RwLock::new(pending),
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()
}
fn current_mode(&self) -> Option<Mode> {
fn current_mode(&self) -> Mode {
self.state.read().unwrap().mode
}
fn pending_mode(&self) -> Option<Mode> {
fn pending_mode(&self) -> Mode {
self.pending.read().unwrap().mode
}
fn add_connector(&self, conn: connector::Handle) -> Result<(), Error> {
if !self.dev.active.load(Ordering::SeqCst) {
return Err(Error::DeviceInactive);
}
let info = self
.get_connector(conn)
.compat()
@ -180,15 +301,15 @@ impl<A: AsRawFd + 'static> Surface for AtomicDrmSurfaceInternal<A> {
let mut pending = self.pending.write().unwrap();
// 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
let req = self.build_request(
&mut [conn].iter(),
&mut [].iter(),
&self.planes,
None,
pending.mode,
pending.blob,
Some(self.create_test_buffer(&pending.mode)?),
Some(pending.mode),
Some(pending.blob),
)?;
self.atomic_commit(
&[AtomicCommitFlags::AllowModeset, AtomicCommitFlags::TestOnly],
@ -202,21 +323,30 @@ impl<A: AsRawFd + 'static> Surface for AtomicDrmSurfaceInternal<A> {
Ok(())
} else {
Err(Error::ModeNotSuitable(pending.mode.unwrap()))
Err(Error::ModeNotSuitable(pending.mode))
}
}
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();
// 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)
let req = self.build_request(
&mut [].iter(),
&mut [conn].iter(),
&self.planes,
None,
pending.mode,
pending.blob,
Some(self.create_test_buffer(&pending.mode)?),
Some(pending.mode),
Some(pending.blob),
)?;
self.atomic_commit(
&[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> {
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 mut pending = self.pending.write().unwrap();
@ -243,9 +381,9 @@ impl<A: AsRawFd + 'static> Surface for AtomicDrmSurfaceInternal<A> {
&mut added,
&mut removed,
&self.planes,
None,
pending.mode,
pending.blob,
Some(self.create_test_buffer(&pending.mode)?),
Some(pending.mode),
Some(pending.blob),
)?;
self.atomic_commit(
@ -259,30 +397,31 @@ impl<A: AsRawFd + 'static> Surface for AtomicDrmSurfaceInternal<A> {
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();
// check if new config is supported
let new_blob = Some(match mode {
Some(mode) => self
.dev
.create_property_blob(mode)
.compat()
.map_err(|source| Error::Access {
errmsg: "Failed to create Property Blob for mode",
dev: self.dev_path(),
source,
})?,
None => property::Value::Unknown(0),
});
let new_blob = self
.create_property_blob(mode)
.compat()
.map_err(|source| Error::Access {
errmsg: "Failed to create Property Blob for mode",
dev: self.dev_path(),
source,
})?;
let test_fb = Some(self.create_test_buffer(&pending.mode)?);
let req = self.build_request(
&mut pending.connectors.iter(),
&mut [].iter(),
&self.planes,
None,
mode,
new_blob,
test_fb,
Some(mode),
Some(new_blob),
)?;
if let Err(err) = self
.atomic_commit(
@ -292,7 +431,7 @@ impl<A: AsRawFd + 'static> Surface for AtomicDrmSurfaceInternal<A> {
.compat()
.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);
}
@ -310,6 +449,10 @@ impl<A: AsRawFd + 'static> RawSurface for AtomicDrmSurfaceInternal<A> {
}
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 pending = self.pending.write().unwrap();
@ -340,11 +483,7 @@ impl<A: AsRawFd + 'static> RawSurface for AtomicDrmSurfaceInternal<A> {
}
if current.mode != pending.mode {
info!(
self.logger,
"Setting new mode: {:?}",
pending.mode.as_ref().unwrap().name()
);
info!(self.logger, "Setting new mode: {:?}", pending.mode.name());
}
trace!(self.logger, "Testing screen config");
@ -355,8 +494,8 @@ impl<A: AsRawFd + 'static> RawSurface for AtomicDrmSurfaceInternal<A> {
&mut removed,
&self.planes,
Some(framebuffer),
pending.mode,
pending.blob,
Some(pending.mode),
Some(pending.blob),
)?;
if let Err(err) = self
@ -380,15 +519,13 @@ impl<A: AsRawFd + 'static> RawSurface for AtomicDrmSurfaceInternal<A> {
&mut [].iter(),
&self.planes,
Some(framebuffer),
current.mode,
current.blob,
Some(current.mode),
Some(current.blob),
)?
} else {
if current.mode != pending.mode {
if let Some(blob) = current.blob {
if let Err(err) = self.dev.destroy_property_blob(blob.into()) {
warn!(self.logger, "Failed to destory old mode property blob: {}", err);
}
if let Err(err) = self.dev.destroy_property_blob(current.blob.into()) {
warn!(self.logger, "Failed to destory old mode property blob: {}", err);
}
}
*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(
&[
AtomicCommitFlags::PageFlipEvent,
@ -417,23 +554,31 @@ impl<A: AsRawFd + 'static> RawSurface for AtomicDrmSurfaceInternal<A> {
Ok(())
}
fn page_flip(&self, framebuffer: framebuffer::Handle) -> Result<(), SwapBuffersError> {
let req = self
.build_request(
&mut [].iter(),
&mut [].iter(),
&self.planes,
Some(framebuffer),
None,
None,
) //current.mode)
.map_err(|_| SwapBuffersError::ContextLost)?;
fn page_flip(&self, framebuffer: framebuffer::Handle) -> Result<(), Error> {
if !self.dev.active.load(Ordering::SeqCst) {
return Err(Error::DeviceInactive);
}
let req = self.build_request(
&mut [].iter(),
&mut [].iter(),
&self.planes,
Some(framebuffer),
None,
None,
)?;
trace!(self.logger, "Queueing page flip: {:#?}", req);
self.atomic_commit(
&[AtomicCommitFlags::PageFlipEvent, AtomicCommitFlags::Nonblock],
req,
)
.map_err(|_| SwapBuffersError::ContextLost)?;
.compat()
.map_err(|source| Error::Access {
errmsg: "Page flip commit failed",
dev: self.dev_path(),
source,
})?;
Ok(())
}
@ -444,6 +589,10 @@ impl<A: AsRawFd + 'static> CursorBackend for AtomicDrmSurfaceInternal<A> {
type Error = 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);
self.cursor.position.set(Some((x, y)));
Ok(())
@ -454,6 +603,10 @@ impl<A: AsRawFd + 'static> CursorBackend for AtomicDrmSurfaceInternal<A> {
buffer: &Self::CursorFormat,
hotspot: (u32, u32),
) -> Result<(), Error> {
if !self.dev.active.load(Ordering::SeqCst) {
return Err(Error::DeviceInactive);
}
trace!(self.logger, "Setting the new imported cursor");
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()
}
fn current_mode(&self) -> Option<Mode> {
fn current_mode(&self) -> Mode {
self.0.current_mode()
}
fn pending_mode(&self) -> Option<Mode> {
fn pending_mode(&self) -> Mode {
self.0.pending_mode()
}
@ -854,7 +1007,7 @@ impl<A: AsRawFd + 'static> Surface for AtomicDrmSurface<A> {
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)
}
}
@ -868,7 +1021,7 @@ impl<A: AsRawFd + 'static> RawSurface for AtomicDrmSurface<A> {
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)
}
}

View File

@ -22,6 +22,7 @@ use drm::{
#[cfg(feature = "renderer_gl")]
use nix::libc::c_void;
use nix::libc::dev_t;
use std::env;
use std::os::unix::io::{AsRawFd, RawFd};
#[cfg(feature = "use_system_lib")]
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>> {
/// Try to initialize an [`AtomicDrmDevice`](::backend::drm:;atomic::AtomicDrmDevice)
/// 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
L: Into<Option<::slog::Logger>>,
{
let log = crate::slog_or_stdlog(logger).new(o!("smithay_module" => "backend_drm_fallback"));
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)),
Err(err) => {
error!(log, "Failed to initialize preferred AtomicDrmDevice: {}", err);
info!(log, "Falling back to fallback LegacyyDrmDevice");
Ok(FallbackDevice::Fallback(LegacyDrmDevice::new(fd, log)?))
info!(log, "Falling back to fallback LegacyDrmDevice");
Ok(FallbackDevice::Fallback(LegacyDrmDevice::new(
fd,
disable_connectors,
log,
)?))
}
}
}
@ -228,10 +263,19 @@ where
}
}
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 {
FallbackDevice::Preference(dev) => Ok(FallbackSurface::Preference(dev.create_surface(crtc)?)),
FallbackDevice::Fallback(dev) => Ok(FallbackSurface::Fallback(dev.create_surface(crtc)?)),
FallbackDevice::Preference(dev) => Ok(FallbackSurface::Preference(
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);
@ -281,9 +325,9 @@ where
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!(set_connectors, &Self, Result<(), E>, conns: &[connector::Handle]);
fallback_surface_impl!(current_mode, &Self, Option<Mode>);
fallback_surface_impl!(pending_mode, &Self, Option<Mode>);
fallback_surface_impl!(use_mode, &Self, Result<(), E>, mode: Option<Mode>);
fallback_surface_impl!(current_mode, &Self, Mode);
fallback_surface_impl!(pending_mode, &Self, Mode);
fallback_surface_impl!(use_mode, &Self, Result<(), E>, mode: Mode);
}
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, &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 {
FallbackSurface::Preference(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.
//!
use crate::backend::graphics::SwapBuffersError;
use drm::control::{connector, crtc, Mode, RawResourceHandle};
use std::path::PathBuf;
pub mod fallback;
@ -19,7 +19,7 @@ pub enum Error {
#[error("Failed to aquire DRM master")]
DrmMasterFailed,
/// The `DrmDevice` encountered an access error
#[error("DRM access error: {errmsg} on device `{dev:?}`")]
#[error("DRM access error: {errmsg} on device `{dev:?}` ({source:})")]
Access {
/// Error message associated to the access error
errmsg: &'static str,
@ -40,6 +40,9 @@ pub enum Error {
/// The given crtc is already in use by another backend
#[error("Crtc `{0:?}` is already in use by another backend")]
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
#[error("No encoder found for the given connector '{connector:?}' on crtc `{crtc:?}`")]
NoSuitableEncoder {
@ -68,3 +71,25 @@ pub enum Error {
#[error("Atomic Test failed for new properties on crtc ({0:?})")]
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.
//!
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 nix::libc::dev_t;
use std::os::unix::io::{AsRawFd, RawFd};
@ -17,9 +17,9 @@ use wayland_server::Display;
use super::{Device, DeviceHandler, Surface};
use crate::backend::egl::native::{Backend, NativeDisplay, NativeSurface};
use crate::backend::egl::Error as EGLError;
#[cfg(feature = "use_system_lib")]
use crate::backend::egl::{display::EGLBufferReader, EGLGraphicsBackend};
use crate::backend::egl::{EGLError as RawEGLError, Error as EGLError, SurfaceCreationError};
mod surface;
pub use self::surface::*;
@ -33,18 +33,25 @@ pub mod session;
#[derive(thiserror::Error, Debug)]
pub enum Error<U: std::error::Error + std::fmt::Debug + std::fmt::Display + 'static> {
/// EGL Error
#[error("EGL error: {0:?}")]
#[error("EGL error: {0:}")]
EGL(#[source] EGLError),
/// EGL Error
#[error("EGL error: {0:}")]
RawEGL(#[source] RawEGLError),
/// Underlying backend error
#[error("Underlying backend error: {0:?}")]
Underlying(#[source] U),
}
type Arguments = (crtc::Handle, Mode, Vec<connector::Handle>);
/// Representation of an egl device to create egl rendering surfaces
pub struct EglDevice<B, D>
where
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,
{
dev: EGLDisplay<B, D>,
@ -56,7 +63,9 @@ where
impl<B, D> AsRawFd for EglDevice<B, D>
where
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,
{
fn as_raw_fd(&self) -> RawFd {
@ -67,7 +76,9 @@ where
impl<B, D> EglDevice<B, D>
where
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,
{
/// Try to create a new [`EglDevice`] from an open device.
@ -122,7 +133,9 @@ where
struct InternalDeviceHandler<B, D>
where
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,
{
handler: Box<dyn DeviceHandler<Device = EglDevice<B, D>> + 'static>,
@ -131,7 +144,9 @@ where
impl<B, D> DeviceHandler for InternalDeviceHandler<B, D>
where
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,
{
type Device = D;
@ -147,7 +162,9 @@ where
impl<B, D> Device for EglDevice<B, D>
where
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,
{
type Surface = EglSurface<<D as Device>::Surface>;
@ -169,6 +186,8 @@ where
fn create_surface(
&mut self,
crtc: crtc::Handle,
mode: Mode,
connectors: &[connector::Handle],
) -> Result<Self::Surface, <Self::Surface as Surface>::Error> {
info!(self.logger, "Initializing EglSurface");
@ -182,9 +201,12 @@ where
context.get_pixel_format(),
self.default_requirements.double_buffer,
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 })
}
@ -221,7 +243,9 @@ where
impl<B, D> EGLGraphicsBackend for EglDevice<B, D>
where
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,
{
fn bind_wl_display(&self, display: &Display) -> Result<EGLBufferReader, EGLError> {
@ -232,7 +256,9 @@ where
impl<B, D> Drop for EglDevice<B, D>
where
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,
{
fn drop(&mut self) {

View File

@ -3,11 +3,11 @@
//! to an open [`Session`](::backend::session::Session).
//!
use drm::control::crtc;
use drm::control::{connector, crtc, Mode};
use std::os::unix::io::RawFd;
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::session::{AsSessionObserver, SessionObserver};
@ -22,7 +22,13 @@ impl<S, B, D> AsSessionObserver<EglDeviceObserver<S>> for EglDevice<B, D>
where
S: SessionObserver + '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,
{
fn observer(&mut self) -> EglDeviceObserver<S> {

View File

@ -1,5 +1,6 @@
use drm::control::{connector, crtc, Mode};
use nix::libc::c_void;
use std::convert::TryInto;
use super::Error;
use crate::backend::drm::Surface;
@ -22,7 +23,7 @@ where
impl<N> Surface for EglSurface<N>
where
N: NativeSurface + Surface,
N: native::NativeSurface + Surface,
{
type Connectors = <N as Surface>::Connectors;
type Error = Error<<N as Surface>::Error>;
@ -53,15 +54,15 @@ where
self.surface.set_connectors(connectors).map_err(Error::Underlying)
}
fn current_mode(&self) -> Option<Mode> {
fn current_mode(&self) -> Mode {
self.surface.current_mode()
}
fn pending_mode(&self) -> Option<Mode> {
fn pending_mode(&self) -> 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)
}
}
@ -90,9 +91,17 @@ where
impl<N> GLGraphicsBackend for EglSurface<N>
where
N: native::NativeSurface + Surface,
<N as NativeSurface>::Error: Into<SwapBuffersError> + 'static,
{
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 {
@ -100,7 +109,7 @@ where
}
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)
}
@ -109,7 +118,9 @@ where
}
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 {

View File

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

View File

@ -10,8 +10,9 @@
//!
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 gbm::{self, BufferObjectFlags, Format as GbmFormat};
use nix::libc::dev_t;
@ -26,25 +27,31 @@ use std::sync::Once;
/// Errors thrown by the [`GbmDevice`](::backend::drm::gbm::GbmDevice)
/// and [`GbmSurface`](::backend::drm::gbm::GbmSurface).
#[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
#[error("Creation of GBM device failed")]
InitFailed(#[source] io::Error),
/// Creation of GBM surface failed
#[error("Creation of GBM surface failed")]
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
#[error("Creation of GBM buffer object failed")]
BufferCreationFailed(#[source] io::Error),
/// Writing to GBM buffer failed
#[error("Writing to GBM buffer failed")]
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
#[error("Lock of GBM surface font buffer failed")]
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
#[error("The GBM device was destroyed")]
DeviceDestroyed,
@ -159,17 +166,16 @@ impl<D: RawDevice + ControlDevice + 'static> Device for GbmDevice<D> {
fn create_surface(
&mut self,
crtc: crtc::Handle,
mode: Mode,
connectors: &[connector::Handle],
) -> Result<GbmSurface<D>, Error<<<D as Device>::Surface as Surface>::Error>> {
info!(self.logger, "Initializing GbmSurface");
let drm_surface =
Device::create_surface(&mut **self.dev.borrow_mut(), crtc).map_err(Error::Underlying)?;
let drm_surface = Device::create_surface(&mut **self.dev.borrow_mut(), crtc, mode, connectors)
.map_err(Error::Underlying)?;
// initialize the surface
let (w, h) = drm_surface
.pending_mode()
.map(|mode| mode.size())
.unwrap_or((1, 1));
let (w, h) = drm_surface.pending_mode().size();
let surface = self
.dev
.borrow()
@ -249,3 +255,29 @@ impl<D: RawDevice + ControlDevice + 'static> Drop for GbmDevice<D> {
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 crate::backend::drm::{RawDevice, RawSurface};
use crate::backend::graphics::CursorBackend;
use crate::backend::session::{AsSessionObserver, SessionObserver};
/// [`SessionObserver`](SessionObserver)
@ -27,11 +28,12 @@ pub struct GbmDeviceObserver<
}
impl<
S: SessionObserver + 'static,
D: RawDevice + ::drm::control::Device + AsSessionObserver<S> + 'static,
> AsSessionObserver<GbmDeviceObserver<S, D>> for GbmDevice<D>
O: SessionObserver + 'static,
S: CursorBackend<CursorFormat = dyn drm::buffer::Buffer> + RawSurface + 'static,
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 {
observer: (**self.dev.borrow_mut()).observer(),
backends: Rc::downgrade(&self.backends),
@ -41,9 +43,10 @@ impl<
}
impl<
S: SessionObserver + 'static,
D: RawDevice + ::drm::control::Device + AsSessionObserver<S> + 'static,
> SessionObserver for GbmDeviceObserver<S, D>
O: SessionObserver + 'static,
S: CursorBackend<CursorFormat = dyn drm::buffer::Buffer> + RawSurface + 'static,
D: RawDevice<Surface = S> + drm::control::Device + AsSessionObserver<O> + 'static,
> SessionObserver for GbmDeviceObserver<O, D>
{
fn pause(&mut self, devnum: Option<(u32, u32)>) {
self.observer.pause(devnum);
@ -56,20 +59,19 @@ impl<
for (crtc, backend) in backends.borrow().iter() {
if let Some(backend) = backend.upgrade() {
// restart rendering loop, if it was previously running
if let Some(fb) = backend.current_frame_buffer.get() {
if backend.crtc.page_flip(fb).is_err() {
// Try more!
if let Err(err) = backend.recreate() {
error!(
self.logger,
"Failed to re-create gbm surface, is the device gone?\n\t{}", err
);
}
if let Err(err) = unsafe { backend.page_flip() } {
warn!(self.logger, "Failed to restart rendering loop. Error: {}", err);
// TODO bubble this up the user somehow
// maybe expose a "running" state from a surface?
}
if let Some(current_fb) = backend.current_frame_buffer.get() {
let result = if backend.crtc.commit_pending() {
backend.crtc.commit(current_fb)
} else {
RawSurface::page_flip(&backend.crtc, current_fb)
};
if let Err(err) = result {
warn!(
self.logger,
"Failed to restart rendering loop. Re-creating resources. Error: {}", err
);
// TODO bubble up
}
}
@ -79,12 +81,7 @@ impl<
let &(ref cursor, ref hotspot): &(BufferObject<()>, (u32, u32)) =
unsafe { &*backend.cursor.as_ptr() };
if backend
.dev
.borrow()
.set_cursor2(*crtc, Some(cursor), ((*hotspot).0 as i32, (*hotspot).1 as i32))
.is_err()
{
if backend.crtc.set_cursor_representation(cursor, *hotspot).is_err() {
if let Err(err) = backend.dev.borrow().set_cursor(*crtc, Some(cursor)) {
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 drm::control::{connector, crtc, framebuffer, Device as ControlDevice, Mode};
use failure::ResultExt;
use gbm::{self, BufferObject, BufferObjectFlags, Format as GbmFormat, SurfaceBufferHandle};
use image::{ImageBuffer, Rgba};
@ -9,7 +10,6 @@ use std::cell::{Cell, RefCell};
use std::rc::Rc;
use crate::backend::graphics::CursorBackend;
use crate::backend::graphics::SwapBuffersError;
pub(super) struct GbmSurfaceInternal<D: RawDevice + 'static> {
pub(super) dev: Rc<RefCell<gbm::Device<D>>>,
@ -32,7 +32,7 @@ impl<D: RawDevice + 'static> GbmSurfaceInternal<D> {
// 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 nb = self.next_buffer.take();
let res = nb.is_some();
@ -42,7 +42,7 @@ impl<D: RawDevice + 'static> GbmSurfaceInternal<D> {
if res {
// We cannot call lock_front_buffer anymore without releasing the previous buffer, which will happen when the page flip is done
warn!(self.logger, "Tried to swap with an already queued flip");
return Err(SwapBuffersError::AlreadySwapped);
return Err(Error::FrontBuffersExhausted);
}
// supporting only one buffer would cause a lot of inconvinience and
@ -53,13 +53,13 @@ impl<D: RawDevice + 'static> GbmSurfaceInternal<D> {
.surface
.borrow()
.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
// (they are reused by gbm)
let maybe_fb = next_bo
.userdata()
.map_err(|_| SwapBuffersError::ContextLost)?
.map_err(|_| Error::InvalidInternalState)?
.cloned();
let fb = if let Some(info) = maybe_fb {
info
@ -67,7 +67,8 @@ impl<D: RawDevice + 'static> GbmSurfaceInternal<D> {
let fb = self
.crtc
.add_planar_framebuffer(&*next_bo, &[0; 4], 0)
.map_err(|_| SwapBuffersError::ContextLost)?;
.compat()
.map_err(Error::FramebufferCreationFailed)?;
next_bo.set_userdata(fb).unwrap();
fb
};
@ -75,14 +76,11 @@ impl<D: RawDevice + 'static> GbmSurfaceInternal<D> {
if self.recreated.get() {
debug!(self.logger, "Commiting new state");
if let Err(err) = self.crtc.commit(fb) {
error!(self.logger, "Error commiting crtc: {}", err);
return Err(SwapBuffersError::ContextLost);
}
self.crtc.commit(fb).map_err(Error::Underlying)?;
self.recreated.set(false);
} else {
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));
@ -91,11 +89,7 @@ impl<D: RawDevice + 'static> GbmSurfaceInternal<D> {
}
pub fn recreate(&self) -> Result<(), Error<<<D as Device>::Surface as Surface>::Error>> {
let (w, h) = self
.pending_mode()
.or_else(|| self.current_mode())
.ok_or(Error::NoModeSet)?
.size();
let (w, h) = self.pending_mode().size();
// Recreate the surface and the related resources to match the new
// resolution.
@ -167,15 +161,15 @@ impl<D: RawDevice + 'static> Surface for GbmSurfaceInternal<D> {
self.crtc.set_connectors(connectors).map_err(Error::Underlying)
}
fn current_mode(&self) -> Option<Mode> {
fn current_mode(&self) -> Mode {
self.crtc.current_mode()
}
fn pending_mode(&self) -> Option<Mode> {
fn pending_mode(&self) -> 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)
}
}
@ -277,7 +271,7 @@ impl<D: RawDevice + 'static> GbmSurface<D> {
///
/// When used in conjunction with an EGL context, this must be called exactly once
/// 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()
}
@ -325,15 +319,15 @@ impl<D: RawDevice + 'static> Surface for GbmSurface<D> {
self.0.set_connectors(connectors)
}
fn current_mode(&self) -> Option<Mode> {
fn current_mode(&self) -> Mode {
self.0.current_mode()
}
fn pending_mode(&self) -> Option<Mode> {
fn pending_mode(&self) -> 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)
}
}

View File

@ -11,24 +11,24 @@
use super::{common::Error, DevPath, Device, DeviceHandler, RawDevice};
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 nix::libc::dev_t;
use nix::sys::stat::fstat;
use std::cell::RefCell;
use std::collections::{HashMap, HashSet};
use std::collections::HashMap;
use std::os::unix::io::{AsRawFd, RawFd};
use std::rc::{Rc, Weak};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, RwLock};
use std::sync::Arc;
use failure::{Fail, ResultExt};
mod surface;
pub use self::surface::LegacyDrmSurface;
use self::surface::{LegacyDrmSurfaceInternal, State};
use self::surface::LegacyDrmSurfaceInternal;
#[cfg(feature = "backend_session")]
pub mod session;
@ -88,9 +88,21 @@ impl<A: AsRawFd + 'static> Drop for Dev<A> {
impl<A: AsRawFd + 'static> LegacyDrmDevice<A> {
/// Create a new [`LegacyDrmDevice`] from an open drm node
///
/// Returns an error if the file is no valid drm node or context creation was not
/// successful.
pub fn new<L>(dev: A, logger: L) -> Result<Self, Error>
/// # Arguments
///
/// - `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
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 {
dev: Rc::new(dev),
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> {
fn as_raw_fd(&self) -> RawFd {
self.dev.as_raw_fd()
@ -186,7 +271,12 @@ impl<A: AsRawFd + 'static> Device for LegacyDrmDevice<A> {
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) {
return Err(Error::CrtcAlreadyInUse(crtc));
}
@ -195,52 +285,17 @@ impl<A: AsRawFd + 'static> Device for LegacyDrmDevice<A> {
return Err(Error::DeviceInactive);
}
// Try to enumarate the current state to set the initial state variable correctly
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);
}
}
}
if connectors.is_empty() {
return Err(Error::SurfaceWithoutConnectors(crtc));
}
let state = State { mode, connectors };
let backend = Rc::new(LegacyDrmSurfaceInternal {
dev: self.dev.clone(),
let backend = Rc::new(LegacyDrmSurfaceInternal::new(
self.dev.clone(),
crtc,
state: RwLock::new(state.clone()),
pending: RwLock::new(state),
logger: self.logger.new(o!("crtc" => format!("{:?}", crtc))),
});
mode,
connectors,
self.logger.new(o!("crtc" => format!("{:?}", crtc))),
)?);
self.backends.borrow_mut().insert(crtc, Rc::downgrade(&backend));
Ok(LegacyDrmSurface(backend))

View File

@ -5,16 +5,17 @@
use drm::control::{crtc, Device as ControlDevice};
use drm::Device as BasicDevice;
use failure::ResultExt;
use nix::libc::dev_t;
use nix::sys::stat;
use std::cell::RefCell;
use std::collections::HashMap;
use std::collections::{HashMap, HashSet};
use std::os::unix::io::{AsRawFd, RawFd};
use std::rc::{Rc, Weak};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use super::{Dev, LegacyDrmDevice, LegacyDrmSurfaceInternal};
use super::{Dev, DevPath, Error, LegacyDrmDevice, LegacyDrmSurfaceInternal};
use crate::backend::session::{AsSessionObserver, 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::os::unix::io::{AsRawFd, RawFd};
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::graphics::CursorBackend;
use crate::backend::graphics::SwapBuffersError;
use super::Dev;
@ -20,7 +19,7 @@ use failure::{Fail, ResultExt};
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct State {
pub mode: Option<Mode>,
pub mode: Mode,
pub connectors: HashSet<connector::Handle>,
}
@ -46,6 +45,10 @@ impl<A: AsRawFd + 'static> CursorBackend for LegacyDrmSurfaceInternal<A> {
type Error = 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);
self.move_cursor(self.crtc, (x as i32, y as i32))
.compat()
@ -61,6 +64,10 @@ impl<A: AsRawFd + 'static> CursorBackend for LegacyDrmSurfaceInternal<A> {
buffer: &Self::CursorFormat,
hotspot: (u32, u32),
) -> Result<(), Error> {
if !self.dev.active.load(Ordering::SeqCst) {
return Err(Error::DeviceInactive);
}
trace!(self.logger, "Setting the new imported cursor");
if self
@ -96,18 +103,22 @@ impl<A: AsRawFd + 'static> Surface for LegacyDrmSurfaceInternal<A> {
self.pending.read().unwrap().connectors.clone()
}
fn current_mode(&self) -> Option<Mode> {
fn current_mode(&self) -> Mode {
self.state.read().unwrap().mode
}
fn pending_mode(&self) -> Option<Mode> {
fn pending_mode(&self) -> Mode {
self.pending.read().unwrap().mode
}
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();
if self.check_connector(conn, pending.mode.as_ref().unwrap())? {
if self.check_connector(conn, &pending.mode)? {
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> {
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(())
}
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();
if connectors
.iter()
.map(|conn| self.check_connector(*conn, pending.mode.as_ref().unwrap()))
.map(|conn| self.check_connector(*conn, &pending.mode))
.collect::<Result<Vec<bool>, _>>()?
.iter()
.all(|v| *v)
@ -135,25 +160,27 @@ impl<A: AsRawFd + 'static> Surface for LegacyDrmSurfaceInternal<A> {
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();
// check the connectors to see if this mode is supported
if let Some(mode) = mode {
for connector in &pending.connectors {
if !self
.get_connector(*connector)
.compat()
.map_err(|source| Error::Access {
errmsg: "Error loading connector info",
dev: self.dev_path(),
source,
})?
.modes()
.contains(&mode)
{
return Err(Error::ModeNotSuitable(mode));
}
for connector in &pending.connectors {
if !self
.get_connector(*connector)
.compat()
.map_err(|source| Error::Access {
errmsg: "Error loading connector info",
dev: self.dev_path(),
source,
})?
.modes()
.contains(&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> {
if !self.dev.active.load(Ordering::SeqCst) {
return Err(Error::DeviceInactive);
}
let mut current = self.state.write().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 mut conn_removed = false;
for conn in removed {
for conn in removed.clone() {
if let Ok(info) = self.get_connector(*conn) {
info!(self.logger, "Removing connector: {:?}", info.interface());
} else {
@ -187,6 +218,7 @@ impl<A: AsRawFd + 'static> RawSurface for LegacyDrmSurfaceInternal<A> {
// the graphics pipeline will not be freed otherwise
conn_removed = true;
}
self.dev.set_connector_state(removed.copied(), false)?;
if conn_removed {
// 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) {
info!(self.logger, "Adding connector: {:?}", info.interface());
} else {
info!(self.logger, "Adding unknown connector");
}
}
self.dev.set_connector_state(added.copied(), true)?;
if current.mode != pending.mode {
info!(
self.logger,
"Setting new mode: {:?}",
pending.mode.as_ref().unwrap().name()
);
info!(self.logger, "Setting new mode: {:?}", pending.mode.name());
}
}
@ -226,7 +255,7 @@ impl<A: AsRawFd + 'static> RawSurface for LegacyDrmSurfaceInternal<A> {
.iter()
.copied()
.collect::<Vec<connector::Handle>>(),
pending.mode,
Some(pending.mode),
)
.compat()
.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");
if !self.dev.active.load(Ordering::SeqCst) {
return Err(Error::DeviceInactive);
}
ControlDevice::page_flip(
self,
self.crtc,
@ -261,11 +294,84 @@ impl<A: AsRawFd + 'static> RawSurface for LegacyDrmSurfaceInternal<A> {
&[PageFlipFlags::PageFlipEvent],
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> {
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> {
let info = self
.get_connector(conn)
@ -319,7 +425,26 @@ impl<A: AsRawFd + 'static> LegacyDrmSurfaceInternal<A> {
impl<A: AsRawFd + 'static> Drop for LegacyDrmSurfaceInternal<A> {
fn drop(&mut self) {
// 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);
// 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()
}
fn current_mode(&self) -> Option<Mode> {
fn current_mode(&self) -> Mode {
self.0.current_mode()
}
fn pending_mode(&self) -> Option<Mode> {
fn pending_mode(&self) -> Mode {
self.0.pending_mode()
}
@ -388,7 +513,7 @@ impl<A: AsRawFd + 'static> Surface for LegacyDrmSurface<A> {
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)
}
}
@ -402,7 +527,7 @@ impl<A: AsRawFd + 'static> RawSurface for LegacyDrmSurface<A> {
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)
}
}

View File

@ -48,8 +48,6 @@ use std::path::PathBuf;
use calloop::{generic::Generic, InsertError, LoopHandle, Source};
use super::graphics::SwapBuffersError;
#[cfg(feature = "backend_drm_atomic")]
pub mod atomic;
#[cfg(feature = "backend_drm")]
@ -91,16 +89,23 @@ pub trait Device: AsRawFd + DevPath {
/// Creates a new rendering surface.
///
/// # Arguments
///
/// Initialization of surfaces happens through the types provided by
/// [`drm-rs`](drm).
///
/// [`crtc`](drm::control::crtc)s represent scanout engines
/// of the device pointer to one framebuffer.
/// 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.
/// - [`crtc`](drm::control::crtc)s represent scanout engines of the device pointing to one framebuffer. \
/// 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.
/// - [`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(
&mut self,
crtc: crtc::Handle,
mode: Mode,
connectors: &[connector::Handle],
) -> Result<Self::Surface, <Self::Surface as Surface>::Error>;
/// 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)
/// 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)
/// (e.g. no suitable [`encoder`](drm::control::encoder) may be found)
/// 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>;
/// Returns the currently active [`Mode`](drm::control::Mode)
/// of the underlying [`crtc`](drm::control::crtc)
/// if any.
fn current_mode(&self) -> Option<Mode>;
fn current_mode(&self) -> Mode;
/// Returns the currently pending [`Mode`](drm::control::Mode)
/// to be used after the next commit, if any.
fn pending_mode(&self) -> Option<Mode>;
/// to be used after the next commit.
fn pending_mode(&self) -> Mode;
/// Tries to set a new [`Mode`](drm::control::Mode)
/// to be used after the next commit.
///
@ -193,7 +202,7 @@ pub trait Surface {
/// *Note*: Only on a [`RawSurface`] you may directly trigger
/// a [`commit`](RawSurface::commit). Other [`Surface`]s provide their
/// 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
@ -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.
/// Make sure to [set a `DeviceHandler`](Device::set_handler) and
/// [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`

View File

@ -1,10 +1,10 @@
//! 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::native::NativeSurface;
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::ptr;
use std::sync::Arc;
@ -93,21 +93,15 @@ impl EGLContext {
trace!(log, "Creating EGL context...");
// TODO: Support shared contexts
let context = unsafe {
let context = wrap_egl_call(|| unsafe {
ffi::egl::CreateContext(
**display.display,
config_id,
ptr::null(),
context_attributes.as_ptr(),
)
};
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)),
}
}
})
.map_err(Error::CreationFailed)?;
info!(log, "EGL context created");
@ -126,25 +120,15 @@ impl EGLContext {
///
/// This function is marked unsafe, because the context cannot be made current
/// on multiple threads.
pub unsafe fn make_current_with_surface<N>(
&self,
surface: &EGLSurface<N>,
) -> ::std::result::Result<(), SwapBuffersError>
pub unsafe fn make_current_with_surface<N>(&self, surface: &EGLSurface<N>) -> Result<(), MakeCurrentError>
where
N: NativeSurface,
{
let surface_ptr = surface.surface.get();
let ret = ffi::egl::MakeCurrent(**self.display, surface_ptr, surface_ptr, self.context);
if ret == 0 {
match ffi::egl::GetError() as u32 {
ffi::egl::CONTEXT_LOST => Err(SwapBuffersError::ContextLost),
err => panic!("eglMakeCurrent failed (eglGetError returned 0x{:x})", err),
}
} else {
Ok(())
}
wrap_egl_call(|| ffi::egl::MakeCurrent(**self.display, surface_ptr, surface_ptr, self.context))
.map(|_| ())
.map_err(Into::into)
}
/// Makes the OpenGL context the current context in the current thread with no surface bound.
@ -152,18 +136,18 @@ impl EGLContext {
/// # Safety
///
/// This function is marked unsafe, because the context cannot be made current
/// on multiple threads.
pub unsafe fn make_current(&self) -> ::std::result::Result<(), SwapBuffersError> {
let ret = ffi::egl::MakeCurrent(**self.display, ptr::null(), ptr::null(), self.context);
if ret == 0 {
match ffi::egl::GetError() as u32 {
ffi::egl::CONTEXT_LOST => Err(SwapBuffersError::ContextLost),
err => panic!("eglMakeCurrent failed (eglGetError returned 0x{:x})", err),
}
} else {
Ok(())
}
/// on multiple threads without being unbound again (see `unbind`)
pub unsafe fn make_current(&self) -> Result<(), MakeCurrentError> {
wrap_egl_call(|| {
ffi::egl::MakeCurrent(
**self.display,
ffi::egl::NO_SURFACE,
ffi::egl::NO_SURFACE,
self.context,
)
})
.map(|_| ())
.map_err(Into::into)
}
/// 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 {
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 {
fn drop(&mut self) {
unsafe {
// We need to ensure the context is unbound, otherwise it egl stalls the destroy call
if ffi::egl::GetCurrentContext() == self.context as *const _ {
ffi::egl::MakeCurrent(ptr::null(), ptr::null(), ptr::null(), ptr::null());
}
// ignore failures at this point
let _ = self.unbind();
ffi::egl::DestroyContext(**self.display, self.context);
}
}
@ -270,7 +269,7 @@ impl Default for PixelFormatRequirements {
impl PixelFormatRequirements {
/// 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 {
out.push(ffi::egl::CONFIG_CAVEAT as c_int);
out.push(if hardware_accelerated {
@ -328,7 +327,7 @@ impl PixelFormatRequirements {
if self.stereoscopy {
error!(logger, "Stereoscopy is currently unsupported (sorry!)");
return Err(Error::NoAvailablePixelFormat);
return Err(());
}
Ok(())

View File

@ -3,7 +3,8 @@
#[cfg(feature = "use_system_lib")]
use crate::backend::egl::EGLGraphicsBackend;
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;
@ -44,6 +45,7 @@ impl Deref for EGLDisplayHandle {
impl Drop for EGLDisplayHandle {
fn drop(&mut self) {
unsafe {
// ignore errors on drop
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
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
// `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<_>>()
}
};
debug!(log, "EGL No-Display Extensions: {:?}", dp_extensions);
let display =
unsafe { B::get_display(ptr, |e: &str| dp_extensions.iter().any(|s| s == e), log.clone()) };
if display == ffi::egl::NO_DISPLAY {
error!(log, "EGL Display is not valid");
return Err(Error::DisplayNotSupported);
}
let display = unsafe {
B::get_display(ptr, |e: &str| dp_extensions.iter().any(|s| s == e), log.clone())
.map_err(Error::DisplayNotSupported)?
};
let egl_version = {
let mut major: 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 {
return Err(Error::InitFailed);
}
wrap_egl_call(|| unsafe {
ffi::egl::Initialize(display, major.as_mut_ptr(), minor.as_mut_ptr())
})
.map_err(Error::InitFailed)?;
let major = unsafe { major.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
// list of extensions obtained earlier
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());
list.split(' ').map(|e| e.to_string()).collect::<Vec<_>>()
} else {
vec![]
};
info!(log, "EGL Extensions: {:?}", extensions);
if egl_version >= (1, 2) && unsafe { ffi::egl::BindAPI(ffi::egl::OPENGL_ES_API) } == 0 {
error!(log, "OpenGLES not supported by the underlying EGL implementation");
return Err(Error::OpenGlesNotSupported);
if egl_version >= (1, 2) {
return Err(Error::OpenGlesNotSupported(None));
}
wrap_egl_call(|| unsafe { ffi::egl::BindAPI(ffi::egl::OPENGL_ES_API) })
.map_err(|source| Error::OpenGlesNotSupported(Some(source)))?;
Ok(EGLDisplay {
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
};
// calling `eglChooseConfig`
let mut num_configs = unsafe { std::mem::zeroed() };
if unsafe {
let mut num_configs = 0;
wrap_egl_call(|| unsafe {
ffi::egl::ChooseConfig(
**self.display,
descriptor.as_ptr(),
@ -235,18 +244,14 @@ impl<B: native::Backend, N: native::NativeDisplay<B>> EGLDisplay<B, N> {
0,
&mut num_configs,
)
} == 0
{
return Err(Error::ConfigFailed);
}
})
.map_err(Error::ConfigFailed)?;
if num_configs == 0 {
return Err(Error::NoAvailablePixelFormat);
}
let mut config_ids = Vec::with_capacity(num_configs as usize);
config_ids.resize_with(num_configs as usize, || unsafe { std::mem::zeroed() });
if unsafe {
let mut config_ids: Vec<ffi::egl::types::EGLConfig> = Vec::with_capacity(num_configs as usize);
wrap_egl_call(|| unsafe {
ffi::egl::ChooseConfig(
**self.display,
descriptor.as_ptr(),
@ -254,9 +259,10 @@ impl<B: native::Backend, N: native::NativeDisplay<B>> EGLDisplay<B, N> {
num_configs,
&mut num_configs,
)
} == 0
{
return Err(Error::ConfigFailed);
})
.map_err(Error::ConfigFailed)?;
unsafe {
config_ids.set_len(num_configs as usize);
}
// 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
.into_iter()
.filter(|&config| unsafe {
.map(|config| unsafe {
let mut min_swap_interval = 0;
ffi::egl::GetConfigAttrib(
**self.display,
config,
ffi::egl::MIN_SWAP_INTERVAL as ffi::egl::types::EGLint,
&mut min_swap_interval,
);
wrap_egl_call(|| {
ffi::egl::GetConfigAttrib(
**self.display,
config,
ffi::egl::MIN_SWAP_INTERVAL as ffi::egl::types::EGLint,
&mut min_swap_interval,
)
})?;
if desired_swap_interval < min_swap_interval {
return false;
return Ok(None);
}
let mut max_swap_interval = 0;
ffi::egl::GetConfigAttrib(
**self.display,
config,
ffi::egl::MAX_SWAP_INTERVAL as ffi::egl::types::EGLint,
&mut max_swap_interval,
);
wrap_egl_call(|| {
ffi::egl::GetConfigAttrib(
**self.display,
config,
ffi::egl::MAX_SWAP_INTERVAL as ffi::egl::types::EGLint,
&mut 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<_>>();
if config_ids.is_empty() {
@ -304,15 +318,15 @@ impl<B: native::Backend, N: native::NativeDisplay<B>> EGLDisplay<B, N> {
macro_rules! attrib {
($display:expr, $config:expr, $attr:expr) => {{
let mut value = MaybeUninit::uninit();
let res = ffi::egl::GetConfigAttrib(
**$display,
$config,
$attr as ffi::egl::types::EGLint,
value.as_mut_ptr(),
);
if res == 0 {
return Err(Error::ConfigFailed);
}
wrap_egl_call(|| {
ffi::egl::GetConfigAttrib(
**$display,
$config,
$attr as ffi::egl::types::EGLint,
value.as_mut_ptr(),
)
})
.map_err(Error::ConfigFailed)?;
value.assume_init()
}};
};
@ -357,12 +371,13 @@ impl<B: native::Backend, N: native::NativeDisplay<B>> EGLDisplay<B, N> {
double_buffer: Option<bool>,
config: ffi::egl::types::EGLConfig,
args: N::Arguments,
) -> Result<EGLSurface<B::Surface>, Error> {
) -> Result<EGLSurface<B::Surface>, SurfaceCreationError<N::Error>> {
trace!(self.logger, "Creating EGL window surface.");
let surface = self.native.borrow_mut().create_surface(args).map_err(|e| {
error!(self.logger, "EGL surface creation failed: {}", e);
Error::SurfaceCreationFailed
})?;
let surface = self
.native
.borrow_mut()
.create_surface(args)
.map_err(SurfaceCreationError::NativeSurfaceCreationFailed)?;
EGLSurface::new(
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");
x
})
.map_err(SurfaceCreationError::EGLSurfaceCreationFailed)
}
/// 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") {
return Err(Error::EglExtensionNotSupported(&["EGL_WL_bind_wayland_display"]));
}
let res = unsafe { ffi::egl::BindWaylandDisplayWL(**self.display, display.c_ptr() as *mut _) };
if res == 0 {
return Err(Error::OtherEGLDisplayAlreadyBound);
}
wrap_egl_call(|| unsafe {
ffi::egl::BindWaylandDisplayWL(**self.display, display.c_ptr() as *mut _)
})
.map_err(Error::OtherEGLDisplayAlreadyBound)?;
Ok(EGLBufferReader::new(self.display.clone(), display.c_ptr()))
}
}
@ -469,16 +485,16 @@ impl EGLBufferReader {
buffer: WlBuffer,
) -> ::std::result::Result<EGLImages, BufferAccessError> {
let mut format: i32 = 0;
if unsafe {
wrap_egl_call(|| unsafe {
ffi::egl::QueryWaylandBufferWL(
**self.display,
buffer.as_ref().c_ptr() as _,
ffi::egl::EGL_TEXTURE_FORMAT,
&mut format,
) == 0
} {
return Err(BufferAccessError::NotManaged(buffer));
}
)
})
.map_err(|source| BufferAccessError::NotManaged(buffer.clone(), source))?;
let format = match format {
x if x == ffi::egl::TEXTURE_RGB as i32 => Format::RGB,
x if x == ffi::egl::TEXTURE_RGBA as i32 => Format::RGBA,
@ -490,40 +506,37 @@ impl EGLBufferReader {
};
let mut width: i32 = 0;
if unsafe {
wrap_egl_call(|| unsafe {
ffi::egl::QueryWaylandBufferWL(
**self.display,
buffer.as_ref().c_ptr() as _,
ffi::egl::WIDTH as i32,
&mut width,
) == 0
} {
return Err(BufferAccessError::NotManaged(buffer));
}
)
})
.map_err(|source| BufferAccessError::NotManaged(buffer.clone(), source))?;
let mut height: i32 = 0;
if unsafe {
wrap_egl_call(|| unsafe {
ffi::egl::QueryWaylandBufferWL(
**self.display,
buffer.as_ref().c_ptr() as _,
ffi::egl::HEIGHT as i32,
&mut height,
) == 0
} {
return Err(BufferAccessError::NotManaged(buffer));
}
)
})
.map_err(|source| BufferAccessError::NotManaged(buffer.clone(), source))?;
let mut inverted: i32 = 0;
if unsafe {
wrap_egl_call(|| unsafe {
ffi::egl::QueryWaylandBufferWL(
**self.display,
buffer.as_ref().c_ptr() as _,
ffi::egl::WAYLAND_Y_INVERTED_WL,
&mut inverted,
) != 0
} {
inverted = 1;
}
)
})
.map_err(|source| BufferAccessError::NotManaged(buffer.clone(), source))?;
let mut images = Vec::with_capacity(format.num_planes());
for i in 0..format.num_planes() {
@ -533,7 +546,7 @@ impl EGLBufferReader {
out.push(ffi::egl::NONE as i32);
images.push({
let image = unsafe {
wrap_egl_call(|| unsafe {
ffi::egl::CreateImageKHR(
**self.display,
ffi::egl::NO_CONTEXT,
@ -541,12 +554,8 @@ impl EGLBufferReader {
buffer.as_ref().c_ptr() as *mut _,
out.as_ptr(),
)
};
if image == ffi::egl::NO_IMAGE_KHR {
return Err(BufferAccessError::EGLImageCreationFailed);
} else {
image
}
})
.map_err(BufferAccessError::EGLImageCreationFailed)?
});
}
@ -601,6 +610,7 @@ impl Drop for EGLBufferReader {
fn drop(&mut self) {
if !self.wayland.is_null() {
unsafe {
// ignore errors on drop
ffi::egl::UnbindWaylandDisplayWL(**self.display, self.wayland as _);
}
}

View File

@ -1,3 +1,5 @@
use super::ffi;
#[derive(thiserror::Error, Debug)]
/// EGL errors
pub enum Error {
@ -5,8 +7,8 @@ pub enum Error {
#[error("The requested OpenGL version {0:?} is not supported")]
OpenGlVersionNotSupported((u8, u8)),
/// The EGL implementation does not support creating OpenGL ES contexts
#[error("The EGL implementation does not support creating OpenGL ES contexts")]
OpenGlesNotSupported,
#[error("The EGL implementation does not support creating OpenGL ES contexts. Err: {0:?}")]
OpenGlesNotSupported(#[source] Option<EGLError>),
/// No available pixel format matched the criteria
#[error("No available pixel format matched the criteria")]
NoAvailablePixelFormat,
@ -14,26 +16,23 @@ pub enum Error {
#[error("The expected backend '{0:?}' does not match the runtime")]
NonMatchingBackend(&'static str),
/// Unable to obtain a valid EGL Display
#[error("Unable to obtain a valid EGL Display")]
DisplayNotSupported,
#[error("Unable to obtain a valid EGL Display. Err: {0:}")]
DisplayNotSupported(#[source] EGLError),
/// `eglInitialize` returned an error
#[error("Failed to initialize EGL")]
InitFailed,
#[error("Failed to initialize EGL. Err: {0:}")]
InitFailed(#[source] EGLError),
/// 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
#[error("Context creation failed as one or more requirements could not be met. Try removing some gl attributes or pixel format requirements")]
CreationFailed,
/// `eglCreateWindowSurface` failed
#[error("`eglCreateWindowSurface` failed")]
SurfaceCreationFailed,
#[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(#[source] EGLError),
/// 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:?}")]
EglExtensionNotSupported(&'static [&'static str]),
/// 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`
#[error("No EGLDisplay is currently bound to this `WlDisplay`")]
NoEGLDisplayBound,
@ -43,7 +42,91 @@ pub enum Error {
/// Failed to create `EGLImages` from the buffer
#[error("Failed to create `EGLImages` from the buffer")]
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),
}
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.
#[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 std::fmt;
#[cfg(feature = "wayland_frontend")]
@ -28,7 +31,7 @@ use wayland_server::{protocol::wl_buffer::WlBuffer, Display};
pub mod context;
pub use self::context::EGLContext;
mod error;
pub use self::error::Error;
pub use self::error::*;
use nix::libc::c_void;
@ -84,11 +87,11 @@ pub enum BufferAccessError {
#[error("The corresponding context was lost")]
ContextLost,
/// This buffer is not managed by the EGL buffer
#[error("This buffer is not managed by EGL")]
NotManaged(WlBuffer),
#[error("This buffer is not managed by EGL. Err: {1:}")]
NotManaged(WlBuffer, #[source] EGLError),
/// Failed to create `EGLImages` from the buffer
#[error("Failed to create EGLImages from the buffer")]
EGLImageCreationFailed,
#[error("Failed to create EGLImages from the buffer. Err: {0:}")]
EGLImageCreationFailed(#[source] EGLError),
/// The required EGL extension is not supported by the underlying EGL implementation
#[error("{0}")]
EglExtensionNotSupported(#[from] EglExtensionNotSupportedError),
@ -99,8 +102,8 @@ impl fmt::Debug for BufferAccessError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> ::std::result::Result<(), fmt::Error> {
match *self {
BufferAccessError::ContextLost => write!(formatter, "BufferAccessError::ContextLost"),
BufferAccessError::NotManaged(_) => write!(formatter, "BufferAccessError::NotManaged"),
BufferAccessError::EGLImageCreationFailed => {
BufferAccessError::NotManaged(_, _) => write!(formatter, "BufferAccessError::NotManaged"),
BufferAccessError::EGLImageCreationFailed(_) => {
write!(formatter, "BufferAccessError::EGLImageCreationFailed")
}
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
#[derive(Debug, Clone, PartialEq, thiserror::Error)]
pub enum TextureCreationError {
@ -249,6 +334,7 @@ impl EGLImages {
impl Drop for EGLImages {
fn drop(&mut self) {
for image in self.images.drain(..) {
// ignore result on drop
unsafe {
ffi::egl::DestroyImageKHR(**self.display, image);
}

View File

@ -1,7 +1,6 @@
//! Type safe native types for safe context/surface creation
use super::{ffi, Error};
use crate::backend::graphics::SwapBuffersError;
use super::{ffi, wrap_egl_call, EGLError, Error};
#[cfg(feature = "backend_winit")]
use std::ptr;
@ -28,7 +27,7 @@ pub trait Backend {
display: ffi::NativeDisplayType,
has_dp_extension: F,
log: ::slog::Logger,
) -> ffi::egl::types::EGLDisplay;
) -> Result<ffi::egl::types::EGLDisplay, EGLError>;
}
#[cfg(feature = "backend_winit")]
@ -42,20 +41,28 @@ impl Backend for Wayland {
display: ffi::NativeDisplayType,
has_dp_extension: F,
log: ::slog::Logger,
) -> ffi::egl::types::EGLDisplay
) -> Result<ffi::egl::types::EGLDisplay, EGLError>
where
F: Fn(&str) -> bool,
{
if has_dp_extension("EGL_KHR_platform_wayland") && ffi::egl::GetPlatformDisplay::is_loaded() {
trace!(log, "EGL Display Initialization via EGL_KHR_platform_wayland");
ffi::egl::GetPlatformDisplay(ffi::egl::PLATFORM_WAYLAND_KHR, display as *mut _, ptr::null())
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()
{
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 {
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,
has_dp_extension: F,
log: ::slog::Logger,
) -> ffi::egl::types::EGLDisplay
) -> Result<ffi::egl::types::EGLDisplay, EGLError>
where
F: Fn(&str) -> bool,
{
if has_dp_extension("EGL_KHR_platform_x11") && ffi::egl::GetPlatformDisplay::is_loaded() {
trace!(log, "EGL Display Initialization via EGL_KHR_platform_x11");
ffi::egl::GetPlatformDisplay(ffi::egl::PLATFORM_X11_KHR, display as *mut _, ptr::null())
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() {
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 {
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.
fn ptr(&self) -> Result<ffi::NativeDisplayType, Error>;
/// 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")]
@ -166,6 +177,9 @@ unsafe impl NativeDisplay<Wayland> for WinitWindow {
/// The returned [`NativeWindowType`](ffi::NativeWindowType) must be valid for EGL
/// and there is no way to test that.
pub unsafe trait NativeSurface {
/// Error of the underlying surface
type Error: std::error::Error;
/// Return a raw pointer egl will accept for surface creation.
fn ptr(&self) -> ffi::NativeWindowType;
@ -184,21 +198,35 @@ pub unsafe trait NativeSurface {
/// Must only be implemented if `needs_recreation` can return `true`.
/// Returns true on success.
/// If this call was successful `ptr()` *should* return something different.
fn recreate(&self) -> bool {
true
fn recreate(&self) -> Result<(), Self::Error> {
Ok(())
}
/// Adds additional semantics when calling
/// [EGLSurface::swap_buffers](::backend::egl::surface::EGLSurface::swap_buffers)
///
/// Only implement if required by the backend.
fn swap_buffers(&self) -> ::std::result::Result<(), SwapBuffersError> {
fn swap_buffers(&self) -> Result<(), Self::Error> {
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")]
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 {
self.0 as *const _
}
@ -206,6 +234,9 @@ unsafe impl NativeSurface for XlibWindow {
#[cfg(feature = "backend_winit")]
unsafe impl NativeSurface for wegl::WlEglSurface {
// type Error = !;
type Error = Never;
fn ptr(&self) -> ffi::NativeWindowType {
self.ptr() as *const _
}

View File

@ -1,8 +1,8 @@
//! 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::graphics::{PixelFormat, SwapBuffersError};
use crate::backend::graphics::PixelFormat;
use nix::libc::c_int;
use std::sync::Arc;
use std::{
@ -41,7 +41,7 @@ impl<N: native::NativeSurface> EGLSurface<N> {
config: ffi::egl::types::EGLConfig,
native: N,
log: L,
) -> Result<EGLSurface<N>, Error>
) -> Result<EGLSurface<N>, EGLError>
where
L: Into<Option<::slog::Logger>>,
{
@ -68,13 +68,9 @@ impl<N: native::NativeSurface> EGLSurface<N> {
out
};
let surface = unsafe {
let surface = wrap_egl_call(|| unsafe {
ffi::egl::CreateWindowSurface(**display, config, native.ptr(), surface_attributes.as_ptr())
};
if surface.is_null() {
return Err(Error::SurfaceCreationFailed);
}
})?;
Ok(EGLSurface {
display,
@ -87,35 +83,43 @@ impl<N: native::NativeSurface> EGLSurface<N> {
}
/// 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();
if !surface.is_null() {
let ret = unsafe { ffi::egl::SwapBuffers(**self.display, surface as *const _) };
if ret == 0 {
match unsafe { ffi::egl::GetError() } as u32 {
ffi::egl::CONTEXT_LOST => return Err(SwapBuffersError::ContextLost),
err => return Err(SwapBuffersError::Unknown(err)),
};
} else {
self.native.swap_buffers()?;
}
let result = if !surface.is_null() {
wrap_egl_call(|| unsafe { ffi::egl::SwapBuffers(**self.display, surface as *const _) })
.map_err(SwapBuffersError::EGLSwapBuffers)
.and_then(|_| self.native.swap_buffers().map_err(SwapBuffersError::Underlying))
} else {
Ok(())
};
if self.native.needs_recreation() || surface.is_null() {
self.native.recreate();
// workaround for missing `PartialEq` impl
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 {
ffi::egl::CreateWindowSurface(
**self.display,
self.config_id,
self.native.ptr(),
self.surface_attributes.as_ptr(),
)
wrap_egl_call(|| {
ffi::egl::CreateWindowSurface(
**self.display,
self.config_id,
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.

View File

@ -4,35 +4,30 @@ use crate::backend::graphics::{gl::GLGraphicsBackend, SwapBuffersError};
use glium::{
backend::{Backend, Context, Facade},
debug::DebugCallbackBehavior,
Frame, SwapBuffersError as GliumSwapBuffersError,
SwapBuffersError as GliumSwapBuffersError,
};
use std::{
cell::{Ref, RefCell, RefMut},
cell::{Cell, Ref, RefCell, RefMut},
os::raw::c_void,
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
pub struct GliumGraphicsBackend<T: GLGraphicsBackend> {
context: Rc<Context>,
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> {
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 {
// cannot fail
@ -40,6 +35,7 @@ impl<T: GLGraphicsBackend + 'static> GliumGraphicsBackend<T> {
Context::new(internal.clone(), true, DebugCallbackBehavior::default()).unwrap()
},
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.
#[inline]
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.
@ -88,7 +87,22 @@ impl<T: GLGraphicsBackend + 'static> From<T> for GliumGraphicsBackend<T> {
unsafe impl<T: GLGraphicsBackend> Backend for InternalBackend<T> {
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 {
@ -104,6 +118,126 @@ unsafe impl<T: GLGraphicsBackend> Backend for InternalBackend<T> {
}
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")
}
}
/// 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;
/// Error that can happen when swapping buffers.
#[derive(Debug, Clone, PartialEq, thiserror::Error)]
#[derive(Debug, thiserror::Error)]
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.
///
/// This error can be returned when `swap_buffers` has been called multiple times
/// without any modification in between.
#[error("Buffers are already swapped, swap_buffers was called too many times")]
AlreadySwapped,
/// Unknown error
#[error("Unknown error: {0:x}")]
Unknown(u32),
/// 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. 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::get_proc_address;
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},
input::{
Axis, AxisSource, Event as BackendEvent, InputBackend, InputHandler, KeyState, KeyboardKeyEvent,
@ -16,6 +16,7 @@ use nix::libc::c_void;
use std::{
cell::{Ref, RefCell},
cmp,
convert::TryInto,
rc::Rc,
time::Instant,
};
@ -47,6 +48,9 @@ pub enum Error {
/// EGL error
#[error("EGL error: {0}")]
EGL(#[from] EGLError),
/// Surface Creation failed
#[error("Surface creation failed: {0}")]
SurfaceCreationError(#[from] SurfaceCreationError<EGLError>),
}
enum Window {
@ -277,8 +281,10 @@ impl GLGraphicsBackend for WinitGraphicsBackend {
fn swap_buffers(&self) -> ::std::result::Result<(), SwapBuffersError> {
trace!(self.logger, "Swapping buffers");
match *self.window {
Window::Wayland { ref surface, .. } => surface.swap_buffers(),
Window::X11 { ref surface, .. } => surface.swap_buffers(),
Window::Wayland { ref surface, .. } => {
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 context,
..
} => context.make_current_with_surface(surface),
} => context.make_current_with_surface(surface).map_err(Into::into),
Window::X11 {
ref surface,
ref context,
..
} => context.make_current_with_surface(surface),
} => context.make_current_with_surface(surface).map_err(Into::into),
}
}