Restructure drm backend

- Remove the `Device`, `Surface`, `RawDevice` and `RawSurface` traits.
  We are not trying to merge the different responsibilities between...
  - Drm-Surfaces: crtcs and planes for displaying buffers
  - Gbm-Surfaces: allocating and managing front- and back-buffers
  - EGL-Surfaces: Load drawing apis.
  ..anymore.
- Instead unify the legacy and atomic backends into one `DrmDevice` and
  a `DrmSurface`, that under the hood dispatches onto the different modules.
- Do not keep a list of surfaces inside the devices. These are reference-counting
  nightmares and a frequent issue due to the read-only nature of `Rc` and `Arc` and
  thread-safety related problems.
- Surfaces are mostly doing state-tracking independently of each other
  and contain some more helper functions.
- Simplify session code, because we do not need to pass the events
  through three layers of devices and surfaces anymore.
- Each surface now represents a plane. Rendering to a plane is just like
  any other rendering step, including the Cursor-plane.
  (Legacy interfaces only have one to avoid dealing with short-comings of the api).
This commit is contained in:
Victor Brekenfeld 2021-04-07 00:16:46 +02:00
parent c388a502c4
commit d606165088
27 changed files with 1581 additions and 6276 deletions

View File

@ -1,169 +0,0 @@
#![warn(rust_2018_idioms)]
#[macro_use]
extern crate slog;
use glium::Surface as GliumSurface;
use slog::Drain;
use smithay::{
backend::{
drm::{
atomic::{AtomicDrmDevice, AtomicDrmSurface},
common::fallback::{EitherError, FallbackDevice, FallbackSurface},
common::Error as DrmError,
device_bind,
egl::{EglDevice, EglSurface, Error as EglError},
eglstream::{
egl::EglStreamDeviceBackend, EglStreamDevice, EglStreamSurface, Error as EglStreamError,
},
legacy::{LegacyDrmDevice, LegacyDrmSurface},
Device, DeviceHandler,
},
graphics::glium::GliumGraphicsBackend,
},
reexports::{
calloop::EventLoop,
drm::control::{connector::State as ConnectorState, crtc},
},
};
use std::{
fs::{File, OpenOptions},
io::Error as IoError,
os::unix::io::{AsRawFd, RawFd},
rc::Rc,
sync::Mutex,
};
pub struct ClonableFile {
file: File,
}
impl AsRawFd for ClonableFile {
fn as_raw_fd(&self) -> RawFd {
self.file.as_raw_fd()
}
}
impl Clone for ClonableFile {
fn clone(&self) -> Self {
ClonableFile {
file: self.file.try_clone().expect("Unable to clone file"),
}
}
}
fn main() {
let log = slog::Logger::root(Mutex::new(slog_term::term_full().fuse()).fuse(), o!());
/*
* Initialize the drm backend
*/
// "Find" a suitable drm device
let mut options = OpenOptions::new();
options.read(true);
options.write(true);
let file = ClonableFile {
file: options.open("/dev/dri/card1").expect("Failed to open card1"),
};
let mut device = EglDevice::new(
EglStreamDevice::new(
FallbackDevice::<AtomicDrmDevice<_>, LegacyDrmDevice<_>>::new(file, true, log.clone())
.expect("Failed to initialize drm device"),
log.clone(),
)
.expect("Failed to initialize egl stream device"),
log.clone(),
)
.expect("Failed to initialize egl device");
// Get a set of all modesetting resource handles (excluding planes):
let res_handles = Device::resource_handles(&device).unwrap();
// Use first connected connector
let connector_info = res_handles
.connectors()
.iter()
.map(|conn| Device::get_connector_info(&device, *conn).unwrap())
.find(|conn| conn.state() == ConnectorState::Connected)
.unwrap();
println!("Conn: {:?}", connector_info.interface());
// Use the first encoder
let encoder_info =
Device::get_encoder_info(&device, connector_info.encoders()[0].expect("expected encoder")).unwrap();
// use the connected crtc if any
let crtc = encoder_info
.crtc()
// or use the first one that is compatible with the encoder
.unwrap_or_else(|| res_handles.filter_crtcs(encoder_info.possible_crtcs())[0]);
println!("Crtc {:?}", crtc);
// Assuming we found a good connector and loaded the info into `connector_info`
let mode = connector_info.modes()[0]; // Use first mode (usually highest resolution, but in reality you should filter and sort and check and match with other connectors, if you use more then one.)
println!("Mode: {:?}", mode);
// Initialize the hardware backend
let surface = device
.create_surface(crtc, mode, &[connector_info.handle()])
.expect("Failed to create surface");
let backend: Rc<GliumGraphicsBackend<_>> = Rc::new(surface.into());
device.set_handler(DrmHandlerImpl {
surface: backend.clone(),
});
/*
* Register the DrmDevice on the EventLoop
*/
let mut event_loop = EventLoop::<()>::new().unwrap();
let _source = device_bind(&event_loop.handle(), device)
.map_err(|err| -> IoError { err.into() })
.unwrap();
// Start rendering
{
if let Err(smithay::backend::graphics::SwapBuffersError::ContextLost(err)) = backend.draw().finish() {
println!("{}", err);
return;
};
}
// Run
event_loop.run(None, &mut (), |_| {}).unwrap();
}
pub struct DrmHandlerImpl {
surface: Rc<
GliumGraphicsBackend<
EglSurface<
EglStreamSurface<
FallbackSurface<AtomicDrmSurface<ClonableFile>, LegacyDrmSurface<ClonableFile>>,
>,
>,
>,
>,
}
impl DeviceHandler for DrmHandlerImpl {
type Device = EglDevice<
EglStreamDeviceBackend<FallbackDevice<AtomicDrmDevice<ClonableFile>, LegacyDrmDevice<ClonableFile>>>,
EglStreamDevice<FallbackDevice<AtomicDrmDevice<ClonableFile>, LegacyDrmDevice<ClonableFile>>>,
>;
fn vblank(&mut self, _crtc: crtc::Handle) {
{
println!("Vblank");
let mut frame = self.surface.draw();
frame.clear(None, Some((0.8, 0.8, 0.9, 1.0)), false, Some(1.0), None);
if let Err(smithay::backend::graphics::SwapBuffersError::ContextLost(err)) = frame.finish() {
panic!("Failed to swap: {}", err);
}
}
}
fn error(&mut self, error: EglError<EglStreamError<EitherError<DrmError, DrmError>>>) {
panic!("{:?}", error);
}
}

View File

@ -1,528 +0,0 @@
//!
//! [`RawDevice`](RawDevice) and [`RawSurface`](RawSurface)
//! implementations using the atomic mode-setting infrastructure.
//!
//! Atomic mode-setting (previously referred to a nuclear page-flip) is a new api of the Direct Rendering
//! Manager subsystem of the linux kernel. Adaptations of this api can be found in BSD kernels.
//!
//! This api is objectively better than the outdated legacy-api, but not supported by every driver.
//! Initialization will fail, if the api is unsupported. The legacy-api is also wrapped by smithay
//! and may be used instead in these cases. Currently there are no features in smithay that are
//! exclusive to the atomic api.
//!
//! Usually this implementation will wrapped into a [`GbmDevice`](::backend::drm::gbm::GbmDevice).
//! Take a look at `anvil`s source code for an example of this.
//!
//! For an example how to use this standalone, take a look at the raw_atomic_drm example.
//!
//! For detailed overview of these abstractions take a look at the module documentation of backend::drm.
//!
use std::cell::RefCell;
use std::collections::HashMap;
use std::fmt;
use std::os::unix::io::{AsRawFd, RawFd};
use std::rc::Rc;
use std::sync::{
atomic::{AtomicBool, Ordering},
Arc, Weak,
};
use drm::control::{atomic::AtomicModeReq, AtomicCommitFlags, Device as ControlDevice, Event};
use drm::control::{
connector, crtc, encoder, framebuffer, plane, property, Mode, PropertyValueSet, ResourceHandle,
ResourceHandles,
};
use drm::SystemError as DrmError;
use drm::{ClientCapability, Device as BasicDevice};
use failure::{Fail, ResultExt};
use nix::libc::dev_t;
use nix::sys::stat::fstat;
use super::{common::Error, DevPath, Device, DeviceHandler, RawDevice};
mod surface;
pub use self::surface::AtomicDrmSurface;
use self::surface::AtomicDrmSurfaceInternal;
#[cfg(feature = "backend_session")]
pub mod session;
/// Open raw drm device utilizing atomic mode-setting
pub struct AtomicDrmDevice<A: AsRawFd + 'static> {
dev: Arc<Dev<A>>,
dev_id: dev_t,
active: Arc<AtomicBool>,
backends: Rc<RefCell<HashMap<crtc::Handle, Weak<AtomicDrmSurfaceInternal<A>>>>>,
handler: Option<RefCell<Box<dyn DeviceHandler<Device = AtomicDrmDevice<A>>>>>,
#[cfg(feature = "backend_session")]
links: Vec<crate::signaling::SignalToken>,
logger: ::slog::Logger,
}
// DeviceHandler does not implement Debug, so we have to impl Debug manually
impl<A: AsRawFd + fmt::Debug + 'static> fmt::Debug for AtomicDrmDevice<A> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut debug = f.debug_struct("AtomicDrmDevice");
debug
.field("dev", &self.dev)
.field("dev_id", &self.dev_id)
.field("active", &self.active)
.field("backends", &self.backends)
.field("handler", &"...");
#[cfg(feature = "backend_session")]
debug.field("links", &self.links);
debug.field("logger", &self.logger).finish()
}
}
type OldState = (
Vec<(connector::Handle, PropertyValueSet)>,
Vec<(crtc::Handle, PropertyValueSet)>,
Vec<(framebuffer::Handle, PropertyValueSet)>,
Vec<(plane::Handle, PropertyValueSet)>,
);
type Mapping = (
HashMap<connector::Handle, HashMap<String, property::Handle>>,
HashMap<crtc::Handle, HashMap<String, property::Handle>>,
HashMap<framebuffer::Handle, HashMap<String, property::Handle>>,
HashMap<plane::Handle, HashMap<String, property::Handle>>,
);
#[derive(Debug)]
pub(in crate::backend::drm) struct Dev<A: AsRawFd + 'static> {
fd: A,
privileged: bool,
active: Arc<AtomicBool>,
old_state: OldState,
prop_mapping: Mapping,
logger: ::slog::Logger,
}
impl<A: AsRawFd + 'static> AsRawFd for Dev<A> {
fn as_raw_fd(&self) -> RawFd {
self.fd.as_raw_fd()
}
}
impl<A: AsRawFd + 'static> BasicDevice for Dev<A> {}
impl<A: AsRawFd + 'static> ControlDevice for Dev<A> {}
impl<A: AsRawFd + 'static> Drop for Dev<A> {
fn drop(&mut self) {
info!(self.logger, "Dropping device: {:?}", self.dev_path());
if self.active.load(Ordering::SeqCst) {
// Here we restore the card/tty's to it's previous state.
// In case e.g. getty was running on the tty sets the correct framebuffer again,
// so that getty will be visible.
// We do exit correctly if this fails, but the user will be presented with
// a black screen if no display handler takes control again.
// create an atomic mode request consisting of all properties we captured on creation.
let mut req = AtomicModeReq::new();
fn add_multiple_props<T: ResourceHandle>(
req: &mut AtomicModeReq,
old_state: &[(T, PropertyValueSet)],
) {
for (handle, set) in old_state {
let (prop_handles, values) = set.as_props_and_values();
for (&prop_handle, &val) in prop_handles.iter().zip(values.iter()) {
req.add_raw_property((*handle).into(), prop_handle, val);
}
}
};
add_multiple_props(&mut req, &self.old_state.0);
add_multiple_props(&mut req, &self.old_state.1);
add_multiple_props(&mut req, &self.old_state.2);
add_multiple_props(&mut req, &self.old_state.3);
if let Err(err) = self.atomic_commit(&[AtomicCommitFlags::AllowModeset], req) {
error!(self.logger, "Failed to restore previous state. Error: {}", err);
}
}
if self.privileged {
if let Err(err) = self.release_master_lock() {
error!(self.logger, "Failed to drop drm master state. Error: {}", err);
}
}
}
}
impl<A: AsRawFd + 'static> Dev<A> {
// Add all properties of given handles to a given drm resource type to state.
// You may use this to snapshot the current state of the drm device (fully or partially).
fn add_props<T>(&self, handles: &[T], state: &mut Vec<(T, PropertyValueSet)>) -> Result<(), Error>
where
A: AsRawFd + 'static,
T: ResourceHandle,
{
let iter = handles.iter().map(|x| (x, self.get_properties(*x)));
if let Some(len) = iter.size_hint().1 {
state.reserve_exact(len)
}
iter.map(|(x, y)| (*x, y))
.try_for_each(|(x, y)| match y {
Ok(y) => {
state.push((x, y));
Ok(())
}
Err(err) => Err(err),
})
.compat()
.map_err(|source| Error::Access {
errmsg: "Error reading properties",
dev: self.dev_path(),
source,
})
}
/// Create a mapping of property names and handles for given handles of a given drm resource type.
/// You may use this to easily lookup properties by name instead of going through this procedure manually.
fn map_props<T>(
&self,
handles: &[T],
mapping: &mut HashMap<T, HashMap<String, property::Handle>>,
) -> Result<(), Error>
where
A: AsRawFd + 'static,
T: ResourceHandle + Eq + std::hash::Hash,
{
handles
.iter()
.map(|x| (x, self.get_properties(*x)))
.try_for_each(|(handle, props)| {
let mut map = HashMap::new();
match props {
Ok(props) => {
let (prop_handles, _) = props.as_props_and_values();
for prop in prop_handles {
if let Ok(info) = self.get_property(*prop) {
let name = info.name().to_string_lossy().into_owned();
map.insert(name, *prop);
}
}
mapping.insert(*handle, map);
Ok(())
}
Err(err) => Err(err),
}
})
.compat()
.map_err(|source| Error::Access {
errmsg: "Error reading properties on {:?}",
dev: self.dev_path(),
source,
})
}
}
impl<A: AsRawFd + 'static> AtomicDrmDevice<A> {
/// Create a new [`AtomicDrmDevice`] from an open drm node
///
/// # 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>>,
{
let log = crate::slog_or_fallback(logger).new(o!("smithay_module" => "backend_drm"));
info!(log, "AtomicDrmDevice initializing");
let dev_id = fstat(fd.as_raw_fd()).map_err(Error::UnableToGetDeviceId)?.st_rdev;
// we wrap some of the internal states in another struct to share with
// the surfaces and event loop handlers.
let active = Arc::new(AtomicBool::new(true));
let mut dev = Dev {
fd,
privileged: true,
active: active.clone(),
old_state: (Vec::new(), Vec::new(), Vec::new(), Vec::new()),
prop_mapping: (HashMap::new(), HashMap::new(), HashMap::new(), HashMap::new()),
logger: log.clone(),
};
// we need to be the master to do modesetting if we run via a tty session.
// This is only needed on older kernels. Newer kernels grant this permission,
// if no other process is already the *master*. So we skip over this error.
if dev.acquire_master_lock().is_err() {
warn!(log, "Unable to become drm master, assuming unprivileged mode");
dev.privileged = false;
};
// Enable the features we need.
// We technically could use the atomic api without universal plane support.
// But the two are almost exclusively implemented together, as plane synchronization
// is one of the killer-features of the atomic api.
//
// We could bould more abstractions in smithay for devices with partial support,
// but for now we role with the oldest possible api (legacy) and the newest feature set
// we can use (atomic + universal planes), although we barely use planes yet.
dev.set_client_capability(ClientCapability::UniversalPlanes, true)
.compat()
.map_err(|source| Error::Access {
errmsg: "Error enabling UniversalPlanes",
dev: dev.dev_path(),
source,
})?;
dev.set_client_capability(ClientCapability::Atomic, true)
.compat()
.map_err(|source| Error::Access {
errmsg: "Error enabling AtomicModesetting",
dev: dev.dev_path(),
source,
})?;
// Enumerate (and save) the current device state.
let res_handles = ControlDevice::resource_handles(&dev)
.compat()
.map_err(|source| Error::Access {
errmsg: "Error loading drm resources",
dev: dev.dev_path(),
source,
})?;
let plane_handles = dev.plane_handles().compat().map_err(|source| Error::Access {
errmsg: "Error loading planes",
dev: dev.dev_path(),
source,
})?;
let planes = plane_handles.planes();
let mut old_state = dev.old_state.clone();
let mut mapping = dev.prop_mapping.clone();
// This helper function takes a snapshot of the current device properties.
// (everything in the atomic api is set via properties.)
dev.add_props(res_handles.connectors(), &mut old_state.0)?;
dev.add_props(res_handles.crtcs(), &mut old_state.1)?;
dev.add_props(res_handles.framebuffers(), &mut old_state.2)?;
dev.add_props(planes, &mut old_state.3)?;
// And because the mapping is not consistent across devices,
// we also need to lookup the handle for a property name.
// And we do this a fair bit, so lets cache that mapping.
dev.map_props(res_handles.connectors(), &mut mapping.0)?;
dev.map_props(res_handles.crtcs(), &mut mapping.1)?;
dev.map_props(res_handles.framebuffers(), &mut mapping.2)?;
dev.map_props(planes, &mut mapping.3)?;
dev.old_state = old_state;
dev.prop_mapping = mapping;
trace!(log, "Mapping: {:#?}", dev.prop_mapping);
// If the user does not explicitly requests us to skip this,
// we clear out the complete connector<->crtc mapping on device creation.
//
// The reason is, that certain operations may be racy otherwise. Surfaces can
// exist on different threads: as a result, we cannot really enumerate the current state
// (it might be changed on another thread during the enumeration). And commits can fail,
// if e.g. a connector is already bound to another surface, which is difficult to analyse at runtime.
//
// An easy workaround is to set a known state on device creation, so we can only
// run into these errors on our own and not because previous compositors left the device
// in a funny state.
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: Arc::new(dev),
dev_id,
active,
backends: Rc::new(RefCell::new(HashMap::new())),
handler: None,
#[cfg(feature = "backend_session")]
links: Vec::new(),
logger: log.clone(),
})
}
}
impl<A: AsRawFd + 'static> AsRawFd for AtomicDrmDevice<A> {
fn as_raw_fd(&self) -> RawFd {
self.dev.as_raw_fd()
}
}
impl<A: AsRawFd + 'static> BasicDevice for AtomicDrmDevice<A> {}
impl<A: AsRawFd + 'static> ControlDevice for AtomicDrmDevice<A> {}
impl<A: AsRawFd + 'static> Device for AtomicDrmDevice<A> {
type Surface = AtomicDrmSurface<A>;
fn device_id(&self) -> dev_t {
self.dev_id
}
fn set_handler(&mut self, handler: impl DeviceHandler<Device = Self> + 'static) {
self.handler = Some(RefCell::new(Box::new(handler)));
}
fn clear_handler(&mut self) {
let _ = self.handler.take();
}
fn create_surface(
&mut self,
crtc: crtc::Handle,
mode: Mode,
connectors: &[connector::Handle],
) -> Result<AtomicDrmSurface<A>, Error> {
if self.backends.borrow().contains_key(&crtc) {
return Err(Error::CrtcAlreadyInUse(crtc));
}
if !self.active.load(Ordering::SeqCst) {
return Err(Error::DeviceInactive);
}
if connectors.is_empty() {
return Err(Error::SurfaceWithoutConnectors(crtc));
}
let backend = Arc::new(AtomicDrmSurfaceInternal::new(
self.dev.clone(),
crtc,
mode,
connectors,
self.logger.new(o!("crtc" => format!("{:?}", crtc))),
)?);
self.backends.borrow_mut().insert(crtc, Arc::downgrade(&backend));
Ok(AtomicDrmSurface(backend))
}
fn process_events(&mut self) {
match self.receive_events() {
Ok(events) => {
for event in events {
if let Event::PageFlip(event) = event {
trace!(self.logger, "Got a page-flip event for crtc ({:?})", event.crtc);
if self
.backends
.borrow()
.get(&event.crtc)
.iter()
.flat_map(|x| x.upgrade())
.next()
.is_some()
{
trace!(self.logger, "Handling event for backend {:?}", event.crtc);
if let Some(handler) = self.handler.as_ref() {
handler.borrow_mut().vblank(event.crtc);
}
} else {
self.backends.borrow_mut().remove(&event.crtc);
}
} else {
trace!(
self.logger,
"Got a non-page-flip event of device '{:?}'.",
self.dev_path()
);
}
}
}
Err(source) => {
if let Some(handler) = self.handler.as_ref() {
handler.borrow_mut().error(Error::Access {
errmsg: "Error processing drm events",
dev: self.dev_path(),
source: source.compat(),
});
}
}
}
}
fn resource_handles(&self) -> Result<ResourceHandles, Error> {
ControlDevice::resource_handles(self)
.compat()
.map_err(|source| Error::Access {
errmsg: "Error loading resource info",
dev: self.dev_path(),
source,
})
}
fn get_connector_info(&self, conn: connector::Handle) -> Result<connector::Info, DrmError> {
self.get_connector(conn)
}
fn get_crtc_info(&self, crtc: crtc::Handle) -> Result<crtc::Info, DrmError> {
self.get_crtc(crtc)
}
fn get_encoder_info(&self, enc: encoder::Handle) -> Result<encoder::Info, DrmError> {
self.get_encoder(enc)
}
fn get_framebuffer_info(&self, fb: framebuffer::Handle) -> Result<framebuffer::Info, DrmError> {
self.get_framebuffer(fb)
}
fn get_plane_info(&self, plane: plane::Handle) -> Result<plane::Info, DrmError> {
self.get_plane(plane)
}
}
impl<A: AsRawFd + 'static> RawDevice for AtomicDrmDevice<A> {
type Surface = AtomicDrmSurface<A>;
}
impl<A: AsRawFd + 'static> Drop for AtomicDrmDevice<A> {
fn drop(&mut self) {
self.clear_handler();
}
}

View File

@ -1,216 +0,0 @@
//!
//! Support to register an open [`AtomicDrmDevice`](AtomicDrmDevice)
//! to an open [`Session`](::backend::session::Session).
//!
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;
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, Weak as WeakArc};
use super::{surface::CursorState, AtomicDrmDevice, AtomicDrmSurfaceInternal, Dev};
use crate::backend::drm::{common::Error, DevPath, Surface};
use crate::{
backend::session::Signal as SessionSignal,
signaling::{Linkable, Signaler},
};
/// [`SessionObserver`](SessionObserver)
/// linked to the [`AtomicDrmDevice`](AtomicDrmDevice)
/// it was created from.
#[derive(Debug)]
pub struct AtomicDrmDeviceObserver<A: AsRawFd + 'static> {
dev: WeakArc<Dev<A>>,
dev_id: dev_t,
privileged: bool,
active: Arc<AtomicBool>,
backends: Weak<RefCell<HashMap<crtc::Handle, WeakArc<AtomicDrmSurfaceInternal<A>>>>>,
logger: ::slog::Logger,
}
impl<A: AsRawFd + 'static> Linkable<SessionSignal> for AtomicDrmDevice<A> {
fn link(&mut self, signaler: Signaler<SessionSignal>) {
let mut observer = AtomicDrmDeviceObserver {
dev: Arc::downgrade(&self.dev),
dev_id: self.dev_id,
active: self.active.clone(),
privileged: self.dev.privileged,
backends: Rc::downgrade(&self.backends),
logger: self.logger.clone(),
};
let token = signaler.register(move |signal| observer.signal(*signal));
self.links.push(token);
}
}
impl<A: AsRawFd + 'static> AtomicDrmDeviceObserver<A> {
fn signal(&mut self, signal: SessionSignal) {
match signal {
SessionSignal::PauseSession => self.pause(None),
SessionSignal::PauseDevice { major, minor } => self.pause(Some((major, minor))),
SessionSignal::ActivateSession => self.activate(None),
SessionSignal::ActivateDevice { major, minor, new_fd } => {
self.activate(Some((major, minor, new_fd)))
}
}
}
fn pause(&mut self, devnum: Option<(u32, u32)>) {
if let Some((major, minor)) = devnum {
if major as u64 != stat::major(self.dev_id) || minor as u64 != stat::minor(self.dev_id) {
return;
}
}
// TODO: Clear overlay planes (if we ever use them)
if let Some(backends) = self.backends.upgrade() {
for surface in backends.borrow().values().filter_map(WeakArc::upgrade) {
// other ttys that use no cursor, might not clear it themselves.
// This makes sure our cursor won't stay visible.
//
// This usually happens with getty, and a cursor on top of a kernel console looks very weird.
if let Err(err) = surface.clear_plane(surface.planes.cursor) {
warn!(
self.logger,
"Failed to clear cursor on {:?}: {}", surface.planes.cursor, err
);
}
}
}
self.active.store(false, Ordering::SeqCst);
if self.privileged {
if let Some(device) = self.dev.upgrade() {
if let Err(err) = device.release_master_lock() {
error!(self.logger, "Failed to drop drm master state Error: {}", err);
}
}
}
}
fn activate(&mut self, devnum: Option<(u32, u32, Option<RawFd>)>) {
if let Some((major, minor, fd)) = devnum {
if major as u64 != stat::major(self.dev_id) || minor as u64 != stat::minor(self.dev_id) {
return;
} else if let Some(fd) = fd {
info!(self.logger, "Replacing fd");
if let Some(device) = self.dev.upgrade() {
::nix::unistd::dup2(device.as_raw_fd(), fd)
.expect("Failed to replace file descriptor of drm device");
}
}
}
if self.privileged {
if let Some(device) = self.dev.upgrade() {
if let Err(err) = device.acquire_master_lock() {
crit!(self.logger, "Failed to acquire drm master again. Error: {}", err);
}
}
}
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
}
}
fn reset_state(&mut self) -> Result<(), Error> {
// reset state sets the connectors into a known state (all disabled),
// for the same reasons we do this on device creation.
//
// We might end up with conflicting commit requirements, if we want to restore our state,
// on top of the state the previous compositor left the device in.
// This is because we do commits per surface and not per device, so we do a global
// commit here, to fix any conflicts.
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,
})?;
// because we change the state and disabled everything,
// we want to force a commit (instead of a page-flip) on all used surfaces
// for the next rendering step to re-activate the connectors.
//
// Lets do that, by creating a garbage/non-matching current-state.
if let Some(backends) = self.backends.upgrade() {
for surface in backends.borrow().values().filter_map(WeakArc::upgrade) {
let mut current = surface.state.write().unwrap();
// lets force a non matching state
current.connectors.clear();
current.mode = unsafe { std::mem::zeroed() };
// recreate property blob
let mode = {
let pending = surface.pending.read().unwrap();
pending.mode
};
surface.use_mode(mode)?;
// drop cursor state to force setting the cursor again.
*surface.cursor.lock().unwrap() = CursorState {
position: None,
hotspot: (0, 0),
framebuffer: None,
};
}
}
}
Ok(())
}
}

View File

@ -1,584 +0,0 @@
//!
//! Types to make fallback device initialization easier
//!
// The macros in this module rely on these constructs
#![allow(clippy::needless_arbitrary_self_type)]
#[cfg(feature = "backend_drm_egl")]
use crate::backend::drm::egl::{Arguments as EglDeviceArguments, EglDevice, Error as EglDeviceError};
#[cfg(all(feature = "backend_drm_atomic", feature = "backend_drm_legacy"))]
use crate::backend::drm::{atomic::AtomicDrmDevice, legacy::LegacyDrmDevice};
use crate::backend::drm::{common::Error, Device, DeviceHandler, RawDevice, RawSurface, Surface};
#[cfg(all(feature = "backend_drm_gbm", feature = "backend_drm_eglstream"))]
use crate::backend::drm::{
eglstream::{EglStreamDevice, Error as EglStreamError},
gbm::{Error as GbmError, GbmDevice},
};
#[cfg(feature = "backend_drm_egl")]
use crate::backend::egl::context::{GlAttributes, PixelFormatRequirements};
#[cfg(feature = "backend_drm_egl")]
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};
#[cfg(feature = "renderer_gl")]
use crate::backend::graphics::gl::GLGraphicsBackend;
#[cfg(feature = "renderer_gl")]
use crate::backend::graphics::PixelFormat;
use crate::backend::graphics::{CursorBackend, SwapBuffersError};
use drm::{
control::{connector, crtc, encoder, framebuffer, plane, Device as ControlDevice, Mode, ResourceHandles},
Device as BasicDevice, SystemError as DrmError,
};
#[cfg(feature = "renderer_gl")]
use nix::libc::c_void;
use nix::libc::dev_t;
use std::env;
use std::fmt;
use std::os::unix::io::{AsRawFd, RawFd};
#[cfg(feature = "use_system_lib")]
use wayland_server::Display;
/// [`Device`](::backend::drm::Device) Wrapper to assist fallback
/// in case initialization of the preferred device type fails.
#[derive(Debug)]
pub enum FallbackDevice<D1: Device + 'static, D2: Device + 'static> {
/// Variant for successful initialization of the preferred device
Preference(D1),
/// Variant for the fallback device
Fallback(D2),
}
struct FallbackDeviceHandlerD1<E1, E2, C, S1, S2, D1, D2>(
Box<dyn DeviceHandler<Device = FallbackDevice<D1, D2>> + 'static>,
)
where
E1: std::error::Error + Send + 'static,
E2: std::error::Error + Send + 'static,
C: IntoIterator<Item = connector::Handle> + 'static,
S1: Surface<Error = E1, Connectors = C> + 'static,
S2: Surface<Error = E2, Connectors = C> + 'static,
D1: Device<Surface = S1> + 'static,
D2: Device<Surface = S2> + 'static;
impl<E1, E2, C, S1, S2, D1, D2> DeviceHandler for FallbackDeviceHandlerD1<E1, E2, C, S1, S2, D1, D2>
where
E1: std::error::Error + Send + 'static,
E2: std::error::Error + Send + 'static,
C: IntoIterator<Item = connector::Handle> + 'static,
S1: Surface<Error = E1, Connectors = C> + 'static,
S2: Surface<Error = E2, Connectors = C> + 'static,
D1: Device<Surface = S1> + 'static,
D2: Device<Surface = S2> + 'static,
{
type Device = D1;
fn vblank(&mut self, crtc: crtc::Handle) {
self.0.vblank(crtc)
}
fn error(&mut self, error: E1) {
self.0.error(EitherError::Either(error));
}
}
struct FallbackDeviceHandlerD2<E1, E2, C, S1, S2, D1, D2>(
Box<dyn DeviceHandler<Device = FallbackDevice<D1, D2>> + 'static>,
)
where
E1: std::error::Error + Send + 'static,
E2: std::error::Error + Send + 'static,
C: IntoIterator<Item = connector::Handle> + 'static,
S1: Surface<Error = E1, Connectors = C> + 'static,
S2: Surface<Error = E2, Connectors = C> + 'static,
D1: Device<Surface = S1> + 'static,
D2: Device<Surface = S2> + 'static;
impl<E1, E2, C, S1, S2, D1, D2> DeviceHandler for FallbackDeviceHandlerD2<E1, E2, C, S1, S2, D1, D2>
where
E1: std::error::Error + Send + 'static,
E2: std::error::Error + Send + 'static,
C: IntoIterator<Item = connector::Handle> + 'static,
S1: Surface<Error = E1, Connectors = C> + 'static,
S2: Surface<Error = E2, Connectors = C> + 'static,
D1: Device<Surface = S1> + 'static,
D2: Device<Surface = S2> + 'static,
{
type Device = D2;
fn vblank(&mut self, crtc: crtc::Handle) {
self.0.vblank(crtc)
}
fn error(&mut self, error: E2) {
self.0.error(EitherError::Or(error));
}
}
#[cfg(feature = "backend_session")]
impl<D1, D2> crate::signaling::Linkable<crate::backend::session::Signal> for FallbackDevice<D1, D2>
where
D1: Device + crate::signaling::Linkable<crate::backend::session::Signal> + 'static,
D2: Device + crate::signaling::Linkable<crate::backend::session::Signal> + 'static,
{
fn link(&mut self, signal: crate::signaling::Signaler<crate::backend::session::Signal>) {
match self {
FallbackDevice::Preference(d) => d.link(signal),
FallbackDevice::Fallback(d) => d.link(signal),
}
}
}
/// [`Surface`](::backend::drm::Surface) Wrapper to assist fallback
/// in case initialization of the preferred device type fails.
#[derive(Debug)]
pub enum FallbackSurface<S1: Surface, S2: Surface> {
/// Variant for successful initialization of the preferred device
Preference(S1),
/// Variant for the fallback device
Fallback(S2),
}
/// Enum uniting two kinds of possible errors.
#[derive(Debug, thiserror::Error)]
pub enum EitherError<E1: std::error::Error + 'static, E2: std::error::Error + 'static> {
/// Either this error
#[error("{0}")]
Either(#[source] E1),
/// Or this error
#[error("{0}")]
Or(#[source] E2),
}
impl<E1, E2> Into<SwapBuffersError> for EitherError<E1, E2>
where
E1: std::error::Error + Into<SwapBuffersError> + 'static,
E2: std::error::Error + Into<SwapBuffersError> + 'static,
{
fn into(self) -> SwapBuffersError {
match self {
EitherError::Either(err) => err.into(),
EitherError::Or(err) => err.into(),
}
}
}
#[cfg(all(feature = "backend_drm_atomic", feature = "backend_drm_legacy"))]
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.
///
/// # 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_fallback(logger).new(o!("smithay_module" => "backend_drm_fallback"));
info!(log, "Trying to initialize AtomicDrmDevice");
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) => {
warn!(log, "Failed to initialize preferred AtomicDrmDevice: {}", err);
info!(log, "Falling back to fallback LegacyDrmDevice");
Ok(FallbackDevice::Fallback(LegacyDrmDevice::new(
fd,
disable_connectors,
log,
)?))
}
}
}
}
#[cfg(all(
feature = "backend_drm_gbm",
feature = "backend_drm_eglstream",
feature = "backend_udev"
))]
type GbmOrEglStreamError<D> = EitherError<
GbmError<<<D as Device>::Surface as Surface>::Error>,
EglStreamError<<<D as Device>::Surface as Surface>::Error>,
>;
#[cfg(all(
feature = "backend_drm_gbm",
feature = "backend_drm_eglstream",
feature = "backend_udev"
))]
impl<D> FallbackDevice<GbmDevice<D>, EglStreamDevice<D>>
where
D: RawDevice + ControlDevice + 'static,
{
/// Try to initialize a [`GbmDevice`](::backend::drm::gbm::GbmDevice)
/// or a [`EglStreamDevice`](::backend::drm::eglstream::EglStreamDevice) depending on the used driver.
///
/// # Arguments
///
/// - `dev` - Open drm device (needs implement [`RawDevice`](::backend::drm::RawDevice))
/// - `logger` - Optional [`slog::Logger`] to be used by the resulting device.
///
/// # Return
///
/// Returns an error, if the choosen device fails to initialize.
pub fn new<L>(dev: D, logger: L) -> Result<Self, GbmOrEglStreamError<D>>
where
L: Into<Option<::slog::Logger>>,
{
let log = crate::slog_or_fallback(logger).new(o!("smithay_module" => "backend_drm_fallback"));
let driver = crate::backend::udev::driver(dev.device_id()).expect("Failed to query device");
info!(log, "Drm device driver: {:?}", driver);
if driver.as_ref().and_then(|x| x.to_str()) == Some("nvidia") {
Ok(FallbackDevice::Fallback(
EglStreamDevice::new(dev, log).map_err(EitherError::Or)?,
))
} else {
Ok(FallbackDevice::Preference(
GbmDevice::new(dev, log.clone()).map_err(EitherError::Either)?,
))
}
}
}
#[cfg(feature = "backend_drm_egl")]
type EglUnderlying<D1, D2> = EitherError<
EglDeviceError<<<D1 as Device>::Surface as Surface>::Error>,
EglDeviceError<<<D2 as Device>::Surface as Surface>::Error>,
>;
#[cfg(feature = "backend_drm_egl")]
type FallbackEglDevice<B1, D1, B2, D2> = FallbackDevice<EglDevice<B1, D1>, EglDevice<B2, D2>>;
#[cfg(feature = "backend_drm_egl")]
impl<D1, D2> FallbackDevice<D1, D2>
where
D1: Device + 'static,
<D1 as Device>::Surface: NativeSurface<Error = <<D1 as Device>::Surface as Surface>::Error>,
D2: Device + 'static,
<D2 as Device>::Surface: NativeSurface<Error = <<D2 as Device>::Surface as Surface>::Error>,
{
/// Try to create a new [`EglDevice`] from a [`FallbackDevice`] containing two compatible device types.
///
/// This helper function is necessary as implementing [`NativeDevice`](::backend::egl::native::NativeDevice) for [`FallbackDevice`] is impossible
/// as selecting the appropriate [`Backend`](::backend::egl::native::Backend) would be impossible without knowing
/// the underlying device type, that was selected by [`FallbackDevice`].
///
/// Returns an error if the context creation was not successful.
pub fn new_egl<B1, B2, L>(
dev: FallbackDevice<D1, D2>,
logger: L,
) -> Result<FallbackEglDevice<B1, D1, B2, D2>, EglUnderlying<D1, D2>>
where
B1: Backend<Surface = <D1 as Device>::Surface, Error = <<D1 as Device>::Surface as Surface>::Error>
+ 'static,
D1: NativeDisplay<B1, Arguments = EglDeviceArguments>,
B2: Backend<Surface = <D2 as Device>::Surface, Error = <<D2 as Device>::Surface as Surface>::Error>
+ 'static,
D2: NativeDisplay<B2, Arguments = EglDeviceArguments>,
L: Into<Option<::slog::Logger>>,
{
let log = crate::slog_or_fallback(logger).new(o!("smithay_module" => "backend_drm_fallback"));
match dev {
FallbackDevice::Preference(gbm) => match EglDevice::new(gbm, log) {
Ok(dev) => Ok(FallbackDevice::Preference(dev)),
Err(err) => Err(EglUnderlying::<D1, D2>::Either(err)),
},
FallbackDevice::Fallback(eglstream) => match EglDevice::new(eglstream, log) {
Ok(dev) => Ok(FallbackDevice::Fallback(dev)),
Err(err) => Err(EglUnderlying::<D1, D2>::Or(err)),
},
}
}
/// Try to create a new [`EglDevice`] from a [`FallbackDevice`] containing two compatible device types with
/// the given attributes and requirements as defaults for new surfaces.
///
/// This helper function is necessary as implementing [`NativeDevice`](::backend::egl::native::NativeDevice) for [`FallbackDevice`] is impossible
/// as selecting the appropriate [`Backend`](::backend::egl::native::Backend) would be impossible without knowing
/// the underlying device type, that was selected by [`FallbackDevice`].
///
/// Returns an error if the context creation was not successful.
pub fn new_egl_with_defaults<B1, B2, L>(
dev: FallbackDevice<D1, D2>,
default_attributes: GlAttributes,
default_requirements: PixelFormatRequirements,
logger: L,
) -> Result<FallbackEglDevice<B1, D1, B2, D2>, EglUnderlying<D1, D2>>
where
B1: Backend<Surface = <D1 as Device>::Surface, Error = <<D1 as Device>::Surface as Surface>::Error>
+ 'static,
D1: NativeDisplay<B1, Arguments = EglDeviceArguments>,
B2: Backend<Surface = <D2 as Device>::Surface, Error = <<D2 as Device>::Surface as Surface>::Error>
+ 'static,
D2: NativeDisplay<B2, Arguments = EglDeviceArguments>,
L: Into<Option<::slog::Logger>>,
{
let log = crate::slog_or_fallback(logger).new(o!("smithay_module" => "backend_drm_fallback"));
match dev {
FallbackDevice::Preference(gbm) => {
match EglDevice::new_with_defaults(gbm, default_attributes, default_requirements, log) {
Ok(dev) => Ok(FallbackDevice::Preference(dev)),
Err(err) => Err(EglUnderlying::<D1, D2>::Either(err)),
}
}
FallbackDevice::Fallback(eglstream) => {
match EglDevice::new_with_defaults(eglstream, default_attributes, default_requirements, log) {
Ok(dev) => Ok(FallbackDevice::Fallback(dev)),
Err(err) => Err(EglUnderlying::<D1, D2>::Or(err)),
}
}
}
}
}
macro_rules! fallback_device_impl {
($func_name:ident, $self:ty, $return:ty, $($arg_name:ident : $arg_ty:ty),*) => {
fn $func_name(self: $self, $($arg_name : $arg_ty),*) -> $return {
match self {
FallbackDevice::Preference(dev) => dev.$func_name($($arg_name),*),
FallbackDevice::Fallback(dev) => dev.$func_name($($arg_name),*),
}
}
};
($func_name:ident, $self:ty, $return:ty) => {
fallback_device_impl!($func_name, $self, $return,);
};
($func_name:ident, $self:ty) => {
fallback_device_impl!($func_name, $self, ());
};
}
macro_rules! fallback_device_err_impl {
($func_name:ident, $self:ty, $return:ty, $($arg_name:ident : $arg_ty:ty),*) => {
fn $func_name(self: $self, $($arg_name : $arg_ty),*) -> $return {
match self {
FallbackDevice::Preference(dev) => dev.$func_name($($arg_name),*).map_err(EitherError::Either),
FallbackDevice::Fallback(dev) => dev.$func_name($($arg_name),*).map_err(EitherError::Or),
}
}
};
($func_name:ident, $self:ty, $return:ty) => {
fallback_device_err_impl!($func_name, $self, $return,);
};
}
macro_rules! fallback_surface_impl {
($func_name:ident, $self:ty, $return:ty, $($arg_name:ident : $arg_ty:ty),*) => {
fn $func_name(self: $self, $($arg_name : $arg_ty),*) -> $return {
match self {
FallbackSurface::Preference(dev) => dev.$func_name($($arg_name),*),
FallbackSurface::Fallback(dev) => dev.$func_name($($arg_name),*),
}
}
};
($func_name:ident, $self:ty, $return:ty) => {
fallback_surface_impl!($func_name, $self, $return,);
};
($func_name:ident, $self:ty) => {
fallback_surface_impl!($func_name, $self, ());
};
}
macro_rules! fallback_surface_err_impl {
($func_name:ident, $self:ty, $return:ty, $($arg_name:ident : $arg_ty:ty),*) => {
fn $func_name(self: $self, $($arg_name : $arg_ty),*) -> $return {
match self {
FallbackSurface::Preference(dev) => dev.$func_name($($arg_name),*).map_err(EitherError::Either),
FallbackSurface::Fallback(dev) => dev.$func_name($($arg_name),*).map_err(EitherError::Or),
}
}
};
}
impl<D1: Device, D2: Device> AsRawFd for FallbackDevice<D1, D2> {
fallback_device_impl!(as_raw_fd, &Self, RawFd);
}
impl<D1: Device + BasicDevice, D2: Device + BasicDevice> BasicDevice for FallbackDevice<D1, D2> {}
impl<D1: Device + ControlDevice, D2: Device + ControlDevice> ControlDevice for FallbackDevice<D1, D2> {}
impl<E1, E2, C, S1, S2, D1, D2> Device for FallbackDevice<D1, D2>
where
// Connectors need to match for both Surfaces
E1: std::error::Error + Send + 'static,
E2: std::error::Error + Send + 'static,
C: IntoIterator<Item = connector::Handle> + 'static,
S1: Surface<Error = E1, Connectors = C> + 'static,
S2: Surface<Error = E2, Connectors = C> + 'static,
D1: Device<Surface = S1> + 'static,
D2: Device<Surface = S2> + 'static,
{
type Surface = FallbackSurface<S1, S2>;
fallback_device_impl!(device_id, &Self, dev_t);
fn set_handler(&mut self, handler: impl DeviceHandler<Device = Self> + 'static) {
match self {
FallbackDevice::Preference(dev) => dev.set_handler(FallbackDeviceHandlerD1(Box::new(handler))),
FallbackDevice::Fallback(dev) => dev.set_handler(FallbackDeviceHandlerD2(Box::new(handler))),
}
}
fallback_device_impl!(clear_handler, &mut Self);
fn create_surface(
&mut self,
crtc: crtc::Handle,
mode: Mode,
connectors: &[connector::Handle],
) -> Result<Self::Surface, EitherError<E1, E2>> {
match self {
FallbackDevice::Preference(dev) => Ok(FallbackSurface::Preference(
dev.create_surface(crtc, mode, connectors)
.map_err(EitherError::Either)?,
)),
FallbackDevice::Fallback(dev) => Ok(FallbackSurface::Fallback(
dev.create_surface(crtc, mode, connectors)
.map_err(EitherError::Or)?,
)),
}
}
fallback_device_impl!(process_events, &mut Self);
fallback_device_err_impl!(resource_handles, &Self, Result<ResourceHandles, EitherError<E1, E2>>);
fallback_device_impl!(get_connector_info, &Self, Result<connector::Info, DrmError>, conn: connector::Handle);
fallback_device_impl!(get_crtc_info, &Self, Result<crtc::Info, DrmError>, crtc: crtc::Handle);
fallback_device_impl!(get_encoder_info, &Self, Result<encoder::Info, DrmError>, enc: encoder::Handle);
fallback_device_impl!(get_framebuffer_info, &Self, Result<framebuffer::Info, DrmError>, fb: framebuffer::Handle);
fallback_device_impl!(get_plane_info, &Self, Result<plane::Info, DrmError>, plane : plane::Handle);
}
// Impl RawDevice where underlying types implement RawDevice
impl<E1, E2, C, S1, S2, D1, D2> RawDevice for FallbackDevice<D1, D2>
where
// Connectors need to match for both Surfaces
E1: std::error::Error + Send + 'static,
E2: std::error::Error + Send + 'static,
C: IntoIterator<Item = connector::Handle> + 'static,
S1: RawSurface + Surface<Error = E1, Connectors = C> + 'static,
S2: RawSurface + Surface<Error = E2, Connectors = C> + 'static,
D1: RawDevice<Surface = S1> + 'static,
D2: RawDevice<Surface = S2> + 'static,
{
type Surface = FallbackSurface<S1, S2>;
}
#[cfg(feature = "use_system_lib")]
impl<D1: Device + EGLGraphicsBackend + 'static, D2: Device + EGLGraphicsBackend + 'static> EGLGraphicsBackend
for FallbackDevice<D1, D2>
{
fallback_device_impl!(bind_wl_display, &Self, Result<EGLBufferReader, EGLError>, display : &Display);
}
impl<E1, E2, C, S1, S2> Surface for FallbackSurface<S1, S2>
where
// Connectors need to match for both Surfaces
E1: std::error::Error + Send + 'static,
E2: std::error::Error + Send + 'static,
C: IntoIterator<Item = connector::Handle> + 'static,
S1: Surface<Error = E1, Connectors = C> + 'static,
S2: Surface<Error = E2, Connectors = C> + 'static,
{
type Error = EitherError<E1, E2>;
type Connectors = C;
fallback_surface_impl!(crtc, &Self, crtc::Handle);
fallback_surface_impl!(current_connectors, &Self, C);
fallback_surface_impl!(pending_connectors, &Self, C);
fallback_surface_err_impl!(add_connector, &Self, Result<(), EitherError<E1, E2>>, conn: connector::Handle);
fallback_surface_err_impl!(remove_connector, &Self, Result<(), EitherError<E1, E2>>, conn: connector::Handle);
fallback_surface_err_impl!(set_connectors, &Self, Result<(), EitherError<E1, E2>>, conns: &[connector::Handle]);
fallback_surface_impl!(current_mode, &Self, Mode);
fallback_surface_impl!(pending_mode, &Self, Mode);
fallback_surface_err_impl!(use_mode, &Self, Result<(), EitherError<E1, E2>>, mode: Mode);
}
impl<E1, E2, C, S1, S2> RawSurface for FallbackSurface<S1, S2>
where
E1: std::error::Error + Send + 'static,
E2: std::error::Error + Send + 'static,
C: IntoIterator<Item = connector::Handle> + 'static,
S1: RawSurface + Surface<Error = E1, Connectors = C> + 'static,
S2: RawSurface + Surface<Error = E2, Connectors = C> + 'static,
{
fallback_surface_impl!(commit_pending, &Self, bool);
fallback_surface_err_impl!(commit, &Self, Result<(), EitherError<E1, E2>>, fb: framebuffer::Handle);
fn page_flip(&self, framebuffer: framebuffer::Handle) -> Result<(), EitherError<E1, E2>> {
match self {
FallbackSurface::Preference(dev) => {
RawSurface::page_flip(dev, framebuffer).map_err(EitherError::Either)
}
FallbackSurface::Fallback(dev) => {
RawSurface::page_flip(dev, framebuffer).map_err(EitherError::Or)
}
}
}
}
impl<S1: Surface + AsRawFd, S2: Surface + AsRawFd> AsRawFd for FallbackSurface<S1, S2> {
fallback_surface_impl!(as_raw_fd, &Self, RawFd);
}
impl<S1: Surface + BasicDevice, S2: Surface + BasicDevice> BasicDevice for FallbackSurface<S1, S2> {}
impl<S1: Surface + ControlDevice, S2: Surface + ControlDevice> ControlDevice for FallbackSurface<S1, S2> {}
impl<E1, E2, E3, E4, C, CF, S1, S2> CursorBackend for FallbackSurface<S1, S2>
where
E1: std::error::Error + Send + 'static,
E2: std::error::Error + Send + 'static,
E3: std::error::Error + 'static,
E4: std::error::Error + 'static,
CF: ?Sized,
C: IntoIterator<Item = connector::Handle> + 'static,
S1: Surface<Error = E1, Connectors = C> + CursorBackend<CursorFormat = CF, Error = E3> + 'static,
S2: Surface<Error = E2, Connectors = C> + CursorBackend<CursorFormat = CF, Error = E4> + 'static,
{
type CursorFormat = CF;
type Error = EitherError<E3, E4>;
fallback_surface_err_impl!(set_cursor_position, &Self, Result<(), EitherError<E3, E4>>, x: u32, y: u32);
fallback_surface_err_impl!(set_cursor_representation, &Self, Result<(), EitherError<E3, E4>>, buffer: &Self::CursorFormat, hotspot: (u32, u32));
fallback_surface_err_impl!(clear_cursor_representation, &Self, Result<(), EitherError<E3, E4>>,);
}
#[cfg(feature = "renderer_gl")]
impl<E1, E2, C, S1, S2> GLGraphicsBackend for FallbackSurface<S1, S2>
where
E1: std::error::Error + Send + 'static,
E2: std::error::Error + Send + 'static,
C: IntoIterator<Item = connector::Handle> + 'static,
S1: Surface<Error = E1, Connectors = C> + GLGraphicsBackend + 'static,
S2: Surface<Error = E2, Connectors = C> + GLGraphicsBackend + 'static,
{
fallback_surface_impl!(swap_buffers, &Self, Result<(), SwapBuffersError>);
fallback_surface_impl!(get_proc_address, &Self, *const c_void, symbol: &str);
fallback_surface_impl!(get_framebuffer_dimensions, &Self, (u32, u32));
fallback_surface_impl!(is_current, &Self, bool);
unsafe fn make_current(&self) -> Result<(), SwapBuffersError> {
match self {
FallbackSurface::Preference(dev) => dev.make_current(),
FallbackSurface::Fallback(dev) => dev.make_current(),
}
}
fallback_surface_impl!(get_pixel_format, &Self, PixelFormat);
}

View File

@ -0,0 +1,254 @@
use std::collections::HashMap;
use std::sync::{Arc, atomic::{AtomicBool, Ordering}};
use std::os::unix::io::AsRawFd;
use drm::control::atomic::AtomicModeReq;
use drm::control::{Device as ControlDevice, AtomicCommitFlags, PropertyValueSet, ResourceHandle, crtc, connector, framebuffer, plane, property};
use super::{DevPath, FdWrapper};
use crate::backend::drm::error::Error;
type OldState = (
Vec<(connector::Handle, PropertyValueSet)>,
Vec<(crtc::Handle, PropertyValueSet)>,
Vec<(framebuffer::Handle, PropertyValueSet)>,
Vec<(plane::Handle, PropertyValueSet)>,
);
pub type Mapping = (
HashMap<connector::Handle, HashMap<String, property::Handle>>,
HashMap<crtc::Handle, HashMap<String, property::Handle>>,
HashMap<framebuffer::Handle, HashMap<String, property::Handle>>,
HashMap<plane::Handle, HashMap<String, property::Handle>>,
);
pub struct AtomicDrmDevice<A: AsRawFd + 'static> {
pub(crate) fd: Arc<FdWrapper<A>>,
pub(crate) active: Arc<AtomicBool>,
old_state: OldState,
pub(crate) prop_mapping: Mapping,
logger: ::slog::Logger,
}
impl<A: AsRawFd + 'static> AtomicDrmDevice<A> {
pub fn new(fd: Arc<FdWrapper<A>>, active: Arc<AtomicBool>, disable_connectors: bool, logger: ::slog::Logger) -> Result<Self, Error>
{
let mut dev = AtomicDrmDevice {
fd,
active,
old_state: (Vec::new(), Vec::new(), Vec::new(), Vec::new()),
prop_mapping: (HashMap::new(), HashMap::new(), HashMap::new(), HashMap::new()),
logger: logger.new(o!("smithay_module" => "backend_drm_atomic", "drm_module" => "device")),
};
// Enumerate (and save) the current device state.
let res_handles = dev.fd.resource_handles()
.map_err(|source| Error::Access {
errmsg: "Error loading drm resources",
dev: dev.fd.dev_path(),
source,
})?;
let plane_handles = dev.fd.plane_handles().map_err(|source| Error::Access {
errmsg: "Error loading planes",
dev: dev.fd.dev_path(),
source,
})?;
let planes = plane_handles.planes();
let mut old_state = dev.old_state.clone();
let mut mapping = dev.prop_mapping.clone();
// This helper function takes a snapshot of the current device properties.
// (everything in the atomic api is set via properties.)
dev.add_props(res_handles.connectors(), &mut old_state.0)?;
dev.add_props(res_handles.crtcs(), &mut old_state.1)?;
dev.add_props(res_handles.framebuffers(), &mut old_state.2)?;
dev.add_props(planes, &mut old_state.3)?;
// And because the mapping is not consistent across devices,
// we also need to lookup the handle for a property name.
// And we do this a fair bit, so lets cache that mapping.
dev.map_props(res_handles.connectors(), &mut mapping.0)?;
dev.map_props(res_handles.crtcs(), &mut mapping.1)?;
dev.map_props(res_handles.framebuffers(), &mut mapping.2)?;
dev.map_props(planes, &mut mapping.3)?;
dev.old_state = old_state;
dev.prop_mapping = mapping;
trace!(dev.logger, "Mapping: {:#?}", dev.prop_mapping);
// If the user does not explicitly requests us to skip this,
// we clear out the complete connector<->crtc mapping on device creation.
//
// The reason is, that certain operations may be racy otherwise. Surfaces can
// exist on different threads: as a result, we cannot really enumerate the current state
// (it might be changed on another thread during the enumeration). And commits can fail,
// if e.g. a connector is already bound to another surface, which is difficult to analyse at runtime.
//
// An easy workaround is to set a known state on device creation, so we can only
// run into these errors on our own and not because previous compositors left the device
// in a funny state.
if disable_connectors {
dev.reset_state()?;
}
Ok(dev)
}
// Add all properties of given handles to a given drm resource type to state.
// You may use this to snapshot the current state of the drm device (fully or partially).
fn add_props<T>(&self, handles: &[T], state: &mut Vec<(T, PropertyValueSet)>) -> Result<(), Error>
where
T: ResourceHandle,
{
let iter = handles.iter().map(|x| (x, self.fd.get_properties(*x)));
if let Some(len) = iter.size_hint().1 {
state.reserve_exact(len)
}
iter.map(|(x, y)| (*x, y))
.try_for_each(|(x, y)| match y {
Ok(y) => {
state.push((x, y));
Ok(())
}
Err(err) => Err(err),
})
.map_err(|source| Error::Access {
errmsg: "Error reading properties",
dev: self.fd.dev_path(),
source,
})
}
/// Create a mapping of property names and handles for given handles of a given drm resource type.
/// You may use this to easily lookup properties by name instead of going through this procedure manually.
fn map_props<T>(
&self,
handles: &[T],
mapping: &mut HashMap<T, HashMap<String, property::Handle>>,
) -> Result<(), Error>
where
T: ResourceHandle + Eq + std::hash::Hash,
{
handles
.iter()
.map(|x| (x, self.fd.get_properties(*x)))
.try_for_each(|(handle, props)| {
let mut map = HashMap::new();
match props {
Ok(props) => {
let (prop_handles, _) = props.as_props_and_values();
for prop in prop_handles {
if let Ok(info) = self.fd.get_property(*prop) {
let name = info.name().to_string_lossy().into_owned();
map.insert(name, *prop);
}
}
mapping.insert(*handle, map);
Ok(())
}
Err(err) => Err(err),
}
})
.map_err(|source| Error::Access {
errmsg: "Error reading properties on {:?}",
dev: self.fd.dev_path(),
source,
})
}
pub(super) fn reset_state(&self) -> Result<(), Error> {
// reset state sets the connectors into a known state (all disabled),
// for the same reasons we do this on device creation.
//
// We might end up with conflicting commit requirements, if we want to restore our state,
// on top of the state the previous compositor left the device in.
// This is because we do commits per surface and not per device, so we do a global
// commit here, to fix any conflicts.
let res_handles = self.fd.resource_handles()
.map_err(|source| Error::Access {
errmsg: "Error loading drm resources",
dev: self.fd.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 = self
.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 = self
.prop_mapping
.1
.get(&crtc)
.expect("Unknown handle")
.get("MODE_ID")
.expect("Unknown property MODE_ID");
let active_prop = self
.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));
}
self.fd.atomic_commit(&[AtomicCommitFlags::AllowModeset], req)
.map_err(|source| Error::Access {
errmsg: "Failed to disable connectors",
dev: self.fd.dev_path(),
source,
})?;
Ok(())
}
}
impl<A: AsRawFd + 'static> Drop for AtomicDrmDevice<A> {
fn drop(&mut self) {
if self.active.load(Ordering::SeqCst) {
// Here we restore the card/tty's to it's previous state.
// In case e.g. getty was running on the tty sets the correct framebuffer again,
// so that getty will be visible.
// We do exit correctly if this fails, but the user will be presented with
// a black screen if no display handler takes control again.
// create an atomic mode request consisting of all properties we captured on creation.
// TODO, check current connector status and remove deactivated connectors from this req.
let mut req = AtomicModeReq::new();
fn add_multiple_props<T: ResourceHandle>(
req: &mut AtomicModeReq,
old_state: &[(T, PropertyValueSet)],
) {
for (handle, set) in old_state {
let (prop_handles, values) = set.as_props_and_values();
for (&prop_handle, &val) in prop_handles.iter().zip(values.iter()) {
req.add_raw_property((*handle).into(), prop_handle, val);
}
}
};
add_multiple_props(&mut req, &self.old_state.0);
add_multiple_props(&mut req, &self.old_state.1);
add_multiple_props(&mut req, &self.old_state.2);
add_multiple_props(&mut req, &self.old_state.3);
if let Err(err) = self.fd.atomic_commit(&[AtomicCommitFlags::AllowModeset], req) {
error!(self.logger, "Failed to restore previous state. Error: {}", err);
}
}
}
}

View File

@ -0,0 +1,184 @@
use std::collections::HashMap;
use std::sync::{Arc, atomic::{AtomicBool, Ordering}};
use std::os::unix::io::AsRawFd;
use drm::control::{Device as ControlDevice, crtc, connector};
use super::{DevPath, FdWrapper};
use crate::backend::drm::error::Error;
pub struct LegacyDrmDevice<A: AsRawFd + 'static> {
pub(crate) fd: Arc<FdWrapper<A>>,
pub(crate) active: Arc<AtomicBool>,
old_state: HashMap<crtc::Handle, (crtc::Info, Vec<connector::Handle>)>,
logger: ::slog::Logger,
}
impl<A: AsRawFd + 'static> LegacyDrmDevice<A> {
pub fn new(fd: Arc<FdWrapper<A>>, active: Arc<AtomicBool>, disable_connectors: bool, logger: slog::Logger) -> Result<Self, Error>
{
let mut dev = LegacyDrmDevice {
fd,
active,
old_state: HashMap::new(),
logger: logger.new(o!("smithay_module" => "backend_drm_legacy", "drm_module" => "device")),
};
// Enumerate (and save) the current device state.
// We need to keep the previous device configuration to restore the state later,
// so we query everything, that we can set.
let res_handles = dev.fd.resource_handles()
.map_err(|source| Error::Access {
errmsg: "Error loading drm resources",
dev: dev.fd.dev_path(),
source,
})?;
for &con in res_handles.connectors() {
let con_info = dev.fd.get_connector(con).map_err(|source| Error::Access {
errmsg: "Error loading connector info",
dev: dev.fd.dev_path(),
source,
})?;
if let Some(enc) = con_info.current_encoder() {
let enc_info = dev.fd.get_encoder(enc).map_err(|source| Error::Access {
errmsg: "Error loading encoder info",
dev: dev.fd.dev_path(),
source,
})?;
if let Some(crtc) = enc_info.crtc() {
let info = dev.fd.get_crtc(crtc).map_err(|source| Error::Access {
errmsg: "Error loading crtc info",
dev: dev.fd.dev_path(),
source,
})?;
dev.old_state
.entry(crtc)
.or_insert((info, Vec::new()))
.1
.push(con);
}
}
}
// If the user does not explicitly requests us to skip this,
// we clear out the complete connector<->crtc mapping on device creation.
//
// The reason is, that certain operations may be racy otherwise, as surfaces can
// exist on different threads. As a result, we cannot enumerate the current state
// on surface creation (it might be changed on another thread during the enumeration).
// An easy workaround is to set a known state on device creation.
if disable_connectors {
dev.reset_state()?;
}
Ok(dev)
}
pub(super) fn reset_state(&self) -> Result<(), Error> {
let res_handles = self.fd.resource_handles().map_err(|source| Error::Access {
errmsg: "Failed to query resource handles",
dev: self.fd.dev_path(),
source,
})?;
set_connector_state(&*self.fd, res_handles.connectors().iter().copied(), false)?;
for crtc in res_handles.crtcs() {
// null commit (necessary to trigger removal on the kernel side with the legacy api.)
self.fd.set_crtc(*crtc, None, (0, 0), &[], None)
.map_err(|source| Error::Access {
errmsg: "Error setting crtc",
dev: self.fd.dev_path(),
source,
})?;
}
Ok(())
}
}
impl<A: AsRawFd + 'static> Drop for LegacyDrmDevice<A> {
fn drop(&mut self) {
info!(self.logger, "Dropping device: {:?}", self.fd.dev_path());
if self.active.load(Ordering::SeqCst) {
// Here we restore the tty to it's previous state.
// In case e.g. getty was running on the tty sets the correct framebuffer again,
// so that getty will be visible.
// We do exit correctly, if this fails, but the user will be presented with
// a black screen, if no display handler takes control again.
for (handle, (info, connectors)) in self.old_state.drain() {
if let Err(err) = self.fd.set_crtc(
handle,
info.framebuffer(),
info.position(),
&connectors,
info.mode(),
) {
error!(self.logger, "Failed to reset crtc ({:?}). Error: {}", handle, err);
}
}
}
}
}
pub fn set_connector_state<D>(
dev: &D,
connectors: impl Iterator<Item = connector::Handle>,
enabled: bool,
) -> Result<(), Error>
where
D: ControlDevice
{
// for every connector...
for conn in connectors {
let info = dev
.get_connector(conn)
.map_err(|source| Error::Access {
errmsg: "Failed to get connector infos",
dev: dev.dev_path(),
source,
})?;
// that is currently connected ...
if info.state() == connector::State::Connected {
// get a list of it's properties.
let props = dev
.get_properties(conn)
.map_err(|source| Error::Access {
errmsg: "Failed to get properties for connector",
dev: dev.dev_path(),
source,
})?;
let (handles, _) = props.as_props_and_values();
// for every handle ...
for handle in handles {
// get information of that property
let info = dev
.get_property(*handle)
.map_err(|source| Error::Access {
errmsg: "Failed to get property of connector",
dev: dev.dev_path(),
source,
})?;
// to find out, if we got the handle of the "DPMS" property ...
if info.name().to_str().map(|x| x == "DPMS").unwrap_or(false) {
// so we can use that to turn on / off the connector
dev.set_property(
conn,
*handle,
if enabled {
0 /*DRM_MODE_DPMS_ON*/
} else {
3 /*DRM_MODE_DPMS_OFF*/
},
)
.map_err(|source| Error::Access {
errmsg: "Failed to set property of connector",
dev: dev.dev_path(),
source,
})?;
}
}
}
}
Ok(())
}

View File

@ -0,0 +1,365 @@
use std::rc::Rc;
use std::cell::RefCell;
use std::sync::{Arc, atomic::AtomicBool};
use std::path::PathBuf;
use std::os::unix::io::{AsRawFd, RawFd};
use calloop::{generic::Generic, InsertError, LoopHandle, Source};
use drm::{Device as BasicDevice, ClientCapability};
use drm::control::{ResourceHandles, PlaneResourceHandles, Device as ControlDevice, Event, Mode, PlaneType, crtc, plane, connector};
use nix::libc::dev_t;
use nix::sys::stat::fstat;
pub(super) mod atomic;
pub(super) mod legacy;
use atomic::AtomicDrmDevice;
use legacy::LegacyDrmDevice;
use super::surface::{DrmSurface, DrmSurfaceInternal, atomic::AtomicDrmSurface, legacy::LegacyDrmSurface};
use super::error::Error;
pub struct DrmDevice<A: AsRawFd + 'static> {
pub(super) dev_id: dev_t,
pub(crate) internal: Arc<DrmDeviceInternal<A>>,
handler: Rc<RefCell<Option<Box<dyn DeviceHandler>>>>,
#[cfg(feature = "backend_session")]
pub(super) links: RefCell<Vec<crate::signaling::SignalToken>>,
has_universal_planes: bool,
resources: ResourceHandles,
planes: PlaneResourceHandles,
pub(super) logger: ::slog::Logger,
}
impl<A: AsRawFd + 'static> AsRawFd for DrmDevice<A> {
fn as_raw_fd(&self) -> RawFd {
match &*self.internal {
DrmDeviceInternal::Atomic(dev) => dev.fd.as_raw_fd(),
DrmDeviceInternal::Legacy(dev) => dev.fd.as_raw_fd(),
}
}
}
impl<A: AsRawFd + 'static> BasicDevice for DrmDevice<A> {}
impl<A: AsRawFd + 'static> ControlDevice for DrmDevice<A> {}
pub struct FdWrapper<A: AsRawFd + 'static> {
fd: A,
pub(super) privileged: bool,
logger: ::slog::Logger,
}
impl<A: AsRawFd + 'static> AsRawFd for FdWrapper<A> {
fn as_raw_fd(&self) -> RawFd {
self.fd.as_raw_fd()
}
}
impl<A: AsRawFd + 'static> BasicDevice for FdWrapper<A> {}
impl<A: AsRawFd + 'static> ControlDevice for FdWrapper<A> {}
impl<A: AsRawFd + 'static> Drop for FdWrapper<A> {
fn drop(&mut self) {
info!(self.logger, "Dropping device: {:?}", self.dev_path());
if self.privileged {
if let Err(err) = self.release_master_lock() {
error!(self.logger, "Failed to drop drm master state. Error: {}", err);
}
}
}
}
pub enum DrmDeviceInternal<A: AsRawFd + 'static> {
Atomic(AtomicDrmDevice<A>),
Legacy(LegacyDrmDevice<A>),
}
impl<A: AsRawFd + 'static> AsRawFd for DrmDeviceInternal<A> {
fn as_raw_fd(&self) -> RawFd {
match self {
DrmDeviceInternal::Atomic(dev) => dev.fd.as_raw_fd(),
DrmDeviceInternal::Legacy(dev) => dev.fd.as_raw_fd(),
}
}
}
impl<A: AsRawFd + 'static> BasicDevice for DrmDeviceInternal<A> {}
impl<A: AsRawFd + 'static> ControlDevice for DrmDeviceInternal<A> {}
impl<A: AsRawFd + 'static> DrmDevice<A> {
pub fn new<L>(fd: A, disable_connectors: bool, logger: L) -> Result<Self, Error>
where
A: AsRawFd + Clone + 'static,
L: Into<Option<::slog::Logger>>,
{
let log = crate::slog_or_fallback(logger).new(o!("smithay_module" => "backend_drm"));
info!(log, "DrmDevice initializing");
let dev_id = fstat(fd.as_raw_fd())
.map_err(Error::UnableToGetDeviceId)?
.st_rdev;
let active = Arc::new(AtomicBool::new(true));
let dev = Arc::new({
let mut dev = FdWrapper {
fd: fd.clone(),
privileged: false,
logger: log.clone(),
};
// We want to modeset, so we better be the master, if we run via a tty session.
// This is only needed on older kernels. Newer kernels grant this permission,
// if no other process is already the *master*. So we skip over this error.
if dev.acquire_master_lock().is_err() {
warn!(log, "Unable to become drm master, assuming unprivileged mode");
} else {
dev.privileged = true;
}
dev
});
let has_universal_planes = dev.set_client_capability(ClientCapability::UniversalPlanes, true).is_ok();
let resources = dev.resource_handles().map_err(|source| Error::Access {
errmsg: "Error loading resource handles",
dev: dev.dev_path(),
source,
})?;
let planes = dev.plane_handles().map_err(|source| Error::Access {
errmsg: "Error loading plane handles",
dev: dev.dev_path(),
source,
})?;
let internal = Arc::new(DrmDevice::create_internal(dev, active, disable_connectors, log.clone())?);
Ok(DrmDevice {
dev_id,
internal,
handler: Rc::new(RefCell::new(None)),
#[cfg(feature = "backend_session")]
links: RefCell::new(Vec::new()),
has_universal_planes,
resources,
planes,
logger: log,
})
}
fn create_internal(dev: Arc<FdWrapper<A>>, active: Arc<AtomicBool>, disable_connectors: bool, log: ::slog::Logger) -> Result<DrmDeviceInternal<A>, Error> {
let force_legacy = std::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.");
};
Ok(if dev.set_client_capability(ClientCapability::Atomic, true).is_ok() && !force_legacy {
DrmDeviceInternal::Atomic(AtomicDrmDevice::new(dev, active, disable_connectors, log)?)
} else {
info!(log, "Falling back to LegacyDrmDevice");
DrmDeviceInternal::Legacy(LegacyDrmDevice::new(dev, active, disable_connectors, log)?)
})
}
pub fn process_events(&mut self) {
match self.receive_events() {
Ok(events) => {
for event in events {
if let Event::PageFlip(event) = event {
trace!(self.logger, "Got a page-flip event for crtc ({:?})", event.crtc);
if let Some(handler) = self.handler.borrow_mut().as_mut() {
handler.vblank(event.crtc);
}
} else {
trace!(
self.logger,
"Got a non-page-flip event of device '{:?}'.",
self.dev_path()
);
}
}
}
Err(source) => {
if let Some(handler) = self.handler.borrow_mut().as_mut() {
handler.error(Error::Access {
errmsg: "Error processing drm events",
dev: self.dev_path(),
source,
});
}
}
}
}
pub fn is_atomic(&self) -> bool {
match *self.internal {
DrmDeviceInternal::Atomic(_) => true,
DrmDeviceInternal::Legacy(_) => false,
}
}
pub fn set_handler(&mut self, handler: impl DeviceHandler + 'static) {
let handler = Some(Box::new(handler) as Box<dyn DeviceHandler + 'static>);
*self.handler.borrow_mut() = handler;
}
pub fn clear_handler(&mut self) {
self.handler.borrow_mut().take();
}
pub fn crtcs(&self) -> &[crtc::Handle] {
self.resources.crtcs()
}
pub fn planes(&self, crtc: &crtc::Handle) -> Result<Planes, Error> {
let mut primary = None;
let mut cursor = None;
let mut overlay = Vec::new();
for plane in self.planes.planes() {
let info = self.get_plane(*plane).map_err(|source| Error::Access {
errmsg: "Failed to get plane information",
dev: self.dev_path(),
source,
})?;
let filter = info.possible_crtcs();
if self.resources.filter_crtcs(filter).contains(crtc) {
match self.plane_type(*plane)? {
PlaneType::Primary => { primary = Some(*plane); },
PlaneType::Cursor => { cursor = Some(*plane); },
PlaneType::Overlay => { overlay.push(*plane); },
};
}
}
Ok(Planes {
primary: primary.expect("Crtc has no primary plane"),
cursor,
overlay: if self.has_universal_planes { Some(overlay) } else { None },
})
}
fn plane_type(&self, plane: plane::Handle) -> Result<PlaneType, Error> {
let props = self.get_properties(plane).map_err(|source| Error::Access {
errmsg: "Failed to get properties of plane",
dev: self.dev_path(),
source,
})?;
let (ids, vals) = props.as_props_and_values();
for (&id, &val) in ids.iter().zip(vals.iter()) {
let info = self.get_property(id).map_err(|source| Error::Access {
errmsg: "Failed to get property info",
dev: self.dev_path(),
source,
})?;
if info.name().to_str().map(|x| x == "type").unwrap_or(false) {
return Ok(match val {
x if x == (PlaneType::Primary as u64) => PlaneType::Primary,
x if x == (PlaneType::Cursor as u64) => PlaneType::Cursor,
_ => PlaneType::Overlay,
});
}
}
unreachable!()
}
pub fn create_surface(&self, crtc: crtc::Handle, plane: plane::Handle, mode: Mode, connectors: &[connector::Handle]) -> Result<DrmSurface<A>, Error> {
if connectors.is_empty() {
return Err(Error::SurfaceWithoutConnectors(crtc));
}
let info = self.get_plane(plane).map_err(|source| Error::Access {
errmsg: "Failed to get plane info",
dev: self.dev_path(),
source
})?;
let filter = info.possible_crtcs();
if !self.resources.filter_crtcs(filter).contains(&crtc) {
return Err(Error::PlaneNotCompatible(crtc, plane));
}
let active = match &*self.internal {
DrmDeviceInternal::Atomic(dev) => dev.active.clone(),
DrmDeviceInternal::Legacy(dev) => dev.active.clone(),
};
let internal = Arc::new(if self.is_atomic() {
let mapping = match &*self.internal {
DrmDeviceInternal::Atomic(dev) => dev.prop_mapping.clone(),
_ => unreachable!(),
};
DrmSurfaceInternal::Atomic(AtomicDrmSurface::new(self.internal.clone(), active, crtc, plane, mapping, mode, connectors, self.logger.clone())?)
} else {
if self.plane_type(plane)? != PlaneType::Primary {
return Err(Error::NonPrimaryPlane(plane));
}
DrmSurfaceInternal::Legacy(LegacyDrmSurface::new(self.internal.clone(), active, crtc, mode, connectors, self.logger.clone())?)
});
Ok(DrmSurface {
crtc,
plane,
internal,
})
}
}
pub struct Planes {
pub primary: plane::Handle,
pub cursor: Option<plane::Handle>,
pub overlay: Option<Vec<plane::Handle>>,
}
impl<A: AsRawFd + 'static> DrmDeviceInternal<A> {
pub(super) fn reset_state(&self) -> Result<(), Error> {
match self {
DrmDeviceInternal::Atomic(dev) => dev.reset_state(),
DrmDeviceInternal::Legacy(dev) => dev.reset_state(),
}
}
}
/// Trait to receive events of a bound [`DrmDevice`]
///
/// See [`device_bind`]
pub trait DeviceHandler {
/// A vblank blank event on the provided crtc has happend
fn vblank(&mut self, crtc: crtc::Handle);
/// An error happend while processing events
fn error(&mut self, error: Error);
}
/// Trait representing open devices that *may* return a `Path`
pub trait DevPath {
/// Returns the path of the open device if possible
fn dev_path(&self) -> Option<PathBuf>;
}
impl<A: AsRawFd> DevPath for A {
fn dev_path(&self) -> Option<PathBuf> {
use std::fs;
fs::read_link(format!("/proc/self/fd/{:?}", self.as_raw_fd())).ok()
}
}
/// calloop source associated with a Device
pub type DrmSource<A> = Generic<DrmDevice<A>>;
/// Bind a `Device` to an [`EventLoop`](calloop::EventLoop),
///
/// This will cause it to recieve events and feed them into a previously
/// set [`DeviceHandler`](DeviceHandler).
pub fn device_bind<A, Data>(
handle: &LoopHandle<Data>,
device: DrmDevice<A>,
) -> ::std::result::Result<Source<DrmSource<A>>, InsertError<DrmSource<A>>>
where
A: AsRawFd + 'static,
Data: 'static,
{
let source = Generic::new(device, calloop::Interest::Readable, calloop::Mode::Level);
handle.insert_source(source, |_, source, _| {
source.process_events();
Ok(())
})
}

View File

@ -1,301 +0,0 @@
//!
//! [`Device`](Device) and [`Surface`](Surface)
//! implementations using egl contexts and surfaces for efficient rendering.
//!
//! Usually this implementation's [`EglSurface`](::backend::drm::egl::EglSurface)s implementation
//! of [`GLGraphicsBackend`](::backend::graphics::gl::GLGraphicsBackend) will be used
//! to let your compositor render.
//! Take a look at `anvil`s source code for an example of this.
//!
//! For detailed overview of these abstractions take a look at the module documentation of backend::drm.
//!
use drm::control::{connector, crtc, encoder, framebuffer, plane, Mode, ResourceHandles};
use drm::SystemError as DrmError;
use nix::libc::dev_t;
use std::cell::RefCell;
use std::collections::HashMap;
use std::fmt;
use std::os::unix::io::{AsRawFd, RawFd};
use std::rc::Rc;
use std::sync::{Arc, Weak as WeakArc};
#[cfg(feature = "use_system_lib")]
use wayland_server::Display;
use super::{Device, DeviceHandler, Surface};
use crate::backend::egl::native::{Backend, NativeDisplay, NativeSurface};
#[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::*;
use crate::backend::egl::context::{GlAttributes, PixelFormatRequirements};
use crate::backend::egl::display::EGLDisplay;
#[cfg(feature = "backend_session")]
pub mod session;
/// Errors for the DRM/EGL module
#[derive(thiserror::Error, Debug)]
pub enum Error<U: std::error::Error + std::fmt::Debug + std::fmt::Display + 'static> {
/// EGL Error
#[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),
}
pub(crate) type Arguments = (crtc::Handle, Mode, Vec<connector::Handle>);
type BackendRef<D> = WeakArc<EglSurfaceInternal<<D as Device>::Surface>>;
/// Representation of an egl device to create egl rendering surfaces
pub struct EglDevice<B, D>
where
B: Backend<Surface = <D as Device>::Surface, Error = <<D as Device>::Surface as Surface>::Error>
+ 'static,
D: Device + NativeDisplay<B, Arguments = Arguments> + 'static,
<D as Device>::Surface: NativeSurface<Error = <<D as Device>::Surface as Surface>::Error>,
{
dev: EGLDisplay<B, D>,
logger: ::slog::Logger,
default_attributes: GlAttributes,
default_requirements: PixelFormatRequirements,
backends: Rc<RefCell<HashMap<crtc::Handle, BackendRef<D>>>>,
#[cfg(feature = "backend_session")]
links: Vec<crate::signaling::SignalToken>,
}
// BackendRef does not implement debug, so we have to impl Debug manually
impl<B, D> fmt::Debug for EglDevice<B, D>
where
B: Backend<Surface = <D as Device>::Surface, Error = <<D as Device>::Surface as Surface>::Error>
+ fmt::Debug
+ 'static,
D: Device + NativeDisplay<B, Arguments = Arguments> + fmt::Debug + 'static,
<D as Device>::Surface: NativeSurface<Error = <<D as Device>::Surface as Surface>::Error>,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut debug = f.debug_struct("EglDevice");
debug
.field("dev", &self.dev)
.field("logger", &self.logger)
.field("default_attributes", &self.default_attributes)
.field("default_requirements", &self.default_requirements)
.field("backends", &"...");
#[cfg(feature = "backend_session")]
debug.field("links", &self.links);
debug.finish()
}
}
impl<B, D> AsRawFd for EglDevice<B, D>
where
B: Backend<Surface = <D as Device>::Surface, Error = <<D as Device>::Surface as Surface>::Error>
+ 'static,
D: Device + NativeDisplay<B, Arguments = Arguments> + 'static,
<D as Device>::Surface: NativeSurface<Error = <<D as Device>::Surface as Surface>::Error>,
{
fn as_raw_fd(&self) -> RawFd {
self.dev.borrow().as_raw_fd()
}
}
impl<B, D> EglDevice<B, D>
where
B: Backend<Surface = <D as Device>::Surface, Error = <<D as Device>::Surface as Surface>::Error>
+ 'static,
D: Device + NativeDisplay<B, Arguments = Arguments> + 'static,
<D as Device>::Surface: NativeSurface<Error = <<D as Device>::Surface as Surface>::Error>,
{
/// Try to create a new [`EglDevice`] from an open device.
///
/// Returns an error if the file is no valid device or context
/// creation was not successful.
pub fn new<L>(dev: D, logger: L) -> Result<Self, Error<<<D as Device>::Surface as Surface>::Error>>
where
L: Into<Option<::slog::Logger>>,
{
EglDevice::new_with_defaults(
dev,
GlAttributes {
version: None,
profile: None,
debug: cfg!(debug_assertions),
vsync: true,
},
Default::default(),
logger,
)
}
/// Try to create a new [`EglDevice`] from an open device with the given attributes and
/// requirements as defaults for new surfaces.
///
/// Returns an error if the file is no valid device or context
/// creation was not successful.
pub fn new_with_defaults<L>(
mut dev: D,
default_attributes: GlAttributes,
default_requirements: PixelFormatRequirements,
logger: L,
) -> Result<Self, Error<<<D as Device>::Surface as Surface>::Error>>
where
L: Into<Option<::slog::Logger>>,
{
let log = crate::slog_or_fallback(logger).new(o!("smithay_module" => "backend_egl"));
dev.clear_handler();
debug!(log, "Creating egl context from device");
Ok(EglDevice {
dev: EGLDisplay::new(dev, log.clone()).map_err(Error::EGL)?,
default_attributes,
default_requirements,
backends: Rc::new(RefCell::new(HashMap::new())),
logger: log,
#[cfg(feature = "backend_session")]
links: Vec::new(),
})
}
}
struct InternalDeviceHandler<B, D>
where
B: Backend<Surface = <D as Device>::Surface, Error = <<D as Device>::Surface as Surface>::Error>
+ 'static,
D: Device + NativeDisplay<B, Arguments = Arguments> + 'static,
<D as Device>::Surface: NativeSurface<Error = <<D as Device>::Surface as Surface>::Error>,
{
handler: Box<dyn DeviceHandler<Device = EglDevice<B, D>> + 'static>,
}
impl<B, D> DeviceHandler for InternalDeviceHandler<B, D>
where
B: Backend<Surface = <D as Device>::Surface, Error = <<D as Device>::Surface as Surface>::Error>
+ 'static,
D: Device + NativeDisplay<B, Arguments = Arguments> + 'static,
<D as Device>::Surface: NativeSurface<Error = <<D as Device>::Surface as Surface>::Error>,
{
type Device = D;
fn vblank(&mut self, crtc: crtc::Handle) {
self.handler.vblank(crtc)
}
fn error(&mut self, error: <<D as Device>::Surface as Surface>::Error) {
self.handler.error(Error::Underlying(error));
}
}
impl<B, D> Device for EglDevice<B, D>
where
B: Backend<Surface = <D as Device>::Surface, Error = <<D as Device>::Surface as Surface>::Error>
+ 'static,
D: Device + NativeDisplay<B, Arguments = Arguments> + 'static,
<D as Device>::Surface: NativeSurface<Error = <<D as Device>::Surface as Surface>::Error>,
{
type Surface = EglSurface<<D as Device>::Surface>;
fn device_id(&self) -> dev_t {
self.dev.borrow().device_id()
}
fn set_handler(&mut self, handler: impl DeviceHandler<Device = Self> + 'static) {
self.dev.borrow_mut().set_handler(InternalDeviceHandler {
handler: Box::new(handler),
});
}
fn clear_handler(&mut self) {
self.dev.borrow_mut().clear_handler()
}
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");
let context = self
.dev
.create_context(self.default_attributes, self.default_requirements)
.map_err(Error::EGL)?;
let surface = self
.dev
.create_surface(
context.get_pixel_format(),
self.default_requirements.double_buffer,
context.get_config_id(),
(crtc, mode, Vec::from(connectors)),
)
.map_err(|err| match err {
SurfaceCreationError::EGLSurfaceCreationFailed(err) => Error::RawEGL(err),
SurfaceCreationError::NativeSurfaceCreationFailed(err) => Error::Underlying(err),
})?;
let backend = Arc::new(EglSurfaceInternal { context, surface });
self.backends.borrow_mut().insert(crtc, Arc::downgrade(&backend));
Ok(EglSurface(backend))
}
fn process_events(&mut self) {
self.dev.borrow_mut().process_events()
}
fn resource_handles(&self) -> Result<ResourceHandles, <Self::Surface as Surface>::Error> {
self.dev.borrow().resource_handles().map_err(Error::Underlying)
}
fn get_connector_info(&self, conn: connector::Handle) -> std::result::Result<connector::Info, DrmError> {
self.dev.borrow().get_connector_info(conn)
}
fn get_crtc_info(&self, crtc: crtc::Handle) -> std::result::Result<crtc::Info, DrmError> {
self.dev.borrow().get_crtc_info(crtc)
}
fn get_encoder_info(&self, enc: encoder::Handle) -> std::result::Result<encoder::Info, DrmError> {
self.dev.borrow().get_encoder_info(enc)
}
fn get_framebuffer_info(
&self,
fb: framebuffer::Handle,
) -> std::result::Result<framebuffer::Info, DrmError> {
self.dev.borrow().get_framebuffer_info(fb)
}
fn get_plane_info(&self, plane: plane::Handle) -> std::result::Result<plane::Info, DrmError> {
self.dev.borrow().get_plane_info(plane)
}
}
#[cfg(feature = "use_system_lib")]
impl<B, D> EGLGraphicsBackend for EglDevice<B, D>
where
B: Backend<Surface = <D as Device>::Surface, Error = <<D as Device>::Surface as Surface>::Error>
+ 'static,
D: Device + NativeDisplay<B, Arguments = Arguments> + 'static,
<D as Device>::Surface: NativeSurface<Error = <<D as Device>::Surface as Surface>::Error>,
{
fn bind_wl_display(&self, display: &Display) -> Result<EGLBufferReader, EGLError> {
self.dev.bind_wl_display(display)
}
}
impl<B, D> Drop for EglDevice<B, D>
where
B: Backend<Surface = <D as Device>::Surface, Error = <<D as Device>::Surface as Surface>::Error>
+ 'static,
D: Device + NativeDisplay<B, Arguments = Arguments> + 'static,
<D as Device>::Surface: NativeSurface<Error = <<D as Device>::Surface as Surface>::Error>,
{
fn drop(&mut self) {
self.clear_handler();
}
}

View File

@ -1,81 +0,0 @@
//!
//! Support to register an [`EglDevice`](EglDevice)
//! to an open [`Session`](::backend::session::Session).
//!
use drm::control::{connector, crtc, Mode};
use std::cell::RefCell;
use std::collections::HashMap;
use std::rc::{Rc, Weak};
use std::sync::{atomic::Ordering, Weak as WeakArc};
use super::{EglDevice, EglSurfaceInternal};
use crate::backend::drm::{Device, Surface};
use crate::backend::egl::{
ffi,
native::{Backend, NativeDisplay, NativeSurface},
};
use crate::{
backend::session::Signal as SessionSignal,
signaling::{Linkable, Signaler},
};
/// [`SessionObserver`](SessionObserver)
/// linked to the [`EglDevice`](EglDevice) it was
/// created from.
#[derive(Debug)]
pub struct EglDeviceObserver<N: NativeSurface + Surface> {
backends: Weak<RefCell<HashMap<crtc::Handle, WeakArc<EglSurfaceInternal<N>>>>>,
}
impl<B, D> Linkable<SessionSignal> for EglDevice<B, D>
where
B: Backend<Surface = <D as Device>::Surface, Error = <<D as Device>::Surface as Surface>::Error>
+ 'static,
D: Device
+ NativeDisplay<B, Arguments = (crtc::Handle, Mode, Vec<connector::Handle>)>
+ Linkable<SessionSignal>
+ 'static,
<D as Device>::Surface: NativeSurface<Error = <<D as Device>::Surface as Surface>::Error>,
{
fn link(&mut self, signaler: Signaler<SessionSignal>) {
let lower_signal = Signaler::new();
self.dev.borrow_mut().link(lower_signal.clone());
let mut observer = EglDeviceObserver {
backends: Rc::downgrade(&self.backends),
};
let token = signaler.register(move |&signal| match signal {
SessionSignal::ActivateSession | SessionSignal::ActivateDevice { .. } => {
// Activate lower *before* we process the event
lower_signal.signal(signal);
observer.activate()
}
_ => {
lower_signal.signal(signal);
}
});
self.links.push(token);
}
}
impl<N: NativeSurface + Surface> EglDeviceObserver<N> {
fn activate(&mut self) {
if let Some(backends) = self.backends.upgrade() {
for (_crtc, backend) in backends.borrow().iter() {
if let Some(backend) = backend.upgrade() {
let old_surface = backend
.surface
.surface
.swap(std::ptr::null_mut(), Ordering::SeqCst);
if !old_surface.is_null() {
unsafe {
ffi::egl::DestroySurface(**backend.surface.display, old_surface as *const _);
}
}
}
}
}
}
}

View File

@ -1,158 +0,0 @@
use drm::control::{connector, crtc, Mode};
use nix::libc::c_void;
use std::convert::TryInto;
use std::sync::Arc;
use super::Error;
use crate::backend::drm::Surface;
use crate::backend::egl::native::NativeSurface;
use crate::backend::egl::{get_proc_address, native, EGLContext, EGLSurface};
#[cfg(feature = "renderer_gl")]
use crate::backend::graphics::gl::GLGraphicsBackend;
#[cfg(feature = "renderer_gl")]
use crate::backend::graphics::PixelFormat;
use crate::backend::graphics::{CursorBackend, SwapBuffersError};
/// Egl surface for rendering
#[derive(Debug)]
pub struct EglSurface<N: native::NativeSurface + Surface>(pub(super) Arc<EglSurfaceInternal<N>>);
#[derive(Debug)]
pub(super) struct EglSurfaceInternal<N>
where
N: native::NativeSurface + Surface,
{
pub(super) context: EGLContext,
pub(super) surface: EGLSurface<N>,
}
impl<N> Surface for EglSurface<N>
where
N: native::NativeSurface + Surface,
{
type Connectors = <N as Surface>::Connectors;
type Error = Error<<N as Surface>::Error>;
fn crtc(&self) -> crtc::Handle {
(*self.0.surface).crtc()
}
fn current_connectors(&self) -> Self::Connectors {
self.0.surface.current_connectors()
}
fn pending_connectors(&self) -> Self::Connectors {
self.0.surface.pending_connectors()
}
fn add_connector(&self, connector: connector::Handle) -> Result<(), Self::Error> {
self.0.surface.add_connector(connector).map_err(Error::Underlying)
}
fn remove_connector(&self, connector: connector::Handle) -> Result<(), Self::Error> {
self.0
.surface
.remove_connector(connector)
.map_err(Error::Underlying)
}
fn set_connectors(&self, connectors: &[connector::Handle]) -> Result<(), Self::Error> {
self.0
.surface
.set_connectors(connectors)
.map_err(Error::Underlying)
}
fn current_mode(&self) -> Mode {
self.0.surface.current_mode()
}
fn pending_mode(&self) -> Mode {
self.0.surface.pending_mode()
}
fn use_mode(&self, mode: Mode) -> Result<(), Self::Error> {
self.0.surface.use_mode(mode).map_err(Error::Underlying)
}
}
impl<N> CursorBackend for EglSurface<N>
where
N: NativeSurface + Surface + CursorBackend,
{
type CursorFormat = <N as CursorBackend>::CursorFormat;
type Error = <N as CursorBackend>::Error;
fn set_cursor_position(&self, x: u32, y: u32) -> ::std::result::Result<(), Self::Error> {
self.0.surface.set_cursor_position(x, y)
}
fn set_cursor_representation(
&self,
buffer: &Self::CursorFormat,
hotspot: (u32, u32),
) -> ::std::result::Result<(), Self::Error> {
self.0.surface.set_cursor_representation(buffer, hotspot)
}
fn clear_cursor_representation(&self) -> Result<(), Self::Error> {
self.0.surface.clear_cursor_representation()
}
}
#[cfg(feature = "renderer_gl")]
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> {
if let Err(err) = self.0.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 {
get_proc_address(symbol)
}
fn get_framebuffer_dimensions(&self) -> (u32, u32) {
let (w, h) = self.pending_mode().size();
(w as u32, h as u32)
}
fn is_current(&self) -> bool {
self.0.context.is_current() && self.0.surface.is_current()
}
unsafe fn make_current(&self) -> ::std::result::Result<(), SwapBuffersError> {
self.0
.context
.make_current_with_surface(&self.0.surface)
.map_err(Into::into)
}
fn get_pixel_format(&self) -> PixelFormat {
self.0.surface.get_pixel_format()
}
}
#[cfg(test)]
mod test {
use super::EglSurface;
use crate::backend::drm::gbm::GbmSurface;
use crate::backend::drm::legacy::LegacyDrmSurface;
use std::fs::File;
fn is_send<S: Send>() {}
#[test]
fn surface_is_send() {
is_send::<EglSurface<GbmSurface<LegacyDrmSurface<File>>>>();
}
}

View File

@ -1,235 +0,0 @@
//!
//! Egl [`NativeDisplay`](::backend::egl::native::NativeDisplay) and
//! [`NativeSurface`](::backend::egl::native::NativeSurface) support for
//! [`EglStreamDevice`](EglStreamDevice) and [`EglStreamSurface`](EglStreamSurface).
//!
#[cfg(feature = "backend_drm_atomic")]
use crate::backend::drm::atomic::AtomicDrmSurface;
#[cfg(all(feature = "backend_drm_atomic", feature = "backend_drm_legacy"))]
use crate::backend::drm::common::fallback::{EitherError, FallbackSurface};
#[cfg(any(feature = "backend_drm_atomic", feature = "backend_drm_legacy"))]
use crate::backend::drm::common::Error as DrmError;
#[cfg(feature = "backend_drm_legacy")]
use crate::backend::drm::legacy::LegacyDrmSurface;
use crate::backend::drm::{Device, RawDevice, RawSurface, Surface};
use crate::backend::egl::native::{Backend, NativeDisplay, NativeSurface};
use crate::backend::egl::{
display::EGLDisplayHandle, ffi, wrap_egl_call, EGLError, Error as EglBackendError, SurfaceCreationError,
SwapBuffersError,
};
use super::Error;
use super::{EglStreamDevice, EglStreamSurface};
use drm::control::{connector, crtc, Device as ControlDevice, Mode};
use nix::libc::{c_int, c_void};
use std::marker::PhantomData;
use std::os::unix::io::AsRawFd;
use std::sync::Arc;
/// Egl Device backend type
///
/// See [`Backend`](::backend::egl::native::Backend).
#[derive(Debug)]
pub struct EglStreamDeviceBackend<D: RawDevice + 'static> {
_userdata: PhantomData<D>,
}
impl<D: RawDevice + 'static> Backend for EglStreamDeviceBackend<D>
where
EglStreamSurface<<D as Device>::Surface>:
NativeSurface<Error = Error<<<D as Device>::Surface as Surface>::Error>>,
{
type Surface = EglStreamSurface<<D as Device>::Surface>;
type Error = Error<<<D as Device>::Surface as Surface>::Error>;
// create an EGLDisplay for the EGLstream platform
unsafe fn get_display<F>(
display: ffi::NativeDisplayType,
attribs: &[ffi::EGLint],
has_dp_extension: F,
log: ::slog::Logger,
) -> Result<ffi::egl::types::EGLDisplay, EGLError>
where
F: Fn(&str) -> bool,
{
if has_dp_extension("EGL_EXT_platform_device") && ffi::egl::GetPlatformDisplayEXT::is_loaded() {
debug!(
log,
"EGL Display Initialization via EGL_EXT_platform_device with {:?}", display
);
wrap_egl_call(|| {
ffi::egl::GetPlatformDisplayEXT(
ffi::egl::PLATFORM_DEVICE_EXT,
display as *mut _,
attribs.as_ptr() as *const _,
)
})
} else {
Ok(ffi::egl::NO_DISPLAY)
}
}
}
unsafe impl<D: RawDevice + ControlDevice + 'static> NativeDisplay<EglStreamDeviceBackend<D>>
for EglStreamDevice<D>
where
EglStreamSurface<<D as Device>::Surface>:
NativeSurface<Error = Error<<<D as Device>::Surface as Surface>::Error>>,
{
type Arguments = (crtc::Handle, Mode, Vec<connector::Handle>);
fn is_backend(&self) -> bool {
true
}
fn ptr(&self) -> Result<ffi::NativeDisplayType, EglBackendError> {
Ok(self.dev as *const _)
}
fn attributes(&self) -> Vec<ffi::EGLint> {
vec![
ffi::egl::DRM_MASTER_FD_EXT as ffi::EGLint,
self.raw.as_raw_fd(),
ffi::egl::NONE as i32,
]
}
fn surface_type(&self) -> ffi::EGLint {
ffi::egl::STREAM_BIT_KHR as ffi::EGLint
}
fn create_surface(
&mut self,
args: Self::Arguments,
) -> Result<EglStreamSurface<<D as Device>::Surface>, Error<<<D as Device>::Surface as Surface>::Error>>
{
Device::create_surface(self, args.0, args.1, &args.2)
}
}
// we need either a `crtc` or a `plane` for EGLStream initializations,
// which totally breaks our abstraction.. (This is normally an implementation details of `RawSurface`-implementations).
//
// as a result, we need three implemenations for atomic, legacy and fallback...
#[cfg(feature = "backend_drm_atomic")]
unsafe impl<A: AsRawFd + 'static> NativeSurface for EglStreamSurface<AtomicDrmSurface<A>> {
type Error = Error<DrmError>;
unsafe fn create(
&self,
display: &Arc<EGLDisplayHandle>,
config_id: ffi::egl::types::EGLConfig,
surface_attribs: &[c_int],
) -> Result<*const c_void, SurfaceCreationError<Self::Error>> {
let output_attributes = {
let mut out: Vec<isize> = Vec::with_capacity(3);
out.push(ffi::egl::DRM_PLANE_EXT as isize);
out.push(Into::<u32>::into(self.0.crtc.0.planes.primary) as isize);
out.push(ffi::egl::NONE as isize);
out
};
self.create_surface(display, config_id, surface_attribs, &output_attributes)
.map_err(SurfaceCreationError::NativeSurfaceCreationFailed)
}
fn needs_recreation(&self) -> bool {
self.0.crtc.commit_pending()
}
fn swap_buffers(
&self,
display: &Arc<EGLDisplayHandle>,
surface: ffi::egl::types::EGLSurface,
) -> Result<(), SwapBuffersError<Error<DrmError>>> {
self.flip(self.0.crtc.0.crtc, display, surface)
}
}
#[cfg(feature = "backend_drm_legacy")]
unsafe impl<A: AsRawFd + 'static> NativeSurface for EglStreamSurface<LegacyDrmSurface<A>> {
type Error = Error<DrmError>;
unsafe fn create(
&self,
display: &Arc<EGLDisplayHandle>,
config_id: ffi::egl::types::EGLConfig,
surface_attribs: &[c_int],
) -> Result<*const c_void, SurfaceCreationError<Self::Error>> {
let output_attributes = {
let mut out: Vec<isize> = Vec::with_capacity(3);
out.push(ffi::egl::DRM_CRTC_EXT as isize);
out.push(Into::<u32>::into(self.0.crtc.0.crtc) as isize);
out.push(ffi::egl::NONE as isize);
out
};
self.create_surface(display, config_id, surface_attribs, &output_attributes)
.map_err(SurfaceCreationError::NativeSurfaceCreationFailed)
}
fn needs_recreation(&self) -> bool {
self.0.crtc.commit_pending()
}
fn swap_buffers(
&self,
display: &Arc<EGLDisplayHandle>,
surface: ffi::egl::types::EGLSurface,
) -> Result<(), SwapBuffersError<Error<DrmError>>> {
self.flip(self.0.crtc.0.crtc, display, surface)
}
}
#[cfg(all(feature = "backend_drm_atomic", feature = "backend_drm_legacy"))]
unsafe impl<A: AsRawFd + 'static> NativeSurface
for EglStreamSurface<FallbackSurface<AtomicDrmSurface<A>, LegacyDrmSurface<A>>>
{
type Error = Error<EitherError<DrmError, DrmError>>;
unsafe fn create(
&self,
display: &Arc<EGLDisplayHandle>,
config_id: ffi::egl::types::EGLConfig,
surface_attribs: &[c_int],
) -> Result<*const c_void, SurfaceCreationError<Self::Error>> {
let output_attributes = {
let mut out: Vec<isize> = Vec::with_capacity(3);
match &self.0.crtc {
FallbackSurface::Preference(dev) => {
out.push(ffi::egl::DRM_PLANE_EXT as isize);
out.push(Into::<u32>::into(dev.0.planes.primary) as isize);
} //AtomicDrmSurface
FallbackSurface::Fallback(dev) => {
out.push(ffi::egl::DRM_CRTC_EXT as isize);
out.push(Into::<u32>::into(dev.0.crtc) as isize);
} // LegacyDrmSurface
}
out.push(ffi::egl::NONE as isize);
out
};
self.create_surface(display, config_id, surface_attribs, &output_attributes)
.map_err(SurfaceCreationError::NativeSurfaceCreationFailed)
}
fn needs_recreation(&self) -> bool {
self.0.crtc.commit_pending()
}
fn swap_buffers(
&self,
display: &Arc<EGLDisplayHandle>,
surface: ffi::egl::types::EGLSurface,
) -> Result<(), SwapBuffersError<Self::Error>> {
let crtc = match &self.0.crtc {
FallbackSurface::Preference(dev) => dev.0.crtc,
FallbackSurface::Fallback(dev) => dev.0.crtc,
};
self.flip(crtc, display, surface)
}
}

View File

@ -1,403 +0,0 @@
//!
//! [`Device`](Device) and [`Surface`](Surface) implementations using
//! the proposed EGLStream api for efficient rendering.
//! Currently this api is only implemented by the proprietary `nvidia` driver.
//!
//! Usually this implementation will be wrapped into a [`EglDevice`](::backend::drm::egl::EglDevice).
//!
//! To use these types standalone, you will need to render via egl yourself as page flips
//! are driven via `eglSwapBuffers`.
//!
//! To use this api in place of GBM for nvidia cards take a look at
//! [`FallbackDevice`](::backend::drm::common::fallback::FallbackDevice).
//! Take a look at `anvil`s source code for an example of this.
//!
//! For detailed overview of these abstractions take a look at the module documentation of backend::drm.
//!
use super::{Device, DeviceHandler, RawDevice, Surface};
use drm::buffer::format::PixelFormat;
use drm::control::{
connector, crtc, encoder, framebuffer, plane, Device as ControlDevice, Mode, ResourceHandles,
};
use drm::SystemError as DrmError;
use failure::ResultExt;
use nix::libc::dev_t;
use std::cell::RefCell;
use std::collections::HashMap;
use std::ffi::CStr;
use std::fmt;
use std::os::unix::fs::MetadataExt;
use std::os::unix::io::{AsRawFd, RawFd};
use std::rc::{Rc, Weak};
use std::sync::{Arc, Mutex, Weak as WeakArc};
use std::{fs, ptr};
mod surface;
pub use self::surface::EglStreamSurface;
use self::surface::EglStreamSurfaceInternal;
pub mod egl;
#[cfg(feature = "backend_session")]
pub mod session;
use crate::backend::egl::ffi::{self, egl::types::EGLDeviceEXT};
use crate::backend::egl::{wrap_egl_call, EGLError as RawEGLError, Error as EglError};
use crate::backend::graphics::SwapBuffersError;
/// Errors thrown by the [`EglStreamDevice`](::backend::drm::eglstream::EglStreamDevice)
/// and [`EglStreamSurface`](::backend::drm::eglstream::EglStreamSurface).
#[derive(thiserror::Error, Debug)]
pub enum Error<U: std::error::Error + std::fmt::Debug + std::fmt::Display + 'static> {
/// `eglInitialize` returned an error
#[error("Failed to initialize EGL: {0:}")]
InitFailed(#[source] RawEGLError),
/// Failed to enumerate EGL_EXT_drm_device
#[error("Failed to enumerate EGL_EXT_drm_device: {0:}")]
FailedToEnumerateDevices(#[source] RawEGLError),
/// Device is not compatible with EGL_EXT_drm_device extension
#[error("Device is not compatible with EGL_EXT_drm_device extension")]
DeviceIsNoEGLStreamDevice,
/// Device has not suitable output layer
#[error("Device has no suitable output layer")]
DeviceNoOutputLayer,
/// Device was unable to create an EGLStream
#[error("EGLStream creation failed")]
DeviceStreamCreationFailed,
/// Underlying backend error
#[error("Underlying error: {0}")]
Underlying(#[source] U),
/// Buffer creation failed
#[error("Buffer creation failed")]
BufferCreationFailed(#[source] failure::Compat<DrmError>),
/// Buffer write failed
#[error("Buffer write failed")]
BufferWriteFailed(#[source] failure::Compat<DrmError>),
/// Stream flip failed
#[error("Stream flip failed ({0})")]
StreamFlipFailed(#[source] RawEGLError),
}
type SurfaceInternalRef<D> = WeakArc<EglStreamSurfaceInternal<<D as Device>::Surface>>;
/// Representation of an open egl stream device to create egl rendering surfaces
pub struct EglStreamDevice<D: RawDevice + ControlDevice + 'static> {
pub(self) dev: EGLDeviceEXT,
raw: D,
backends: Rc<RefCell<HashMap<crtc::Handle, SurfaceInternalRef<D>>>>,
logger: ::slog::Logger,
#[cfg(feature = "backend_session")]
links: Vec<crate::signaling::SignalToken>,
}
// SurfaceInternalRef does not implement debug, so we have to impl Debug manually
impl<D: RawDevice + ControlDevice + fmt::Debug + 'static> fmt::Debug for EglStreamDevice<D> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut debug = f.debug_struct("EglStreamDevice");
debug
.field("dev", &self.dev)
.field("raw", &self.raw)
.field("backends", &"...")
.field("logger", &self.logger);
#[cfg(feature = "backend_session")]
debug.field("links", &self.links);
debug.finish()
}
}
impl<D: RawDevice + ControlDevice + 'static> EglStreamDevice<D> {
/// Try to create a new [`EglStreamDevice`] from an open device.
///
/// Returns an error if the underlying device would not support the required EGL extensions.
pub fn new<L>(mut raw: D, logger: L) -> Result<Self, Error<<<D as Device>::Surface as Surface>::Error>>
where
L: Into<Option<::slog::Logger>>,
{
let log = crate::slog_or_fallback(logger).new(o!("smithay_module" => "backend_eglstream"));
raw.clear_handler();
debug!(log, "Creating egl stream device");
ffi::make_sure_egl_is_loaded();
fn has_extensions(exts: &[String], check: &'static [&'static str]) -> Result<(), EglError> {
check
.iter()
.map(|ext| {
if exts.iter().any(|s| *s == *ext) {
Ok(())
} else {
Err(EglError::EglExtensionNotSupported(check))
}
})
.collect()
}
let device = unsafe {
// the first step is to query the list of extensions without any display, if supported
let dp_extensions = {
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
if p.is_null() {
vec![]
} else {
let p = CStr::from_ptr(p);
let list = String::from_utf8(p.to_bytes().to_vec()).unwrap_or_else(|_| String::new());
list.split(' ').map(|e| e.to_string()).collect::<Vec<_>>()
}
};
debug!(log, "EGL No-Display Extensions: {:?}", dp_extensions);
// we need either EGL_EXT_device_base or EGL_EXT_device_enumeration &_query
if let Err(_err) = has_extensions(&dp_extensions, &["EGL_EXT_device_base"]) {
has_extensions(
&dp_extensions,
&["EGL_EXT_device_enumeration", "EGL_EXT_device_query"],
)
.map_err(|_| Error::DeviceIsNoEGLStreamDevice)?;
}
// we can now query the amount of devices implementing the required extension
let mut num_devices = 0;
wrap_egl_call(|| ffi::egl::QueryDevicesEXT(0, ptr::null_mut(), &mut num_devices))
.map_err(Error::FailedToEnumerateDevices)?;
if num_devices == 0 {
return Err(Error::DeviceIsNoEGLStreamDevice);
}
// afterwards we can allocate a buffer large enough and query the actual device (this is a common pattern in egl).
let mut devices = Vec::with_capacity(num_devices as usize);
wrap_egl_call(|| ffi::egl::QueryDevicesEXT(num_devices, devices.as_mut_ptr(), &mut num_devices))
.map_err(Error::FailedToEnumerateDevices)?;
devices.set_len(num_devices as usize);
debug!(log, "Devices: {:#?}, Count: {}", devices, num_devices);
devices
.into_iter()
.find(|device| {
// we may get devices, that are - well - NO_DEVICE...
*device != ffi::egl::NO_DEVICE_EXT
&& {
// the device then also needs EGL_EXT_device_drm
let device_extensions = {
let p = ffi::egl::QueryDeviceStringEXT(*device, ffi::egl::EXTENSIONS as i32);
if p.is_null() {
vec![]
} else {
let p = CStr::from_ptr(p);
let list = String::from_utf8(p.to_bytes().to_vec())
.unwrap_or_else(|_| String::new());
list.split(' ').map(|e| e.to_string()).collect::<Vec<_>>()
}
};
device_extensions.iter().any(|s| *s == "EGL_EXT_device_drm")
}
&& {
// and we want to get the file descriptor to check, that we found
// the device the user wants to initialize.
//
// notice how this is kinda the other way around.
// EGL_EXT_device_query expects use to find all devices using this extension...
// But there is no way, we are going to replace our udev-interface with this, so we list devices
// just to find the id of the one, that we actually want, because we cannot
// request it directly afaik...
let path = {
let p = ffi::egl::QueryDeviceStringEXT(
*device,
ffi::egl::DRM_DEVICE_FILE_EXT as i32,
);
if p.is_null() {
String::new()
} else {
let p = CStr::from_ptr(p);
String::from_utf8(p.to_bytes().to_vec()).unwrap_or_else(|_| String::new())
}
};
match fs::metadata(&path) {
Ok(metadata) => metadata.rdev() == raw.device_id(),
Err(_) => false,
}
}
})
.ok_or(Error::DeviceIsNoEGLStreamDevice)?
};
// okay the device is compatible and found, ready to go.
Ok(EglStreamDevice {
dev: device,
raw,
backends: Rc::new(RefCell::new(HashMap::new())),
logger: log,
#[cfg(feature = "backend_session")]
links: Vec::new(),
})
}
}
struct InternalDeviceHandler<D: RawDevice + ControlDevice + 'static> {
handler: Box<dyn DeviceHandler<Device = EglStreamDevice<D>> + 'static>,
backends: Weak<RefCell<HashMap<crtc::Handle, SurfaceInternalRef<D>>>>,
logger: ::slog::Logger,
}
impl<D: RawDevice + ControlDevice + 'static> DeviceHandler for InternalDeviceHandler<D> {
type Device = D;
fn vblank(&mut self, crtc: crtc::Handle) {
if let Some(backends) = self.backends.upgrade() {
if let Some(surface) = backends.borrow().get(&crtc) {
if surface.upgrade().is_some() {
self.handler.vblank(crtc);
}
} else {
warn!(
self.logger,
"Surface ({:?}) not managed by egl stream, event not handled.", crtc
);
}
}
}
fn error(&mut self, error: <<D as Device>::Surface as Surface>::Error) {
self.handler.error(Error::Underlying(error))
}
}
impl<D: RawDevice + ControlDevice + 'static> Device for EglStreamDevice<D> {
type Surface = EglStreamSurface<<D as Device>::Surface>;
fn device_id(&self) -> dev_t {
self.raw.device_id()
}
fn set_handler(&mut self, handler: impl DeviceHandler<Device = Self> + 'static) {
self.raw.set_handler(InternalDeviceHandler {
handler: Box::new(handler),
backends: Rc::downgrade(&self.backends),
logger: self.logger.clone(),
});
}
fn clear_handler(&mut self) {
self.raw.clear_handler();
}
fn create_surface(
&mut self,
crtc: crtc::Handle,
mode: Mode,
connectors: &[connector::Handle],
) -> Result<EglStreamSurface<<D as Device>::Surface>, Error<<<D as Device>::Surface as Surface>::Error>>
{
info!(self.logger, "Initializing EglStreamSurface");
let drm_surface =
Device::create_surface(&mut self.raw, crtc, mode, connectors).map_err(Error::Underlying)?;
// initialize a buffer for the cursor image
let cursor = Some((
self.raw
.create_dumb_buffer((1, 1), PixelFormat::ARGB8888)
.compat()
.map_err(Error::BufferCreationFailed)?,
(0, 0),
));
let backend = Arc::new(EglStreamSurfaceInternal {
crtc: drm_surface,
cursor: Mutex::new(cursor),
stream: Mutex::new(None),
commit_buffer: Mutex::new(None),
logger: self.logger.new(o!("crtc" => format!("{:?}", crtc))),
locked: std::sync::atomic::AtomicBool::new(false),
});
self.backends.borrow_mut().insert(crtc, Arc::downgrade(&backend));
Ok(EglStreamSurface(backend))
}
fn process_events(&mut self) {
self.raw.process_events()
}
fn resource_handles(&self) -> Result<ResourceHandles, Error<<<D as Device>::Surface as Surface>::Error>> {
Device::resource_handles(&self.raw).map_err(Error::Underlying)
}
fn get_connector_info(&self, conn: connector::Handle) -> Result<connector::Info, DrmError> {
self.raw.get_connector_info(conn)
}
fn get_crtc_info(&self, crtc: crtc::Handle) -> Result<crtc::Info, DrmError> {
self.raw.get_crtc_info(crtc)
}
fn get_encoder_info(&self, enc: encoder::Handle) -> Result<encoder::Info, DrmError> {
self.raw.get_encoder_info(enc)
}
fn get_framebuffer_info(&self, fb: framebuffer::Handle) -> Result<framebuffer::Info, DrmError> {
self.raw.get_framebuffer_info(fb)
}
fn get_plane_info(&self, plane: plane::Handle) -> Result<plane::Info, DrmError> {
self.raw.get_plane_info(plane)
}
}
impl<D: RawDevice + ControlDevice + 'static> AsRawFd for EglStreamDevice<D> {
fn as_raw_fd(&self) -> RawFd {
self.raw.as_raw_fd()
}
}
impl<D: RawDevice + ControlDevice + 'static> Drop for EglStreamDevice<D> {
fn drop(&mut self) {
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::BufferCreationFailed(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(Error::<E>::BufferCreationFailed(x)))
}
Error::BufferWriteFailed(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(Error::<E>::BufferCreationFailed(x)))
}
Error::StreamFlipFailed(x @ RawEGLError::ResourceBusy) => {
SwapBuffersError::TemporaryFailure(Box::new(Error::<E>::StreamFlipFailed(x)))
}
Error::Underlying(x) => x.into(),
x => SwapBuffersError::ContextLost(Box::new(x)),
}
}
}

View File

@ -1,102 +0,0 @@
//!
//! Support to register a [`EglStreamDevice`](EglStreamDevice)
//! to an open [`Session`](::backend::session::Session).
//!
use super::{EglStreamDevice, EglStreamSurfaceInternal};
use crate::backend::drm::{Device, RawDevice, RawSurface};
use crate::backend::egl::ffi;
use crate::backend::session::Signal as SessionSignal;
use crate::signaling::{Linkable, Signaler};
use std::cell::RefCell;
use std::collections::HashMap;
use std::rc::{Rc, Weak};
use std::sync::Weak as WeakArc;
use drm::control::{crtc, Device as ControlDevice};
/// [`SessionObserver`](SessionObserver)
/// linked to the [`EglStreamDevice`](EglStreamDevice) it was
/// created from.
#[derive(Debug)]
pub struct EglStreamDeviceObserver<S: RawSurface + 'static> {
backends: Weak<RefCell<HashMap<crtc::Handle, WeakArc<EglStreamSurfaceInternal<S>>>>>,
logger: ::slog::Logger,
}
impl<D> Linkable<SessionSignal> for EglStreamDevice<D>
where
D: RawDevice + ControlDevice + Linkable<SessionSignal> + 'static,
{
fn link(&mut self, signaler: Signaler<SessionSignal>) {
let lower_signal = Signaler::new();
self.raw.link(lower_signal.clone());
let mut observer = EglStreamDeviceObserver::<<D as Device>::Surface> {
backends: Rc::downgrade(&self.backends),
logger: self.logger.clone(),
};
let token = signaler.register(move |&signal| match signal {
SessionSignal::ActivateSession | SessionSignal::ActivateDevice { .. } => {
// activate lower device *before* we process the signal
lower_signal.signal(signal);
observer.activate();
}
SessionSignal::PauseSession | SessionSignal::PauseDevice { .. } => {
// pause lower device *after* we process the signal
observer.pause();
lower_signal.signal(signal);
}
});
self.links.push(token);
}
}
impl<S: RawSurface + 'static> EglStreamDeviceObserver<S> {
fn pause(&mut self) {
if let Some(backends) = self.backends.upgrade() {
for (_, backend) in backends.borrow().iter() {
if let Some(backend) = backend.upgrade() {
// destroy/disable the streams so it will not submit any pending frames
if let Some((display, stream)) = backend.stream.lock().unwrap().take() {
unsafe {
ffi::egl::DestroyStreamKHR(display.handle, stream.0);
}
}
// framebuffers will be likely not valid anymore, lets just recreate those after activation.
if let Some((buffer, fb)) = backend.commit_buffer.lock().unwrap().take() {
let _ = backend.crtc.destroy_framebuffer(fb);
let _ = backend.crtc.destroy_dumb_buffer(buffer);
}
}
}
}
}
fn activate(&mut self) {
if let Some(backends) = self.backends.upgrade() {
for (_, backend) in backends.borrow().iter() {
if let Some(backend) = backend.upgrade() {
let cursor = backend.cursor.lock().unwrap();
if let Some((ref cursor, ref hotspot)) = &*cursor {
if backend
.crtc
.set_cursor2(
backend.crtc.crtc(),
Some(cursor),
(hotspot.0 as i32, hotspot.1 as i32),
)
.is_err()
{
if let Err(err) = backend.crtc.set_cursor(backend.crtc.crtc(), Some(cursor)) {
warn!(self.logger, "Failed to reset cursor: {}", err)
}
}
}
}
}
}
}
}

View File

@ -1,601 +0,0 @@
use super::super::{RawSurface, Surface};
use super::Error;
use drm::buffer::format::PixelFormat;
use drm::control::{connector, crtc, dumbbuffer::DumbBuffer, framebuffer, Mode};
#[cfg(feature = "backend_drm")]
use failure::ResultExt;
#[cfg(feature = "backend_drm")]
use image::{ImageBuffer, Rgba};
use nix::libc::{c_int, c_void};
use std::ffi::CStr;
use std::ptr;
use std::sync::{
atomic::{AtomicBool, Ordering},
Arc, Mutex,
};
#[cfg(feature = "backend_drm")]
use crate::backend::drm::common::Error as DrmError;
#[cfg(feature = "backend_drm")]
use crate::backend::drm::DevPath;
use crate::backend::egl::ffi::{
self,
egl::{self, types::EGLStreamKHR},
};
use crate::backend::egl::{display::EGLDisplayHandle, wrap_egl_call, EGLError, SwapBuffersError};
#[cfg(feature = "backend_drm")]
use crate::backend::graphics::CursorBackend;
// We do not want to mark the whole surface as `Send` in case we screw up somewhere else
// and because S needs to be `Send` as well for this to work.
#[derive(Debug)]
pub(super) struct StreamHandle(pub(super) EGLStreamKHR);
// EGLStreamKHR can be moved between threads
unsafe impl Send for StreamHandle {}
#[derive(Debug)]
pub(in crate::backend::drm) struct EglStreamSurfaceInternal<S: RawSurface + 'static> {
pub(in crate::backend::drm) crtc: S,
pub(in crate::backend::drm) cursor: Mutex<Option<(DumbBuffer, (u32, u32))>>,
pub(super) stream: Mutex<Option<(Arc<EGLDisplayHandle>, StreamHandle)>>,
pub(in crate::backend::drm) commit_buffer: Mutex<Option<(DumbBuffer, framebuffer::Handle)>>,
pub(in crate::backend::drm) locked: AtomicBool,
pub(in crate::backend::drm) logger: ::slog::Logger,
}
impl<S: RawSurface + 'static> Surface for EglStreamSurfaceInternal<S> {
type Connectors = <S as Surface>::Connectors;
type Error = Error<<S as Surface>::Error>;
fn crtc(&self) -> crtc::Handle {
self.crtc.crtc()
}
fn current_connectors(&self) -> Self::Connectors {
self.crtc.current_connectors()
}
fn pending_connectors(&self) -> Self::Connectors {
self.crtc.pending_connectors()
}
fn add_connector(&self, connector: connector::Handle) -> Result<(), Self::Error> {
self.crtc.add_connector(connector).map_err(Error::Underlying)
}
fn remove_connector(&self, connector: connector::Handle) -> Result<(), Self::Error> {
self.crtc.remove_connector(connector).map_err(Error::Underlying)
}
fn set_connectors(&self, connectors: &[connector::Handle]) -> Result<(), Self::Error> {
self.crtc.set_connectors(connectors).map_err(Error::Underlying)
}
fn current_mode(&self) -> Mode {
self.crtc.current_mode()
}
fn pending_mode(&self) -> Mode {
self.crtc.pending_mode()
}
fn use_mode(&self, mode: Mode) -> Result<(), Error<<S as Surface>::Error>> {
self.crtc.use_mode(mode).map_err(Error::Underlying)
}
}
impl<S: RawSurface + 'static> Drop for EglStreamSurfaceInternal<S> {
fn drop(&mut self) {
if let Some((buffer, _)) = self.cursor.lock().unwrap().take() {
let _ = self.crtc.destroy_dumb_buffer(buffer);
}
if let Some((buffer, fb)) = self.commit_buffer.lock().unwrap().take() {
let _ = self.crtc.destroy_framebuffer(fb);
let _ = self.crtc.destroy_dumb_buffer(buffer);
}
if let Some((display, stream)) = self.stream.lock().unwrap().take() {
unsafe {
egl::DestroyStreamKHR(display.handle, stream.0);
}
}
}
}
// Conceptionally EglStream is a weird api.
// It does modesetting on its own, bypassing our `RawSurface::commit` function.
// As a result, we cannot easily sync any other planes to the commit without more
// experimental egl extensions to do this via the EglStream-API.
//
// So instead we leverage the fact, that all drm-drivers still support the legacy
// `drmModeSetCursor` and `drmModeMoveCursor` functions, that (mostly) implicitly sync to the primary plane.
// That way we can use hardware cursors at least on all drm-backends (including atomic), although
// this is a little hacky. Overlay planes however are completely out of question for now.
//
// Note that this might still appear a little choppy, we should just use software cursors
// on eglstream devices by default and only use this, if the user really wants it.
#[cfg(feature = "backend_drm")]
impl<S: RawSurface + 'static> CursorBackend for EglStreamSurfaceInternal<S> {
type CursorFormat = ImageBuffer<Rgba<u8>, Vec<u8>>;
type Error = Error<DrmError>;
fn set_cursor_position(&self, x: u32, y: u32) -> Result<(), Self::Error> {
trace!(self.logger, "Move the cursor to {},{}", x, y);
self.crtc
.move_cursor(self.crtc.crtc(), (x as i32, y as i32))
.compat()
.map_err(|source| DrmError::Access {
errmsg: "Error moving cursor",
dev: self.crtc.dev_path(),
source,
})
.map_err(Error::Underlying)
}
fn set_cursor_representation(
&self,
buffer: &ImageBuffer<Rgba<u8>, Vec<u8>>,
hotspot: (u32, u32),
) -> Result<(), Self::Error> {
let (w, h) = buffer.dimensions();
debug!(self.logger, "Importing cursor");
// import the cursor into a buffer we can render
let mut cursor = self
.crtc
.create_dumb_buffer((w, h), PixelFormat::ARGB8888)
.compat()
.map_err(Error::BufferCreationFailed)?;
{
let mut mapping = self
.crtc
.map_dumb_buffer(&mut cursor)
.compat()
.map_err(Error::BufferWriteFailed)?;
mapping.as_mut().copy_from_slice(buffer);
}
trace!(self.logger, "Setting the new imported cursor");
trace!(self.logger, "Setting the new imported cursor");
// call the drm-functions directly to bypass ::commit/::page_flip and eglstream...
if self
.crtc
.set_cursor2(
self.crtc.crtc(),
Some(&cursor),
(hotspot.0 as i32, hotspot.1 as i32),
)
.is_err()
{
self.crtc
.set_cursor(self.crtc.crtc(), Some(&cursor))
.compat()
.map_err(|source| DrmError::Access {
errmsg: "Failed to set cursor",
dev: self.crtc.dev_path(),
source,
})
.map_err(Error::Underlying)?;
}
// and store it
if let Some((old, _)) = self.cursor.lock().unwrap().replace((cursor, hotspot)) {
if self.crtc.destroy_dumb_buffer(old).is_err() {
warn!(self.logger, "Failed to free old cursor");
}
}
Ok(())
}
fn clear_cursor_representation(&self) -> Result<(), Self::Error> {
self.crtc
.set_cursor(self.crtc.crtc(), Option::<&DumbBuffer>::None)
.compat()
.map_err(|source| DrmError::Access {
errmsg: "Failed to clear cursor",
dev: self.crtc.dev_path(),
source,
})
.map_err(Error::Underlying)
}
}
/// egl stream surface for rendering
#[derive(Debug)]
pub struct EglStreamSurface<S: RawSurface + 'static>(
pub(in crate::backend::drm) Arc<EglStreamSurfaceInternal<S>>,
);
impl<S: RawSurface + 'static> EglStreamSurface<S> {
/// Check if underlying gbm resources need to be recreated.
pub fn needs_recreation(&self) -> bool {
self.0.crtc.commit_pending() || self.0.stream.lock().unwrap().is_none()
}
// An EGLStream is basically the pump that requests and consumes images to display them.
// The heart of this weird api. Any changes to its configuration, require a re-creation.
pub(super) fn create_stream(
&self,
display: &Arc<EGLDisplayHandle>,
output_attribs: &[isize],
) -> Result<EGLStreamKHR, Error<<S as Surface>::Error>> {
let mut stream = self.0.stream.lock().unwrap();
// drop old steam, if it already exists
if let Some((display, stream)) = stream.take() {
// ignore result
unsafe {
ffi::egl::DestroyStreamKHR(display.handle, stream.0);
}
}
// because we are re-creating there might be a new mode. if there is? -> commit it
if self.0.crtc.commit_pending() {
let (w, h) = self.pending_mode().size();
// but we need a buffer to commit...
// well lets create one and clean it up once the stream is running
if let Ok(buffer) = self
.0
.crtc
.create_dumb_buffer((w as u32, h as u32), PixelFormat::ARGB8888)
{
if let Ok(fb) = self.0.crtc.add_framebuffer(&buffer) {
if let Some((buffer, fb)) = self.0.commit_buffer.lock().unwrap().replace((buffer, fb)) {
let _ = self.0.crtc.destroy_framebuffer(fb);
let _ = self.0.crtc.destroy_dumb_buffer(buffer);
}
self.0.crtc.commit(fb).map_err(Error::Underlying)?;
}
}
}
// again enumerate extensions
let extensions = {
let p =
unsafe { CStr::from_ptr(ffi::egl::QueryString(display.handle, ffi::egl::EXTENSIONS as i32)) };
let list = String::from_utf8(p.to_bytes().to_vec()).unwrap_or_else(|_| String::new());
list.split(' ').map(|e| e.to_string()).collect::<Vec<_>>()
};
// we need quite a bunch to implement a full-blown renderer.
if !extensions.iter().any(|s| *s == "EGL_EXT_output_base")
|| !extensions.iter().any(|s| *s == "EGL_EXT_output_drm")
|| !extensions.iter().any(|s| *s == "EGL_KHR_stream")
|| !extensions
.iter()
.any(|s| *s == "EGL_EXT_stream_consumer_egloutput")
|| !extensions
.iter()
.any(|s| *s == "EGL_KHR_stream_producer_eglsurface")
{
error!(self.0.logger, "Extension for EGLStream surface creation missing");
return Err(Error::DeviceIsNoEGLStreamDevice);
}
if cfg!(debug_assertions) {
// TEST START
let mut num_layers = 0;
if unsafe {
ffi::egl::GetOutputLayersEXT(
display.handle,
ptr::null(),
ptr::null_mut(),
10,
&mut num_layers,
)
} == 0
{
error!(self.0.logger, "Failed to get any! output layer");
}
if num_layers == 0 {
error!(self.0.logger, "Failed to find any! output layer");
}
let mut layers = Vec::with_capacity(num_layers as usize);
if unsafe {
ffi::egl::GetOutputLayersEXT(
display.handle,
ptr::null(),
layers.as_mut_ptr(),
num_layers,
&mut num_layers,
)
} == 0
{
error!(self.0.logger, "Failed to receive Output Layers");
}
unsafe {
layers.set_len(num_layers as usize);
}
for layer in layers {
debug!(self.0.logger, "Found layer: {:?}", layer);
let mut val = 0;
if unsafe {
ffi::egl::QueryOutputLayerAttribEXT(
display.handle,
layer,
ffi::egl::DRM_CRTC_EXT as i32,
&mut val,
)
} != 0
{
info!(self.0.logger, "Possible crtc output layer: {}", val);
}
val = 0;
if unsafe {
ffi::egl::QueryOutputLayerAttribEXT(
display.handle,
layer,
ffi::egl::DRM_PLANE_EXT as i32,
&mut val,
)
} != 0
{
info!(self.0.logger, "Possible plane output layer: {}", val);
}
}
// TEST END
}
// alright, if the surface appears to be supported, we need an "output layer".
// this is basically just a fancy name for a `crtc` or a `plane`.
// those are exactly whats inside the `output_attribs` depending on underlying device.
let mut num_layers = 0;
if unsafe {
ffi::egl::GetOutputLayersEXT(
display.handle,
output_attribs.as_ptr(),
ptr::null_mut(),
1,
&mut num_layers,
)
} == 0
{
error!(
self.0.logger,
"Failed to acquire Output Layer. Attributes {:?}", output_attribs
);
return Err(Error::DeviceNoOutputLayer);
}
if num_layers == 0 {
error!(self.0.logger, "Failed to find Output Layer");
return Err(Error::DeviceNoOutputLayer);
}
let mut layers = Vec::with_capacity(num_layers as usize);
if unsafe {
ffi::egl::GetOutputLayersEXT(
display.handle,
output_attribs.as_ptr(),
layers.as_mut_ptr(),
num_layers,
&mut num_layers,
)
} == 0
{
error!(self.0.logger, "Failed to get Output Layer");
return Err(Error::DeviceNoOutputLayer);
}
unsafe {
layers.set_len(num_layers as usize);
}
// lets just use the first layer and try to set the swap interval.
// this is needed to make sure `eglSwapBuffers` does not block.
let layer = layers[0];
unsafe {
ffi::egl::OutputLayerAttribEXT(display.handle, layer, ffi::egl::SWAP_INTERVAL_EXT as i32, 0);
}
// The stream needs to know, it needs to request frames
// as soon as another one is rendered (we do not want to build a buffer and delay frames),
// which is handled by STREAM_FIFO_LENGTH_KHR = 0.
// We also want to "acquire" the frames manually. Like this we can request page-flip events
// to drive our event loop. Otherwise we would have no way to know rendering is finished.
let stream_attributes = {
let mut out: Vec<c_int> = Vec::with_capacity(7);
out.push(ffi::egl::STREAM_FIFO_LENGTH_KHR as i32);
out.push(0);
out.push(ffi::egl::CONSUMER_AUTO_ACQUIRE_EXT as i32);
out.push(ffi::egl::FALSE as i32);
out.push(ffi::egl::CONSUMER_ACQUIRE_TIMEOUT_USEC_KHR as i32);
out.push(0);
out.push(ffi::egl::NONE as i32);
out
};
// okay, we have a config, lets create the stream.
let raw_stream = unsafe { ffi::egl::CreateStreamKHR(display.handle, stream_attributes.as_ptr()) };
if raw_stream == ffi::egl::NO_STREAM_KHR {
error!(self.0.logger, "Failed to create egl stream");
return Err(Error::DeviceStreamCreationFailed);
}
// we have a stream, lets connect it to our output layer
if unsafe { ffi::egl::StreamConsumerOutputEXT(display.handle, raw_stream, layer) } == 0 {
error!(self.0.logger, "Failed to link Output Layer as Stream Consumer");
return Err(Error::DeviceStreamCreationFailed);
}
*stream = Some((display.clone(), StreamHandle(raw_stream)));
Ok(raw_stream)
}
pub(super) fn create_surface(
&self,
display: &Arc<EGLDisplayHandle>,
config_id: ffi::egl::types::EGLConfig,
_surface_attribs: &[c_int],
output_attribs: &[isize],
) -> Result<*const c_void, Error<<S as Surface>::Error>> {
// our surface needs a stream
let stream = self.create_stream(display, output_attribs)?;
let (w, h) = self.current_mode().size();
info!(self.0.logger, "Creating stream surface with size: ({}:{})", w, h);
let surface_attributes = {
let mut out: Vec<c_int> = Vec::with_capacity(5);
out.push(ffi::egl::WIDTH as i32);
out.push(w as i32);
out.push(ffi::egl::HEIGHT as i32);
out.push(h as i32);
out.push(ffi::egl::NONE as i32);
out
};
// the stream is already connected to the consumer (output layer) during creation.
// we now connect the producer (out egl surface, that we render to).
let surface = unsafe {
ffi::egl::CreateStreamProducerSurfaceKHR(
display.handle,
config_id,
stream,
surface_attributes.as_ptr(),
)
};
if surface == ffi::egl::NO_SURFACE {
error!(self.0.logger, "Failed to create surface: 0x{:X}", unsafe {
ffi::egl::GetError()
});
}
Ok(surface)
}
pub(super) fn flip(
&self,
crtc: crtc::Handle,
display: &Arc<EGLDisplayHandle>,
surface: ffi::egl::types::EGLSurface,
) -> Result<(), SwapBuffersError<Error<<S as Surface>::Error>>> {
// if we have already swapped the buffer successfully, we need to free it again.
//
// we need to do this here, because the call may fail (compare this with gbm's unlock_buffer...).
// if it fails we do not want to swap, because that would block and as a result deadlock us.
if self.0.locked.load(Ordering::SeqCst) {
// which means in eglstream terms: "acquire it".
// here we set the user data of the page_flip event
// (which is also only triggered if we manually acquire frames).
// This is the crtc id like always to get the matching surface for the device handler later.
let acquire_attributes = [
ffi::egl::DRM_FLIP_EVENT_DATA_NV as isize,
Into::<u32>::into(crtc) as isize,
ffi::egl::NONE as isize,
];
if let Ok(stream) = self.0.stream.try_lock() {
// lets try to acquire the frame.
// this may fail, if the buffer is still in use by the gpu,
// e.g. the flip was not done yet. In this case this call fails as `BUSY`.
let res = if let Some(&(ref display, ref stream)) = stream.as_ref() {
wrap_egl_call(|| unsafe {
ffi::egl::StreamConsumerAcquireAttribNV(
display.handle,
stream.0,
acquire_attributes.as_ptr(),
);
})
.map_err(Error::StreamFlipFailed)
} else {
Err(Error::StreamFlipFailed(EGLError::NotInitialized))
};
// so we need to unlock on success and return on failure.
if res.is_ok() {
self.0.locked.store(false, Ordering::SeqCst);
} else {
return res.map_err(SwapBuffersError::Underlying);
}
}
}
// so if we are not locked any more we can send the next frame by calling swap buffers.
if !self.0.locked.load(Ordering::SeqCst) {
wrap_egl_call(|| unsafe { ffi::egl::SwapBuffers(***display, surface as *const _) })
.map_err(SwapBuffersError::EGLSwapBuffers)?;
self.0.locked.store(true, Ordering::SeqCst);
}
Ok(())
}
}
impl<S: RawSurface + 'static> Surface for EglStreamSurface<S> {
type Connectors = <S as Surface>::Connectors;
type Error = Error<<S as Surface>::Error>;
fn crtc(&self) -> crtc::Handle {
self.0.crtc()
}
fn current_connectors(&self) -> Self::Connectors {
self.0.current_connectors()
}
fn pending_connectors(&self) -> Self::Connectors {
self.0.pending_connectors()
}
fn add_connector(&self, connector: connector::Handle) -> Result<(), Self::Error> {
self.0.add_connector(connector)
}
fn remove_connector(&self, connector: connector::Handle) -> Result<(), Self::Error> {
self.0.remove_connector(connector)
}
fn set_connectors(&self, connectors: &[connector::Handle]) -> Result<(), Self::Error> {
self.0.set_connectors(connectors)
}
fn current_mode(&self) -> Mode {
self.0.current_mode()
}
fn pending_mode(&self) -> Mode {
self.0.pending_mode()
}
fn use_mode(&self, mode: Mode) -> Result<(), Self::Error> {
self.0.use_mode(mode)
}
}
#[cfg(feature = "backend_drm_legacy")]
impl<S: RawSurface + 'static> CursorBackend for EglStreamSurface<S> {
type CursorFormat = ImageBuffer<Rgba<u8>, Vec<u8>>;
type Error = Error<DrmError>;
fn set_cursor_position(&self, x: u32, y: u32) -> Result<(), Self::Error> {
self.0.set_cursor_position(x, y)
}
fn set_cursor_representation(
&self,
buffer: &ImageBuffer<Rgba<u8>, Vec<u8>>,
hotspot: (u32, u32),
) -> Result<(), Self::Error> {
self.0.set_cursor_representation(buffer, hotspot)
}
fn clear_cursor_representation(&self) -> Result<(), Self::Error> {
self.0.clear_cursor_representation()
}
}
#[cfg(test)]
mod test {
use super::EglStreamSurface;
use crate::backend::drm::legacy::LegacyDrmSurface;
use std::fs::File;
fn is_send<S: Send>() {}
#[test]
fn surface_is_send() {
is_send::<EglStreamSurface<LegacyDrmSurface<File>>>();
}
}

View File

@ -1,18 +1,9 @@
//! //use crate::backend::graphics::SwapBuffersError;
//! Module for common/shared types of the various [`Device`](::backend::drm::Device) use drm::control::{connector, crtc, plane, Mode, RawResourceHandle};
//! 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; use std::path::PathBuf;
pub mod fallback; /// Errors thrown by the [`DrmDevice`](::backend::drm::DrmDevice)
/// and the [`DrmSurface`](::backend::drm::DrmSurface).
/// Errors thrown by the [`LegacyDrmDevice`](::backend::drm::legacy::LegacyDrmDevice),
/// [`AtomicDrmDevice`](::backend::drm::atomic::AtomicDrmDevice)
/// and their surfaces: [`LegacyDrmSurface`](::backend::drm::legacy::LegacyDrmSurface)
/// and [`AtomicDrmSurface`](::backend::drm::atomic::AtomicDrmSurface).
#[derive(thiserror::Error, Debug)] #[derive(thiserror::Error, Debug)]
pub enum Error { pub enum Error {
/// Unable to acquire DRM master /// Unable to acquire DRM master
@ -26,7 +17,7 @@ pub enum Error {
/// Device on which the error was generated /// Device on which the error was generated
dev: Option<PathBuf>, dev: Option<PathBuf>,
/// Underlying device error /// Underlying device error
source: failure::Compat<drm::SystemError>, source: drm::SystemError,
}, },
/// Unable to determine device id of drm device /// Unable to determine device id of drm device
#[error("Unable to determine device id of drm device")] #[error("Unable to determine device id of drm device")]
@ -43,6 +34,10 @@ pub enum Error {
/// This operation would result in a surface without connectors. /// This operation would result in a surface without connectors.
#[error("Surface of crtc `{0:?}` would have no connectors, which is not accepted")] #[error("Surface of crtc `{0:?}` would have no connectors, which is not accepted")]
SurfaceWithoutConnectors(crtc::Handle), SurfaceWithoutConnectors(crtc::Handle),
#[error("Plane `{1:?}` is not compatible for use with crtc `{0:?}`")]
PlaneNotCompatible(crtc::Handle, plane::Handle),
#[error("Non-Primary Planes (provided was `{0:?}`) are not available for use with legacy devices")]
NonPrimaryPlane(plane::Handle),
/// No encoder was found for a given connector on the set crtc /// No encoder was found for a given connector on the set crtc
#[error("No encoder found for the given connector '{connector:?}' on crtc `{crtc:?}`")] #[error("No encoder found for the given connector '{connector:?}' on crtc `{crtc:?}`")]
NoSuitableEncoder { NoSuitableEncoder {
@ -51,14 +46,6 @@ pub enum Error {
/// CRTC /// CRTC
crtc: crtc::Handle, crtc: crtc::Handle,
}, },
/// No matching primary and cursor plane could be found for the given crtc
#[error("No matching primary and cursor plane could be found for crtc {crtc:?} on {dev:?}")]
NoSuitablePlanes {
/// CRTC
crtc: crtc::Handle,
/// Device on which the error was generated
dev: Option<PathBuf>,
},
/// The DrmDevice is missing a required property /// The DrmDevice is missing a required property
#[error("The DrmDevice is missing a required property '{name}' for handle ({handle:?})")] #[error("The DrmDevice is missing a required property '{name}' for handle ({handle:?})")]
UnknownProperty { UnknownProperty {
@ -72,6 +59,7 @@ pub enum Error {
TestFailed(crtc::Handle), TestFailed(crtc::Handle),
} }
/*
impl Into<SwapBuffersError> for Error { impl Into<SwapBuffersError> for Error {
fn into(self) -> SwapBuffersError { fn into(self) -> SwapBuffersError {
match self { match self {
@ -95,3 +83,4 @@ impl Into<SwapBuffersError> for Error {
} }
} }
} }
*/

View File

@ -1,126 +0,0 @@
//!
//! Egl [`NativeDisplay`](::backend::egl::native::NativeDisplay) and
//! [`NativeSurface`](::backend::egl::native::NativeSurface) support for
//! [`GbmDevice`](GbmDevice) and [`GbmSurface`](GbmSurface).
//!
use crate::backend::drm::{Device, RawDevice, RawSurface, Surface};
use crate::backend::egl::native::{Backend, NativeDisplay, NativeSurface};
use crate::backend::egl::{display::EGLDisplayHandle, ffi};
use crate::backend::egl::{
wrap_egl_call, EGLError, Error as EglBackendError, SurfaceCreationError, SwapBuffersError,
};
use super::{Error, GbmDevice, GbmSurface};
use drm::control::{connector, crtc, Device as ControlDevice, Mode};
use gbm::AsRaw;
use nix::libc::{c_int, c_void};
use std::marker::PhantomData;
use std::sync::Arc;
/// Egl Gbm backend type
///
/// See [`Backend`](::backend::egl::native::Backend).
pub struct Gbm<D: RawDevice + 'static> {
_userdata: PhantomData<D>,
}
impl<D: RawDevice + 'static> Backend for Gbm<D> {
type Surface = GbmSurface<<D as Device>::Surface>;
type Error = Error<<<D as Device>::Surface as Surface>::Error>;
// this creates an EGLDisplay for the gbm platform.
unsafe fn get_display<F>(
display: ffi::NativeDisplayType,
attribs: &[ffi::EGLint],
has_dp_extension: F,
log: ::slog::Logger,
) -> 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");
let attribs = attribs.iter().map(|x| *x as isize).collect::<Vec<_>>();
wrap_egl_call(|| {
ffi::egl::GetPlatformDisplay(ffi::egl::PLATFORM_GBM_KHR, display as *mut _, attribs.as_ptr())
})
} else if has_dp_extension("EGL_MESA_platform_gbm") && ffi::egl::GetPlatformDisplayEXT::is_loaded() {
trace!(log, "EGL Display Initialization via EGL_MESA_platform_gbm");
wrap_egl_call(|| {
ffi::egl::GetPlatformDisplayEXT(
ffi::egl::PLATFORM_GBM_MESA,
display as *mut _,
attribs.as_ptr(),
)
})
} else if has_dp_extension("EGL_MESA_platform_gbm") && ffi::egl::GetPlatformDisplay::is_loaded() {
trace!(log, "EGL Display Initialization via EGL_MESA_platform_gbm");
let attribs = attribs.iter().map(|x| *x as isize).collect::<Vec<_>>();
wrap_egl_call(|| {
ffi::egl::GetPlatformDisplay(ffi::egl::PLATFORM_GBM_MESA, display as *mut _, attribs.as_ptr())
})
} else {
Ok(ffi::egl::NO_DISPLAY)
}
}
}
unsafe impl<D: RawDevice + ControlDevice + 'static> NativeDisplay<Gbm<D>> for GbmDevice<D> {
type Arguments = (crtc::Handle, Mode, Vec<connector::Handle>);
fn is_backend(&self) -> bool {
true
}
fn ptr(&self) -> Result<ffi::NativeDisplayType, EglBackendError> {
Ok(self.dev.lock().unwrap().as_raw() as *const _)
}
fn create_surface(
&mut self,
args: Self::Arguments,
) -> Result<GbmSurface<<D as Device>::Surface>, Error<<<D as Device>::Surface as Surface>::Error>> {
Device::create_surface(self, args.0, args.1, &args.2)
}
}
unsafe impl<S: RawSurface + 'static> NativeSurface for GbmSurface<S> {
type Error = Error<<S as Surface>::Error>;
unsafe fn create(
&self,
display: &Arc<EGLDisplayHandle>,
config_id: ffi::egl::types::EGLConfig,
surface_attributes: &[c_int],
) -> Result<*const c_void, SurfaceCreationError<Self::Error>> {
GbmSurface::recreate(self).map_err(SurfaceCreationError::NativeSurfaceCreationFailed)?;
wrap_egl_call(|| {
ffi::egl::CreateWindowSurface(
display.handle,
config_id,
self.0.surface.lock().unwrap().as_raw() as *const _,
surface_attributes.as_ptr(),
)
})
.map_err(SurfaceCreationError::EGLSurfaceCreationFailed)
}
fn needs_recreation(&self) -> bool {
self.needs_recreation()
}
fn swap_buffers(
&self,
display: &Arc<EGLDisplayHandle>,
surface: ffi::egl::types::EGLSurface,
) -> Result<(), SwapBuffersError<Self::Error>> {
wrap_egl_call(|| unsafe { ffi::egl::SwapBuffers(***display, surface as *const _) })
.map_err(SwapBuffersError::EGLSwapBuffers)?;
// 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() }.map_err(SwapBuffersError::Underlying)
}
}

View File

@ -1,264 +0,0 @@
//!
//! [`Device`](Device) and [`Surface`](Surface)
//! implementations using gbm buffers for efficient rendering.
//!
//! Usually this implementation will be wrapped into a [`EglDevice`](::backend::drm::egl::EglDevice).
//! Take a look at `anvil`s source code for an example of this.
//!
//! To use these types standalone, you will need to consider the special requirements
//! of [`GbmSurface::page_flip`](::backend::drm::gbm::GbmSurface::page_flip).
//!
//! For detailed overview of these abstractions take a look at the module documentation of backend::drm.
//!
use super::{Device, DeviceHandler, RawDevice, ResourceHandles, Surface};
use crate::backend::graphics::SwapBuffersError;
use drm::control::{connector, crtc, encoder, framebuffer, plane, Device as ControlDevice, Mode};
use drm::SystemError as DrmError;
use nix::libc::dev_t;
use std::cell::RefCell;
use std::collections::HashMap;
use std::io;
use std::os::unix::io::{AsRawFd, RawFd};
use std::rc::{Rc, Weak};
use std::sync::{Arc, Mutex, Once, Weak as WeakArc};
/// 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 + '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),
/// 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,
/// Underlying backend error
#[error("Underlying error: {0}")]
Underlying(#[source] U),
}
mod surface;
pub use self::surface::GbmSurface;
use self::surface::GbmSurfaceInternal;
#[cfg(feature = "backend_egl")]
pub mod egl;
#[cfg(feature = "backend_session")]
pub mod session;
static LOAD: Once = Once::new();
type SurfaceInternalRef<D> = WeakArc<GbmSurfaceInternal<<D as Device>::Surface>>;
/// Representation of an open gbm device to create rendering surfaces
pub struct GbmDevice<D: RawDevice + ControlDevice + 'static> {
pub(self) dev: Arc<Mutex<gbm::Device<gbm::FdWrapper>>>,
pub(self) raw: D,
backends: Rc<RefCell<HashMap<crtc::Handle, SurfaceInternalRef<D>>>>,
#[cfg(feature = "backend_session")]
links: Vec<crate::signaling::SignalToken>,
logger: ::slog::Logger,
}
impl<D: RawDevice + ControlDevice + 'static> GbmDevice<D> {
/// Create a new [`GbmDevice`] from an open drm node
///
/// Returns an error if the file is no valid drm node or context creation was not
/// successful.
pub fn new<L>(mut dev: D, logger: L) -> Result<Self, Error<<<D as Device>::Surface as Surface>::Error>>
where
L: Into<Option<::slog::Logger>>,
{
/* GBM will load a dri driver, but even though they need symbols from
* libglapi, in some version of Mesa they are not linked to it. Since
* only the gl-renderer module links to it, these symbols won't be
* globally available, and loading the DRI driver fails.
* Workaround this by dlopen()'ing libglapi with RTLD_GLOBAL.
*/
LOAD.call_once(|| unsafe {
nix::libc::dlopen(
"libglapi.so.0".as_ptr() as *const _,
nix::libc::RTLD_LAZY | nix::libc::RTLD_GLOBAL,
);
});
let log = crate::slog_or_fallback(logger).new(o!("smithay_module" => "backend_gbm"));
dev.clear_handler();
debug!(log, "Creating gbm device");
Ok(GbmDevice {
// Open the gbm device from the drm device
dev: Arc::new(Mutex::new(unsafe {
gbm::Device::new_from_fd(dev.as_raw_fd()).map_err(Error::InitFailed)?
})),
raw: dev,
backends: Rc::new(RefCell::new(HashMap::new())),
#[cfg(feature = "backend_session")]
links: Vec::new(),
logger: log,
})
}
}
struct InternalDeviceHandler<D: RawDevice + ControlDevice + 'static> {
handler: Box<dyn DeviceHandler<Device = GbmDevice<D>> + 'static>,
backends: Weak<RefCell<HashMap<crtc::Handle, SurfaceInternalRef<D>>>>,
logger: ::slog::Logger,
}
impl<D: RawDevice + ControlDevice + 'static> DeviceHandler for InternalDeviceHandler<D> {
type Device = D;
fn vblank(&mut self, crtc: crtc::Handle) {
if let Some(backends) = self.backends.upgrade() {
if let Some(surface) = backends.borrow().get(&crtc) {
if let Some(surface) = surface.upgrade() {
// here we unlock the buffer again, that was locked during rendering,
// to make sure it is always unlocked after a successful page_flip.
surface.unlock_buffer();
self.handler.vblank(crtc);
}
} else {
warn!(
self.logger,
"Surface ({:?}) not managed by gbm, event not handled.", crtc
);
}
}
}
fn error(&mut self, error: <<D as Device>::Surface as Surface>::Error) {
self.handler.error(Error::Underlying(error))
}
}
impl<D: RawDevice + ControlDevice + 'static> Device for GbmDevice<D> {
type Surface = GbmSurface<<D as Device>::Surface>;
fn device_id(&self) -> dev_t {
self.raw.device_id()
}
fn set_handler(&mut self, handler: impl DeviceHandler<Device = Self> + 'static) {
self.raw.set_handler(InternalDeviceHandler {
handler: Box::new(handler),
backends: Rc::downgrade(&self.backends),
logger: self.logger.clone(),
});
}
fn clear_handler(&mut self) {
self.raw.clear_handler();
}
fn create_surface(
&mut self,
crtc: crtc::Handle,
mode: Mode,
connectors: &[connector::Handle],
) -> Result<GbmSurface<<D as Device>::Surface>, Error<<<D as Device>::Surface as Surface>::Error>> {
info!(self.logger, "Initializing GbmSurface");
let drm_surface = self
.raw
.create_surface(crtc, mode, connectors)
.map_err(Error::Underlying)?;
let backend = Arc::new(GbmSurfaceInternal::new(
self.dev.clone(),
drm_surface,
self.logger.new(o!("crtc" => format!("{:?}", crtc))),
)?);
self.backends.borrow_mut().insert(crtc, Arc::downgrade(&backend));
Ok(GbmSurface(backend))
}
fn process_events(&mut self) {
self.raw.process_events()
}
fn resource_handles(&self) -> Result<ResourceHandles, Error<<<D as Device>::Surface as Surface>::Error>> {
Device::resource_handles(&self.raw).map_err(Error::Underlying)
}
fn get_connector_info(&self, conn: connector::Handle) -> std::result::Result<connector::Info, DrmError> {
self.raw.get_connector_info(conn)
}
fn get_crtc_info(&self, crtc: crtc::Handle) -> std::result::Result<crtc::Info, DrmError> {
self.raw.get_crtc_info(crtc)
}
fn get_encoder_info(&self, enc: encoder::Handle) -> std::result::Result<encoder::Info, DrmError> {
self.raw.get_encoder_info(enc)
}
fn get_framebuffer_info(
&self,
fb: framebuffer::Handle,
) -> std::result::Result<framebuffer::Info, DrmError> {
self.raw.get_framebuffer_info(fb)
}
fn get_plane_info(&self, plane: plane::Handle) -> std::result::Result<plane::Info, DrmError> {
self.raw.get_plane_info(plane)
}
}
impl<D: RawDevice + ControlDevice + 'static> AsRawFd for GbmDevice<D> {
fn as_raw_fd(&self) -> RawFd {
self.raw.as_raw_fd()
}
}
impl<D: RawDevice + ControlDevice + 'static> Drop for GbmDevice<D> {
fn drop(&mut self) {
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(Error::<E>::FramebufferCreationFailed(x)))
}
Error::Underlying(x) => x.into(),
x => SwapBuffersError::ContextLost(Box::new(x)),
}
}
}

View File

@ -1,80 +0,0 @@
//!
//! Support to register a [`GbmDevice`](GbmDevice)
//! to an open [`Session`](::backend::session::Session).
//!
use drm::control::crtc;
use std::cell::RefCell;
use std::collections::HashMap;
use std::rc::{Rc, Weak};
use super::{GbmDevice, SurfaceInternalRef};
use crate::backend::drm::{Device, RawDevice};
use crate::backend::graphics::CursorBackend;
use crate::{
backend::session::Signal as SessionSignal,
signaling::{Linkable, Signaler},
};
/// [`SessionObserver`](SessionObserver)
/// linked to the [`GbmDevice`](GbmDevice) it was
/// created from.
pub(crate) struct GbmDeviceObserver<D: RawDevice + ::drm::control::Device + 'static> {
backends: Weak<RefCell<HashMap<crtc::Handle, SurfaceInternalRef<D>>>>,
_logger: ::slog::Logger,
}
impl<D> Linkable<SessionSignal> for GbmDevice<D>
where
D: RawDevice + drm::control::Device + Linkable<SessionSignal> + 'static,
<D as Device>::Surface: CursorBackend<CursorFormat = dyn drm::buffer::Buffer>,
{
fn link(&mut self, signaler: Signaler<SessionSignal>) {
let lower_signal = Signaler::new();
self.raw.link(lower_signal.clone());
let mut observer = GbmDeviceObserver::<D> {
backends: Rc::downgrade(&self.backends),
_logger: self.logger.clone(),
};
let token = signaler.register(move |&signal| match signal {
SessionSignal::ActivateSession | SessionSignal::ActivateDevice { .. } => {
// Activate lower *before* we process the event
lower_signal.signal(signal);
observer.activate()
}
_ => {
lower_signal.signal(signal);
}
});
self.links.push(token);
}
}
impl<D: RawDevice + drm::control::Device + 'static> GbmDeviceObserver<D>
where
<D as Device>::Surface: CursorBackend<CursorFormat = dyn drm::buffer::Buffer>,
{
fn activate(&mut self) {
let mut crtcs = Vec::new();
if let Some(backends) = self.backends.upgrade() {
for (crtc, backend) in backends.borrow().iter() {
if let Some(backend) = backend.upgrade() {
backend.clear_framebuffers();
// reset cursor
{
let cursor = backend.cursor.lock().unwrap();
let _ = backend.crtc.set_cursor_representation(&cursor.0, cursor.1);
}
} else {
crtcs.push(*crtc);
}
}
for crtc in crtcs {
backends.borrow_mut().remove(&crtc);
}
}
}
}

View File

@ -1,453 +0,0 @@
use super::super::{RawSurface, Surface};
use super::Error;
use drm::control::{connector, crtc, framebuffer, Mode};
use failure::ResultExt;
use gbm::{self, BufferObject, BufferObjectFlags, Format as GbmFormat};
use image::{ImageBuffer, Rgba};
use std::sync::{
atomic::{AtomicBool, Ordering},
Arc, Mutex,
};
use crate::backend::graphics::CursorBackend;
pub struct Buffers {
pub(super) current_frame_buffer: Option<framebuffer::Handle>,
pub(super) front_buffer: Option<BufferObject<framebuffer::Handle>>,
pub(super) next_buffer: Option<BufferObject<framebuffer::Handle>>,
}
pub(super) struct GbmSurfaceInternal<S: RawSurface + 'static> {
pub(super) dev: Arc<Mutex<gbm::Device<gbm::FdWrapper>>>,
pub(super) surface: Mutex<gbm::Surface<framebuffer::Handle>>,
pub(super) crtc: S,
pub(super) cursor: Mutex<(BufferObject<()>, (u32, u32))>,
pub(super) buffers: Mutex<Buffers>,
pub(super) recreated: AtomicBool,
pub(super) logger: ::slog::Logger,
}
impl<S: RawSurface + 'static> GbmSurfaceInternal<S> {
pub(super) fn new(
dev: Arc<Mutex<gbm::Device<gbm::FdWrapper>>>,
drm_surface: S,
logger: ::slog::Logger,
) -> Result<GbmSurfaceInternal<S>, Error<<S as Surface>::Error>> {
// initialize the surface
let (w, h) = drm_surface.pending_mode().size();
let surface = dev
.lock()
.unwrap()
.create_surface(
w as u32,
h as u32,
GbmFormat::XRGB8888,
BufferObjectFlags::SCANOUT | BufferObjectFlags::RENDERING,
)
.map_err(Error::SurfaceCreationFailed)?;
// initialize a buffer for the cursor image
let cursor = (
dev.lock()
.unwrap()
.create_buffer_object(
1,
1,
GbmFormat::ARGB8888,
BufferObjectFlags::CURSOR | BufferObjectFlags::WRITE,
)
.map_err(Error::BufferCreationFailed)?,
(0, 0),
);
Ok(GbmSurfaceInternal {
dev,
surface: Mutex::new(surface),
crtc: drm_surface,
cursor: Mutex::new(cursor),
buffers: Mutex::new(Buffers {
current_frame_buffer: None,
front_buffer: None,
next_buffer: None,
}),
recreated: AtomicBool::new(true),
logger,
})
}
pub(super) fn unlock_buffer(&self) {
// after the page swap is finished we need to release the rendered buffer.
// this is called from the PageFlipHandler
trace!(self.logger, "Releasing old front buffer");
let mut buffers = self.buffers.lock().unwrap();
buffers.front_buffer = buffers.next_buffer.take();
// drop and release the old buffer
}
pub unsafe fn page_flip(&self) -> Result<(), Error<<S as Surface>::Error>> {
let (result, fb) = {
let mut buffers = self.buffers.lock().unwrap();
if buffers.next_buffer.is_some() {
// 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(Error::FrontBuffersExhausted);
}
// supporting only one buffer would cause a lot of inconvinience and
// would most likely result in a lot of flickering.
// neither weston, wlc or wlroots bother with that as well.
// so we just assume we got at least two buffers to do flipping.
let mut next_bo = self
.surface
.lock()
.unwrap()
.lock_front_buffer()
.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(|_| Error::InvalidInternalState)?
.cloned();
let fb = if let Some(info) = maybe_fb {
info
} else {
let fb = self
.crtc
.add_planar_framebuffer(&next_bo, &[0; 4], 0)
.compat()
.map_err(Error::FramebufferCreationFailed)?;
next_bo.set_userdata(fb).unwrap();
fb
};
buffers.next_buffer = Some(next_bo);
if cfg!(debug_assertions) {
if let Err(err) = self.crtc.get_framebuffer(fb) {
error!(self.logger, "Cached framebuffer invalid: {:?}: {}", fb, err);
}
}
// if we re-created the surface, we need to commit the new changes, as we might trigger a modeset
(
if self.recreated.load(Ordering::SeqCst) {
debug!(self.logger, "Commiting new state");
self.crtc.commit(fb).map_err(Error::Underlying)
} else {
trace!(self.logger, "Queueing Page flip");
RawSurface::page_flip(&self.crtc, fb).map_err(Error::Underlying)
},
fb,
)
};
// if it was successful, we can clear the re-created state
match result {
Ok(_) => {
self.recreated.store(false, Ordering::SeqCst);
let mut buffers = self.buffers.lock().unwrap();
buffers.current_frame_buffer = Some(fb);
Ok(())
}
Err(err) => {
// if there was an error we need to free the buffer again,
// otherwise we may never lock again.
self.unlock_buffer();
Err(err)
}
}
}
// this function is called, if we e.g. need to create the surface to match a new mode.
pub fn recreate(&self) -> Result<(), Error<<S as Surface>::Error>> {
let (w, h) = self.pending_mode().size();
// Recreate the surface and the related resources to match the new
// resolution.
debug!(self.logger, "(Re-)Initializing surface (with mode: {}:{})", w, h);
let surface = self
.dev
.lock()
.unwrap()
.create_surface(
w as u32,
h as u32,
GbmFormat::XRGB8888,
BufferObjectFlags::SCANOUT | BufferObjectFlags::RENDERING,
)
.map_err(Error::SurfaceCreationFailed)?;
// Clean up buffers
self.clear_framebuffers();
// Drop the old surface after cleanup
*self.surface.lock().unwrap() = surface;
self.recreated.store(true, Ordering::SeqCst);
Ok(())
}
// if the underlying drm-device is closed and re-opened framebuffers may get invalided.
// here we clear them just to be sure, they get recreated on the next page_flip.
pub fn clear_framebuffers(&self) {
let mut buffers = self.buffers.lock().unwrap();
if let Some(Ok(Some(fb))) = buffers.next_buffer.take().map(|mut bo| bo.take_userdata()) {
if let Err(err) = self.crtc.destroy_framebuffer(fb) {
warn!(
self.logger,
"Error releasing old back_buffer framebuffer: {:?}", err
);
}
}
if let Some(Ok(Some(fb))) = buffers.front_buffer.take().map(|mut bo| bo.take_userdata()) {
if let Err(err) = self.crtc.destroy_framebuffer(fb) {
warn!(
self.logger,
"Error releasing old front_buffer framebuffer: {:?}", err
);
}
}
}
}
impl<S: RawSurface + 'static> Surface for GbmSurfaceInternal<S> {
type Connectors = <S as Surface>::Connectors;
type Error = Error<<S as Surface>::Error>;
fn crtc(&self) -> crtc::Handle {
self.crtc.crtc()
}
fn current_connectors(&self) -> Self::Connectors {
self.crtc.current_connectors()
}
fn pending_connectors(&self) -> Self::Connectors {
self.crtc.pending_connectors()
}
fn add_connector(&self, connector: connector::Handle) -> Result<(), Self::Error> {
self.crtc.add_connector(connector).map_err(Error::Underlying)
}
fn remove_connector(&self, connector: connector::Handle) -> Result<(), Self::Error> {
self.crtc.remove_connector(connector).map_err(Error::Underlying)
}
fn set_connectors(&self, connectors: &[connector::Handle]) -> Result<(), Self::Error> {
self.crtc.set_connectors(connectors).map_err(Error::Underlying)
}
fn current_mode(&self) -> Mode {
self.crtc.current_mode()
}
fn pending_mode(&self) -> Mode {
self.crtc.pending_mode()
}
fn use_mode(&self, mode: Mode) -> Result<(), Self::Error> {
self.crtc.use_mode(mode).map_err(Error::Underlying)
}
}
#[cfg(feature = "backend_drm")]
impl<S: RawSurface + 'static> CursorBackend for GbmSurfaceInternal<S>
where
S: CursorBackend<CursorFormat = dyn drm::buffer::Buffer>,
<S as CursorBackend>::Error: ::std::error::Error + Send,
{
type CursorFormat = ImageBuffer<Rgba<u8>, Vec<u8>>;
type Error = Error<<S as CursorBackend>::Error>;
fn set_cursor_position(&self, x: u32, y: u32) -> Result<(), Self::Error> {
self.crtc.set_cursor_position(x, y).map_err(Error::Underlying)
}
fn set_cursor_representation(
&self,
buffer: &ImageBuffer<Rgba<u8>, Vec<u8>>,
hotspot: (u32, u32),
) -> Result<(), Self::Error> {
let (w, h) = buffer.dimensions();
debug!(self.logger, "Importing cursor");
// import the cursor into a buffer we can render
let mut cursor = self
.dev
.lock()
.unwrap()
.create_buffer_object(
w,
h,
GbmFormat::ARGB8888,
BufferObjectFlags::CURSOR | BufferObjectFlags::WRITE,
)
.map_err(Error::BufferCreationFailed)?;
cursor
.write(&**buffer)
.map_err(|_| Error::DeviceDestroyed)?
.map_err(Error::BufferWriteFailed)?;
trace!(self.logger, "Setting the new imported cursor");
self.crtc
.set_cursor_representation(&cursor, hotspot)
.map_err(Error::Underlying)?;
// and store it
*self.cursor.lock().unwrap() = (cursor, hotspot);
Ok(())
}
fn clear_cursor_representation(&self) -> Result<(), Self::Error> {
*self.cursor.lock().unwrap() = (
self.dev
.lock()
.unwrap()
.create_buffer_object(
1,
1,
GbmFormat::ARGB8888,
BufferObjectFlags::CURSOR | BufferObjectFlags::WRITE,
)
.map_err(Error::BufferCreationFailed)?,
(0, 0),
);
self.crtc.clear_cursor_representation().map_err(Error::Underlying)
}
}
impl<S: RawSurface + 'static> Drop for GbmSurfaceInternal<S> {
fn drop(&mut self) {
// Drop framebuffers attached to the userdata of the gbm surface buffers.
// (They don't implement drop, as they need the device)
self.clear_framebuffers();
}
}
/// Gbm surface for rendering
pub struct GbmSurface<S: RawSurface + 'static>(pub(super) Arc<GbmSurfaceInternal<S>>);
impl<S: RawSurface + 'static> GbmSurface<S> {
/// Flips the underlying buffers.
///
/// The surface will report being already flipped until the matching event
/// was processed either by calling [`Device::process_events`] manually after the flip
/// (bad idea performance-wise) or by binding the device to an event-loop by using
/// [`device_bind`](::backend::drm::device_bind).
///
/// *Note*: This might trigger a full modeset on the underlying device,
/// potentially causing some flickering. In that case this operation is
/// blocking until the crtc is in the desired state.
///
/// # Safety
///
/// 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<(), <Self as Surface>::Error> {
self.0.page_flip()
}
/// Recreate underlying gbm resources.
///
/// This recreates the gbm surfaces resources, which might be needed after e.g.
/// calling [`Surface::use_mode`](Surface::use_mode).
/// You may check if your [`GbmSurface`] needs recreation through
/// [`needs_recreation`](GbmSurface::needs_recreation).
pub fn recreate(&self) -> Result<(), <Self as Surface>::Error> {
self.0.recreate()
}
/// Check if underlying gbm resources need to be recreated.
pub fn needs_recreation(&self) -> bool {
self.0.crtc.commit_pending()
}
}
impl<S: RawSurface + 'static> Surface for GbmSurface<S> {
type Connectors = <S as Surface>::Connectors;
type Error = Error<<S as Surface>::Error>;
fn crtc(&self) -> crtc::Handle {
self.0.crtc()
}
fn current_connectors(&self) -> Self::Connectors {
self.0.current_connectors()
}
fn pending_connectors(&self) -> Self::Connectors {
self.0.pending_connectors()
}
fn add_connector(&self, connector: connector::Handle) -> Result<(), Self::Error> {
self.0.add_connector(connector)
}
fn remove_connector(&self, connector: connector::Handle) -> Result<(), Self::Error> {
self.0.remove_connector(connector)
}
fn set_connectors(&self, connectors: &[connector::Handle]) -> Result<(), Self::Error> {
self.0.set_connectors(connectors)
}
fn current_mode(&self) -> Mode {
self.0.current_mode()
}
fn pending_mode(&self) -> Mode {
self.0.pending_mode()
}
fn use_mode(&self, mode: Mode) -> Result<(), Self::Error> {
self.0.use_mode(mode)
}
}
#[cfg(feature = "backend_drm")]
impl<S: RawSurface + 'static> CursorBackend for GbmSurface<S>
where
S: CursorBackend<CursorFormat = dyn drm::buffer::Buffer>,
<S as CursorBackend>::Error: ::std::error::Error + Send,
{
type CursorFormat = ImageBuffer<Rgba<u8>, Vec<u8>>;
type Error = Error<<S as CursorBackend>::Error>;
fn set_cursor_position(&self, x: u32, y: u32) -> Result<(), Self::Error> {
self.0.set_cursor_position(x, y)
}
fn set_cursor_representation(
&self,
buffer: &ImageBuffer<Rgba<u8>, Vec<u8>>,
hotspot: (u32, u32),
) -> Result<(), Self::Error> {
self.0.set_cursor_representation(buffer, hotspot)
}
fn clear_cursor_representation(&self) -> Result<(), Self::Error> {
self.0.clear_cursor_representation()
}
}
#[cfg(test)]
mod test {
use super::GbmSurface;
use crate::backend::drm::legacy::LegacyDrmSurface;
use std::fs::File;
fn is_send<S: Send>() {}
#[test]
fn surface_is_send() {
is_send::<GbmSurface<LegacyDrmSurface<File>>>();
}
}

View File

@ -1,412 +0,0 @@
//!
//! [`RawDevice`](RawDevice) and [`RawSurface`](RawSurface)
//! implementations using the legacy mode-setting infrastructure.
//!
//! Legacy mode-setting refers to the now outdated, but still supported direct manager api
//! of the linux kernel. Adaptations of this api can be found in BSD kernels.
//!
//! The newer and objectively better api is known as atomic-modesetting (or nuclear-page-flip),
//! however this api is not supported by every driver, so this is provided for backwards compatibility.
//! Currenly there are no features in smithay, that are exclusive to the atomic api.
//!
//! Usually this implementation will be wrapped into a [`GbmDevice`](::backend::drm::gbm::GbmDevice).
//! Take a look at `anvil`s source code for an example of this.
//!
//! For an example how to use this standalone, take a look at the `raw_legacy_drm` example.
//!
//! For detailed overview of these abstractions take a look at the module documentation of backend::drm.
//!
use super::{common::Error, DevPath, Device, DeviceHandler, RawDevice};
use drm::control::{
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;
use std::os::unix::io::{AsRawFd, RawFd};
use std::rc::Rc;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Weak};
use failure::{Fail, ResultExt};
mod surface;
pub use self::surface::LegacyDrmSurface;
use self::surface::LegacyDrmSurfaceInternal;
#[cfg(feature = "backend_session")]
pub mod session;
/// Open raw drm device utilizing legacy mode-setting
pub struct LegacyDrmDevice<A: AsRawFd + 'static> {
dev: Arc<Dev<A>>,
dev_id: dev_t,
active: Arc<AtomicBool>,
backends: Rc<RefCell<HashMap<crtc::Handle, Weak<LegacyDrmSurfaceInternal<A>>>>>,
handler: Option<RefCell<Box<dyn DeviceHandler<Device = LegacyDrmDevice<A>>>>>,
#[cfg(feature = "backend_session")]
links: Vec<crate::signaling::SignalToken>,
logger: ::slog::Logger,
}
pub(in crate::backend::drm) struct Dev<A: AsRawFd + 'static> {
fd: A,
privileged: bool,
active: Arc<AtomicBool>,
old_state: HashMap<crtc::Handle, (crtc::Info, Vec<connector::Handle>)>,
logger: ::slog::Logger,
}
impl<A: AsRawFd + 'static> AsRawFd for Dev<A> {
fn as_raw_fd(&self) -> RawFd {
self.fd.as_raw_fd()
}
}
impl<A: AsRawFd + 'static> BasicDevice for Dev<A> {}
impl<A: AsRawFd + 'static> ControlDevice for Dev<A> {}
impl<A: AsRawFd + 'static> Drop for Dev<A> {
fn drop(&mut self) {
info!(self.logger, "Dropping device: {:?}", self.dev_path());
if self.active.load(Ordering::SeqCst) {
// Here we restore the tty to it's previous state.
// In case e.g. getty was running on the tty sets the correct framebuffer again,
// so that getty will be visible.
// We do exit correctly, if this fails, but the user will be presented with
// a black screen, if no display handler takes control again.
let old_state = self.old_state.clone();
for (handle, (info, connectors)) in old_state {
if let Err(err) = self.set_crtc(
handle,
info.framebuffer(),
info.position(),
&connectors,
info.mode(),
) {
error!(self.logger, "Failed to reset crtc ({:?}). Error: {}", handle, err);
}
}
}
if self.privileged {
if let Err(err) = self.release_master_lock() {
error!(self.logger, "Failed to drop drm master state. Error: {}", err);
}
}
}
}
impl<A: AsRawFd + 'static> LegacyDrmDevice<A> {
/// Create a new [`LegacyDrmDevice`] from an open drm node.
///
/// # 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>>,
{
let log = crate::slog_or_fallback(logger).new(o!("smithay_module" => "backend_drm"));
info!(log, "LegacyDrmDevice initializing");
let dev_id = fstat(dev.as_raw_fd())
.map_err(Error::UnableToGetDeviceId)?
.st_rdev;
// we wrap some of the internal state in another struct to share with
// the surfaces and event loop handlers.
let active = Arc::new(AtomicBool::new(true));
let mut dev = Dev {
fd: dev,
privileged: true,
old_state: HashMap::new(),
active: active.clone(),
logger: log.clone(),
};
// We want to modeset, so we better be the master, if we run via a tty session.
// This is only needed on older kernels. Newer kernels grant this permission,
// if no other process is already the *master*. So we skip over this error.
if dev.acquire_master_lock().is_err() {
warn!(log, "Unable to become drm master, assuming unprivileged mode");
dev.privileged = false;
};
// Enumerate (and save) the current device state.
// We need to keep the previous device configuration to restore the state later,
// so we query everything, that we can set.
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(crtc) = enc_info.crtc() {
let info = dev.get_crtc(crtc).compat().map_err(|source| Error::Access {
errmsg: "Error loading crtc info",
dev: dev.dev_path(),
source,
})?;
dev.old_state
.entry(crtc)
.or_insert((info, Vec::new()))
.1
.push(con);
}
}
}
// If the user does not explicitly requests us to skip this,
// we clear out the complete connector<->crtc mapping on device creation.
//
// The reason is, that certain operations may be racy otherwise, as surfaces can
// exist on different threads. As a result, we cannot enumerate the current state
// on surface creation (it might be changed on another thread during the enumeration).
// An easy workaround is to set a known state on device creation.
if disable_connectors {
dev.set_connector_state(res_handles.connectors().iter().copied(), false)?;
for crtc in res_handles.crtcs() {
// null commit (necessary to trigger removal on the kernel side with the legacy api.)
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: Arc::new(dev),
dev_id,
active,
backends: Rc::new(RefCell::new(HashMap::new())),
handler: None,
#[cfg(feature = "backend_session")]
links: Vec::new(),
logger: log.clone(),
})
}
}
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 every connector...
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,
})?;
// that is currently connected ...
if info.state() == connector::State::Connected {
// get a list of it's properties.
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 every handle ...
for handle in handles {
// get information of that property
let info = self
.get_property(*handle)
.compat()
.map_err(|source| Error::Access {
errmsg: "Failed to get property of connector",
dev: self.dev_path(),
source,
})?;
// to find out, if we got the handle of the "DPMS" property ...
if info.name().to_str().map(|x| x == "DPMS").unwrap_or(false) {
// so we can use that to turn on / off the connector
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()
}
}
impl<A: AsRawFd + 'static> BasicDevice for LegacyDrmDevice<A> {}
impl<A: AsRawFd + 'static> ControlDevice for LegacyDrmDevice<A> {}
impl<A: AsRawFd + 'static> Device for LegacyDrmDevice<A> {
type Surface = LegacyDrmSurface<A>;
fn device_id(&self) -> dev_t {
self.dev_id
}
fn set_handler(&mut self, handler: impl DeviceHandler<Device = Self> + 'static) {
self.handler = Some(RefCell::new(Box::new(handler)));
}
fn clear_handler(&mut self) {
let _ = self.handler.take();
}
fn create_surface(
&mut self,
crtc: crtc::Handle,
mode: Mode,
connectors: &[connector::Handle],
) -> Result<LegacyDrmSurface<A>, Error> {
if self.backends.borrow().contains_key(&crtc) {
return Err(Error::CrtcAlreadyInUse(crtc));
}
if !self.active.load(Ordering::SeqCst) {
return Err(Error::DeviceInactive);
}
if connectors.is_empty() {
return Err(Error::SurfaceWithoutConnectors(crtc));
}
let backend = Arc::new(LegacyDrmSurfaceInternal::new(
self.dev.clone(),
crtc,
mode,
connectors,
self.logger.new(o!("crtc" => format!("{:?}", crtc))),
)?);
self.backends.borrow_mut().insert(crtc, Arc::downgrade(&backend));
Ok(LegacyDrmSurface(backend))
}
fn process_events(&mut self) {
match self.receive_events() {
Ok(events) => {
for event in events {
if let Event::PageFlip(event) = event {
if self
.backends
.borrow()
.get(&event.crtc)
.iter()
.flat_map(|x| x.upgrade())
.next()
.is_some()
{
trace!(self.logger, "Handling event for backend {:?}", event.crtc);
if let Some(handler) = self.handler.as_ref() {
handler.borrow_mut().vblank(event.crtc);
}
} else {
self.backends.borrow_mut().remove(&event.crtc);
}
} else {
trace!(self.logger, "Unrelated event");
}
}
}
Err(source) => {
if let Some(handler) = self.handler.as_ref() {
handler.borrow_mut().error(Error::Access {
errmsg: "Error processing drm events",
dev: self.dev_path(),
source: source.compat(),
});
}
}
}
}
fn resource_handles(&self) -> Result<ResourceHandles, Error> {
ControlDevice::resource_handles(self)
.compat()
.map_err(|source| Error::Access {
errmsg: "Error loading resource info",
dev: self.dev_path(),
source,
})
}
fn get_connector_info(&self, conn: connector::Handle) -> Result<connector::Info, DrmError> {
self.get_connector(conn)
}
fn get_crtc_info(&self, crtc: crtc::Handle) -> Result<crtc::Info, DrmError> {
self.get_crtc(crtc)
}
fn get_encoder_info(&self, enc: encoder::Handle) -> Result<encoder::Info, DrmError> {
self.get_encoder(enc)
}
fn get_framebuffer_info(&self, fb: framebuffer::Handle) -> Result<framebuffer::Info, DrmError> {
self.get_framebuffer(fb)
}
fn get_plane_info(&self, plane: plane::Handle) -> Result<plane::Info, DrmError> {
self.get_plane(plane)
}
}
impl<A: AsRawFd + 'static> RawDevice for LegacyDrmDevice<A> {
type Surface = LegacyDrmSurface<A>;
}
impl<A: AsRawFd + 'static> Drop for LegacyDrmDevice<A> {
fn drop(&mut self) {
self.clear_handler();
}
}

View File

@ -1,198 +0,0 @@
//!
//! Support to register an open [`LegacyDrmDevice`](LegacyDrmDevice)
//! to an open [`Session`](::backend::session::Session).
//!
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, HashSet};
use std::os::unix::io::{AsRawFd, RawFd};
use std::rc::{Rc, Weak};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Weak as WeakArc};
use super::{Dev, DevPath, Error, LegacyDrmDevice, LegacyDrmSurfaceInternal};
use crate::{
backend::session::Signal as SessionSignal,
signaling::{Linkable, Signaler},
};
/// [`SessionObserver`](SessionObserver)
/// linked to the [`LegacyDrmDevice`](LegacyDrmDevice)
/// it was created from.
pub(crate) struct LegacyDrmDeviceObserver<A: AsRawFd + 'static> {
dev: WeakArc<Dev<A>>,
dev_id: dev_t,
privileged: bool,
active: Arc<AtomicBool>,
backends: Weak<RefCell<HashMap<crtc::Handle, WeakArc<LegacyDrmSurfaceInternal<A>>>>>,
logger: ::slog::Logger,
}
impl<A: AsRawFd + 'static> Linkable<SessionSignal> for LegacyDrmDevice<A> {
fn link(&mut self, signaler: Signaler<SessionSignal>) {
let mut observer = LegacyDrmDeviceObserver {
dev: Arc::downgrade(&self.dev),
dev_id: self.dev_id,
active: self.active.clone(),
privileged: self.dev.privileged,
backends: Rc::downgrade(&self.backends),
logger: self.logger.clone(),
};
let token = signaler.register(move |signal| observer.signal(*signal));
self.links.push(token);
}
}
impl<A: AsRawFd + 'static> LegacyDrmDeviceObserver<A> {
fn signal(&mut self, signal: SessionSignal) {
match signal {
SessionSignal::PauseSession => self.pause(None),
SessionSignal::PauseDevice { major, minor } => self.pause(Some((major, minor))),
SessionSignal::ActivateSession => self.activate(None),
SessionSignal::ActivateDevice { major, minor, new_fd } => {
self.activate(Some((major, minor, new_fd)))
}
}
}
fn pause(&mut self, devnum: Option<(u32, u32)>) {
if let Some((major, minor)) = devnum {
if major as u64 != stat::major(self.dev_id) || minor as u64 != stat::minor(self.dev_id) {
return;
}
}
if let Some(device) = self.dev.upgrade() {
if let Some(backends) = self.backends.upgrade() {
for surface in backends.borrow().values().filter_map(WeakArc::upgrade) {
// other ttys that use no cursor, might not clear it themselves.
// This makes sure our cursor won't stay visible.
//
// This usually happens with getty, and a cursor on top of a kernel console looks very weird.
let _ = (*device).set_cursor(
surface.crtc,
Option::<&drm::control::dumbbuffer::DumbBuffer>::None,
);
}
}
}
self.active.store(false, Ordering::SeqCst);
if self.privileged {
if let Some(device) = self.dev.upgrade() {
if let Err(err) = device.release_master_lock() {
error!(self.logger, "Failed to drop drm master state. Error: {}", err);
}
}
}
}
fn activate(&mut self, devnum: Option<(u32, u32, Option<RawFd>)>) {
if let Some((major, minor, fd)) = devnum {
if major as u64 != stat::major(self.dev_id) || minor as u64 != stat::minor(self.dev_id) {
return;
} else if let Some(fd) = fd {
info!(self.logger, "Replacing fd");
if let Some(device) = self.dev.upgrade() {
::nix::unistd::dup2(device.as_raw_fd(), fd)
.expect("Failed to replace file descriptor of drm device");
}
}
}
self.active.store(true, Ordering::SeqCst);
if self.privileged {
if let Some(device) = self.dev.upgrade() {
if let Err(err) = device.acquire_master_lock() {
crit!(self.logger, "Failed to acquire drm master again. Error: {}", err);
}
}
}
// 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> {
// reset state enumerates the actual current state of the drm device
// and applies that to the `current_state` as saved in the surfaces.
//
// This re-sync is necessary after a tty swap, as the pipeline might
// be left in a different state.
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(WeakArc::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

@ -1,391 +1,9 @@
//! pub(crate) mod device;
//! This module provides Traits reprensentating open devices pub(self) mod surface;
//! and their surfaces to render contents. pub(self) mod error;
//! pub(self) mod session;
//! # Abstractions
//!
//! This is a model of what the user typically perceives as a `Device` and a `Surface`.
//! The meaning of these words is very overloaded in the general computer graphics context,
//! so let me give you a quick run down, what these mean in the context of smithay's public api:
//!
//! ## Device
//!
//! A device is some sort of rendering device. It exposes certain properties, which are directly derived
//! from the *device* as perceived by the direct rendering manager api (drm). These resources consists
//! out of connectors, encoders, framebuffers, planes and crtcs.
//!
//! [`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 independent output devices the hardware may handle.
//!
//! On modern graphic cards it is better to think about the `crtc` as some sort of rendering engine.
//! You can only have so many different pictures, you may display, as you have `crtc`s, but a single image
//! may be put onto multiple displays.
//!
//! An [`encoder`](drm::control::encoder) encodes the data of connected crtcs into a video signal for a fixed set of connectors.
//! E.g. you might have an analog encoder based on a DAG for VGA ports, but another one for digital ones.
//! Also not every encoder might be connected to every crtc.
//!
//! A [`connector`](drm::control::connector) represents a port on your computer, possibly with a connected monitor, TV, capture card, etc.
//!
//! A [`framebuffer`](drm::control::framebuffer) represents a buffer you may be rendering to, see `Surface` below.
//!
//! Planes are another layer on top of the crtcs, which allow us to layer multiple images on top of each other more efficiently
//! then by combining the rendered images in the rendering phase, e.g. via OpenGL. They are internally used by smithay to provide
//! hardware-accelerated cursors. More features regarding planes may be added in the future, but they are mostly optional and
//! mainly provide possibilies for optimizations.
//!
//! The main functionality of a `Device` in smithay is to give access to all these properties for the user to
//! choose an appropriate rendering configuration. What that means is defined by the requirements and constraints documented
//! in the specific device implementations. The second functionality is the creation of a `Surface`.
//! Surface creation requires a `crtc` (which cannot be the same as another existing `Surface`'s crtc),
//! as well as a `Mode` and a set of `connectors`.
//!
//! smithay does not make sure that `connectors` are not already in use by another `Surface`. Overlapping `connector`-Sets may
//! be an error or result in undefined rendering behavior depending on the `Surface` implementation.
//!
//! ## Surface
//!
//! A surface is a part of a `Device` that may output a picture to a number of connectors. It pumps pictures of buffers to outputs.
//!
//! On surface creation a matching encoder for your `encoder`-`connector` is automatically selected,
//! if it exists, which means you still need to check your configuration.
//!
//! A surface consists of one `crtc` that is rendered to by the user. This is fixed for the `Surface`s lifetime and cannot be changed.
//! A surface also always needs at least one connector to output the resulting image to as well as a `Mode` that is valid for the given connector.
//!
//! The state of a `Surface` is double-buffered, meaning all operations that chance the set of `connector`s or their `Mode` are stored and
//! only applied on the next commit. `Surface`s do their best to validate these changes, if possible.
//!
//! A commit/page_flip may be triggered by any other method the `Surface` has, but needs to be documented as such.
//! The most low-level `Surface`s also implement `RawSurface`, which let you trigger a commit explicitly.
//!
//! ## RawDevice
//!
//! A low-level device that produces not only `Surface`s, but `RawSurface`s.
//!
//! ## RawSurface
//!
//! RawSurfaces provide two additional functions related to rendering: `commit` and `page_flip`.
//! Both attach the contents of a framebuffer, resulting in it's contents being displayed on the set connectors.
//!
//! `commit` also applies any pending changes to the current `Mode` and `connector`-Sets.
//! You can easily check, if there are pending changes and avoid the costly commit by using `commit_pending`,
//! which compares the current and pending state internally.
//!
//! # Rendering
//!
//! To perform actual rendering operations you need to render your image to a framebuffer.
//! Several types of framebuffers exists.
//!
//! The simplest one - the `Dumbbuffer` is a simple bitmap, usually very slow and not hardware-accelerated.
//! It should be avoided at any cost, but works on all devices and can be used directly with a `RawSurface`.
//! Rendering to it can be done manually by coping bitmaps around (cpu rendering).
//!
//! Accelerated buffers are usually hardware manufacturer dependent. A problem, which is mostly solved by
//! the General Buffer Manager (gbm-)API. `gbm::Buffer`s can only be rendered to by egl. Appropriate hardware-accelerated
//! buffers are picked automatically. gbm even may fall back to `Dumbbuffers` on unsupported devices and
//! egl-support is then provided by Mesa's software renderer `llvmpipe`.
//!
//! An alternative api to `gbm` is the so called `eglstream`-api, proposed (and implemented) by nvidia.
//! It is currenly only supported by nvidias proprietary driver, which is also not accelerated by gbm, although other
//! manufacturers could in theory implement the standard, just like nvidia could implement gbm-support in their driver.
//! Ongoing discussions about these api's may result in another (but hopefully unified) api in the future.
//!
//! # Implementations
//!
//! Smithay provided these different functionalities as wrappers around many `Device` and `Surface` implementations.
//!
//! At the low-level we have:
//!
//! - [`AtomicDrmDevice`](atomic::AtomicDrmDevice) (and [`AtomicDrmSurface`](atomic::AtomicDrmSurface)), which implement the traits directly using the modern atomic drm-api.
//! - [`LegacyDrmDevice`](legacy::LegacyDrmDevice) (and [`LegacyDrmSurface`](legacy::LegacyDrmSurface)), which implement the traits directly using the outdated drm-api.
//!
//! Both of these also implement `RawDevice` and `RawSurface`.
//!
//! On top of that the following wrappers add buffer allocation apis:
//!
//! - [`GbmDevice`](gbm::GbmDevice) (and [`GbmSurface`](gbm::Surface)), which replace the direct rendering capabilities of any underlying `RawSurface` with an egl-specific `page_flip` function.
//! - [`EglStreamDevice`](eglstream::EglStreamDevice) (and [`EglStreamSurface`](eglstream::EglStreamSurface)), which replace the direct rendering capabilities of any underlying `RawSurface` with functionality to create EGLStreams.
//!
//! On top of that the [`EglDevice`](egl::EglDevice) (and [`EglSurface`](egl::EglSurface)) replace the manual buffer management
//! with an implementation of smithays [`GLGraphicsBackend`](::backend::graphics::gl::GLGraphicsBackend) to initialize the OpenGL-API for rendering.
//!
//! Additionally the [`FallbackDevice`](fallback::FallbackDevice) provides an easy wrapper to automatically pick an appropriate device.
//! E.g. it can initialize an `AtomicDrmDevice` or fallback to a `LegacyDrmDevice` with the atomic api is not supported by the graphics card.
//! It can also check for a loaded nvidia-driver for a given device and choose between an `EglStreamDevice` or a `GbmDevice`.
//!
//! Any layer of this chain can be implemented and therefore extended by the user. All building blocks are exposed to add addtional
//! buffer management solutions or rendering apis out of tree. Additionally all of these implementations can be excluded from a build
//! by disabling their corresponding feature.
//!
//! The most versatile type currently provided by smithay, that should be able to initialize almost every
//! common graphics card is
//! ```rust,ignore
//! type RenderDevice<A: AsRawFd> = FallbackDevice<
//! EglDevice<
//! EglGbmBackend<FallbackDevice<AtomicDrmDevice<A>, LegacyDrmDevice<A>>>,
//! GbmDevice<FallbackDevice<AtomicDrmDevice<A>, LegacyDrmDevice<A>>>,
//! >,
//! EglDevice<
//! EglStreamDeviceBackend<FallbackDevice<AtomicDrmDevice<A>, LegacyDrmDevice<A>>>,
//! EglStreamDevice<FallbackDevice<AtomicDrmDevice<A>, LegacyDrmDevice<A>>>,
//! >
//! >;
//! ```
//!
//! # Event handling
//!
//! Devices can be attached to an eventloop using `device_bind`.
//! Events triggered by a drm-device correspond to finished page-flips.
//! After submitting a page_flip (possibly through a high-level api, such as a `EglSurface`)
//! the GPU may take some time until the new framebuffer is displayed. Rendering the next
//! frame should be delayed until the flip is done.
//!
//! Naive implementations should thus be driven by the drm device events to synchronize rendering
//! with the device. More advanced implementations may take into account that, if no change has occurred,
//! a page_flip may waste resources and therefore delay rendering until the buffer change from a client
//! is received or input is registered, etc. Because a flip may fail (which will result in no event),
//! a robust rescheduling implementation for missed frames or intentionally skipped flips should be
//! in place.
//!
use drm::{ pub use device::{DrmDevice, DrmSource, device_bind};
control::{connector, crtc, encoder, framebuffer, plane, Device as ControlDevice, Mode, ResourceHandles}, pub use surface::DrmSurface;
Device as BasicDevice, SystemError as DrmError, pub use error::Error as DrmError;
}; pub use session::DrmDeviceObserver;
use nix::libc::dev_t;
use std::error::Error;
use std::iter::IntoIterator;
use std::os::unix::io::AsRawFd;
use std::path::PathBuf;
use calloop::{generic::Generic, InsertError, LoopHandle, Source};
#[cfg(feature = "backend_drm_atomic")]
pub mod atomic;
#[cfg(feature = "backend_drm")]
pub mod common;
#[cfg(feature = "backend_drm_egl")]
pub mod egl;
#[cfg(feature = "backend_drm_eglstream")]
pub mod eglstream;
#[cfg(feature = "backend_drm_gbm")]
pub mod gbm;
#[cfg(feature = "backend_drm_legacy")]
pub mod legacy;
/// Trait to receive events of a bound [`Device`]
///
/// See [`device_bind`]
pub trait DeviceHandler {
/// The [`Device`] type this handler can handle
type Device: Device + ?Sized;
/// A vblank blank event on the provided crtc has happend
fn vblank(&mut self, crtc: crtc::Handle);
/// An error happend while processing events
fn error(&mut self, error: <<<Self as DeviceHandler>::Device as Device>::Surface as Surface>::Error);
}
/// An open drm device
pub trait Device: AsRawFd + DevPath {
/// Associated [`Surface`] of this [`Device`] type
type Surface: Surface;
/// Returns the id of this device node.
fn device_id(&self) -> dev_t;
/// Assigns a [`DeviceHandler`] called during event processing.
///
/// See [`device_bind`] and [`DeviceHandler`]
fn set_handler(&mut self, handler: impl DeviceHandler<Device = Self> + 'static);
/// Clear a set [`DeviceHandler`](trait.DeviceHandler.html), if any
fn clear_handler(&mut self);
/// 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 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.
///
/// You should not call this function manually, but rather use
/// [`device_bind`] to register the device
/// to an [`EventLoop`](calloop::EventLoop)
/// to synchronize your rendering to the vblank events of the open crtc's
fn process_events(&mut self);
/// Attempts to acquire a copy of the [`Device`]'s
/// [`ResourceHandle`](drm::control::ResourceHandle)
fn resource_handles(&self) -> Result<ResourceHandles, <Self::Surface as Surface>::Error>;
/// Retrieve the information for a connector
fn get_connector_info(&self, conn: connector::Handle) -> Result<connector::Info, DrmError>;
/// Retrieve the information for a crtc
fn get_crtc_info(&self, crtc: crtc::Handle) -> Result<crtc::Info, DrmError>;
/// Retrieve the information for an encoder
fn get_encoder_info(&self, enc: encoder::Handle) -> Result<encoder::Info, DrmError>;
/// Retrieve the information for a framebuffer
fn get_framebuffer_info(&self, fb: framebuffer::Handle) -> Result<framebuffer::Info, DrmError>;
/// Retrieve the information for a plane
fn get_plane_info(&self, plane: plane::Handle) -> Result<plane::Info, DrmError>;
}
/// Marker trait for [`Device`]s able to provide [`RawSurface`]s
pub trait RawDevice: Device<Surface = <Self as RawDevice>::Surface> {
/// Associated [`RawSurface`] of this [`RawDevice`] type
type Surface: RawSurface;
}
/// An open crtc that can be used for rendering
pub trait Surface {
/// Type repesenting a collection of
/// [`connector`](drm::control::connector)s
/// returned by [`current_connectors`](Surface::current_connectors) and
/// [`pending_connectors`](Surface::pending_connectors)
type Connectors: IntoIterator<Item = connector::Handle>;
/// Error type returned by methods of this trait
type Error: Error + Send + 'static;
/// Returns the underlying [`crtc`](drm::control::crtc) of this surface
fn crtc(&self) -> crtc::Handle;
/// Currently used [`connector`](drm::control::connector)s of this `Surface`
fn current_connectors(&self) -> Self::Connectors;
/// Returns the pending [`connector`](drm::control::connector)s
/// used after the next [`commit`](RawSurface::commit) of this [`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 pending_connectors(&self) -> Self::Connectors;
/// 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
/// [`Mode`](drm::control::Mode).
fn add_connector(&self, connector: connector::Handle) -> Result<(), Self::Error>;
/// Tries to mark a [`connector`](drm::control::connector)
/// for removal on the next commit.
fn remove_connector(&self, connector: connector::Handle) -> Result<(), Self::Error>;
/// Tries to replace the current connector set with the newly provided one on the next commit.
///
/// Fails if one new `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
/// [`Mode`](drm::control::Mode).
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)
fn current_mode(&self) -> Mode;
/// Returns the currently pending [`Mode`](drm::control::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.
///
/// Fails if the mode is not compatible with the underlying
/// [`crtc`](drm::control::crtc) or any of the
/// pending [`connector`](drm::control::connector)s.
///
/// *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: Mode) -> Result<(), Self::Error>;
}
/// An open bare crtc without any rendering abstractions
pub trait RawSurface: Surface + ControlDevice + BasicDevice {
/// Returns true whenever any state changes are pending to be commited
///
/// The following functions may trigger a pending commit:
/// - [`add_connector`](Surface::add_connector)
/// - [`remove_connector`](Surface::remove_connector)
/// - [`use_mode`](Surface::use_mode)
fn commit_pending(&self) -> bool;
/// Commit the pending state rendering a given framebuffer.
///
/// *Note*: This will trigger a full modeset on the underlying device,
/// potentially causing some flickering. Check before performing this
/// operation if a commit really is necessary using [`commit_pending`](RawSurface::commit_pending).
///
/// This operation is not necessarily blocking until the crtc is in the desired state,
/// but will trigger a `vblank` event once 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 commit(&self, framebuffer: framebuffer::Handle) -> Result<(), <Self as Surface>::Error>;
/// Page-flip the underlying [`crtc`](drm::control::crtc)
/// to a new given [`framebuffer`].
///
/// This will not cause the crtc to modeset.
///
/// 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<(), <Self as Surface>::Error>;
}
/// Trait representing open devices that *may* return a `Path`
pub trait DevPath {
/// Returns the path of the open device if possible
fn dev_path(&self) -> Option<PathBuf>;
}
impl<A: AsRawFd> DevPath for A {
fn dev_path(&self) -> Option<PathBuf> {
use std::fs;
fs::read_link(format!("/proc/self/fd/{:?}", self.as_raw_fd())).ok()
}
}
/// calloop source associated with a Device
pub type DrmSource<D> = Generic<D>;
/// Bind a `Device` to an [`EventLoop`](calloop::EventLoop),
///
/// This will cause it to recieve events and feed them into a previously
/// set [`DeviceHandler`](DeviceHandler).
pub fn device_bind<D: Device + 'static, Data>(
handle: &LoopHandle<Data>,
device: D,
) -> ::std::result::Result<Source<DrmSource<D>>, InsertError<DrmSource<D>>>
where
D: Device,
Data: 'static,
{
let source = Generic::new(device, calloop::Interest::Readable, calloop::Mode::Level);
handle.insert_source(source, |_, source, _| {
source.process_events();
Ok(())
})
}

27
src/backend/drm/render.rs Normal file
View File

@ -0,0 +1,27 @@
pub struct DrmRenderSurface<
D: AsRawFd + 'static,
A: Allocator<S>,
S: Buffer,
D: Buffer + TryFrom<S>,
E: Error,
T, F,
R: Renderer<Error=Error, Texture=T, Frame=F, Buffer=D>,
> {
drm: DrmSurface<A>,
allocator: A,
renderer: R,
swapchain: Swapchain,
}
impl<D, A, S, D, E, T, R> DrmRenderSurface<D, A, S, D, E, T, R>
where
D: AsRawFd + 'static,
A: Allocator<S>,
S: Buffer,
D: Buffer + TryFrom<S>,
E: Error,
T, F,
R: Renderer<Error=Error, Texture=T, Frame=F, Buffer=D>,
{
}

113
src/backend/drm/session.rs Normal file
View File

@ -0,0 +1,113 @@
use std::sync::{Arc, Weak, atomic::{AtomicBool, Ordering}};
use std::os::unix::io::{AsRawFd, RawFd};
use drm::Device as BasicDevice;
use nix::libc::dev_t;
use nix::sys::stat;
use super::device::{DrmDevice, DrmDeviceInternal};
use super::error::Error;
use crate::{
backend::session::Signal as SessionSignal,
signaling::{Linkable, Signaler},
};
/// [`SessionObserver`](SessionObserver)
/// linked to the [`DrmDevice`](DrmDevice)
/// it was created from.
pub struct DrmDeviceObserver<A: AsRawFd + 'static> {
dev_id: dev_t,
dev: Weak<DrmDeviceInternal<A>>,
privileged: bool,
active: Arc<AtomicBool>,
logger: ::slog::Logger,
}
impl<A: AsRawFd + 'static> Linkable<SessionSignal> for DrmDevice<A> {
fn link(&mut self, signaler: Signaler<SessionSignal>) {
let mut observer = DrmDeviceObserver {
dev: Arc::downgrade(&self.internal),
dev_id: self.dev_id,
active: match &*self.internal {
DrmDeviceInternal::Atomic(dev) => dev.active.clone(),
DrmDeviceInternal::Legacy(dev) => dev.active.clone(),
},
privileged: match &*self.internal {
DrmDeviceInternal::Atomic(dev) => dev.fd.privileged,
DrmDeviceInternal::Legacy(dev) => dev.fd.privileged,
},
logger: self.logger.new(o!("drm_module" => "observer")),
};
let token = signaler.register(move |signal| observer.signal(*signal));
self.links.borrow_mut().push(token);
}
}
impl<A: AsRawFd + 'static> DrmDeviceObserver<A> {
fn signal(&mut self, signal: SessionSignal) {
match signal {
SessionSignal::PauseSession => self.pause(None),
SessionSignal::PauseDevice { major, minor } => self.pause(Some((major, minor))),
SessionSignal::ActivateSession => self.activate(None),
SessionSignal::ActivateDevice { major, minor, new_fd } => {
self.activate(Some((major, minor, new_fd)))
}
}
}
fn pause(&mut self, devnum: Option<(u32, u32)>) {
if let Some((major, minor)) = devnum {
if major as u64 != stat::major(self.dev_id) || minor as u64 != stat::minor(self.dev_id) {
return;
}
}
self.active.store(false, Ordering::SeqCst);
if self.privileged {
if let Some(device) = self.dev.upgrade() {
if let Err(err) = device.release_master_lock() {
error!(self.logger, "Failed to drop drm master state Error: {}", err);
}
}
}
}
fn activate(&mut self, devnum: Option<(u32, u32, Option<RawFd>)>) {
if let Some((major, minor, fd)) = devnum {
if major as u64 != stat::major(self.dev_id) || minor as u64 != stat::minor(self.dev_id) {
return;
} else if let Some(fd) = fd {
info!(self.logger, "Replacing fd");
if let Some(device) = self.dev.upgrade() {
::nix::unistd::dup2(device.as_raw_fd(), fd)
.expect("Failed to replace file descriptor of drm device");
}
}
}
if self.privileged {
if let Some(device) = self.dev.upgrade() {
if let Err(err) = device.acquire_master_lock() {
crit!(self.logger, "Failed to acquire drm master again. Error: {}", err);
}
}
}
// 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
}
self.active.store(true, Ordering::SeqCst);
}
fn reset_state(&mut self) -> Result<(), Error> {
if let Some(dev) = self.dev.upgrade() {
dev.reset_state()
} else {
Ok(())
}
}
}

View File

@ -1,20 +1,17 @@
use drm::buffer::Buffer;
use drm::control::{ use drm::control::{
connector, crtc, dumbbuffer::DumbBuffer, encoder, framebuffer, Device as ControlDevice, Mode, connector, crtc, encoder, framebuffer, Device as ControlDevice, Mode,
PageFlipFlags, PageFlipFlags,
}; };
use drm::Device as BasicDevice;
use std::collections::HashSet; use std::collections::HashSet;
use std::os::unix::io::{AsRawFd, RawFd}; use std::os::unix::io::AsRawFd;
use std::sync::{atomic::Ordering, Arc, RwLock}; use std::sync::{atomic::{AtomicBool, Ordering}, Arc, RwLock};
use crate::backend::drm::{common::Error, DevPath, RawSurface, Surface}; use crate::backend::drm::{
use crate::backend::graphics::CursorBackend; device::{DevPath, DrmDeviceInternal},
device::legacy::set_connector_state,
use super::Dev; error::Error,
};
use failure::{Fail, ResultExt};
#[derive(Debug, PartialEq, Eq, Clone)] #[derive(Debug, PartialEq, Eq, Clone)]
pub struct State { pub struct State {
@ -22,313 +19,25 @@ pub struct State {
pub connectors: HashSet<connector::Handle>, pub connectors: HashSet<connector::Handle>,
} }
pub(in crate::backend::drm) struct LegacyDrmSurfaceInternal<A: AsRawFd + 'static> { pub struct LegacyDrmSurface<A: AsRawFd + 'static> {
pub(super) dev: Arc<Dev<A>>, pub(super) fd: Arc<DrmDeviceInternal<A>>,
pub(in crate::backend::drm) crtc: crtc::Handle, active: Arc<AtomicBool>,
pub(super) state: RwLock<State>, crtc: crtc::Handle,
pub(super) pending: RwLock<State>, state: RwLock<State>,
pub(super) logger: ::slog::Logger, pending: RwLock<State>,
logger: ::slog::Logger,
} }
impl<A: AsRawFd + 'static> AsRawFd for LegacyDrmSurfaceInternal<A> { impl<A: AsRawFd + 'static> LegacyDrmSurface<A> {
fn as_raw_fd(&self) -> RawFd { pub fn new(
self.dev.as_raw_fd() fd: Arc<DrmDeviceInternal<A>>,
} active: Arc<AtomicBool>,
}
impl<A: AsRawFd + 'static> BasicDevice for LegacyDrmSurfaceInternal<A> {}
impl<A: AsRawFd + 'static> ControlDevice for LegacyDrmSurfaceInternal<A> {}
impl<A: AsRawFd + 'static> CursorBackend for LegacyDrmSurfaceInternal<A> {
type CursorFormat = dyn Buffer;
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()
.map_err(|source| Error::Access {
errmsg: "Error moving cursor",
dev: self.dev_path(),
source,
})
}
fn set_cursor_representation(
&self,
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");
// set_cursor2 allows us to set the hotspot, but is not supported by every implementation.
if self
.set_cursor2(self.crtc, Some(buffer), (hotspot.0 as i32, hotspot.1 as i32))
.is_err()
{
// the cursor will be slightly misplaced, when using the function for hotspots other then (0, 0),
// but that is still better then no cursor.
self.set_cursor(self.crtc, Some(buffer))
.compat()
.map_err(|source| Error::Access {
errmsg: "Failed to set cursor",
dev: self.dev_path(),
source,
})?;
}
Ok(())
}
fn clear_cursor_representation(&self) -> Result<(), Error> {
self.set_cursor(self.crtc, Option::<&DumbBuffer>::None)
.compat()
.map_err(|source| Error::Access {
errmsg: "Failed to set cursor",
dev: self.dev_path(),
source,
})
}
}
impl<A: AsRawFd + 'static> Surface for LegacyDrmSurfaceInternal<A> {
type Error = Error;
type Connectors = HashSet<connector::Handle>;
fn crtc(&self) -> crtc::Handle {
self.crtc
}
fn current_connectors(&self) -> Self::Connectors {
self.state.read().unwrap().connectors.clone()
}
fn pending_connectors(&self) -> Self::Connectors {
self.pending.read().unwrap().connectors.clone()
}
fn current_mode(&self) -> Mode {
self.state.read().unwrap().mode
}
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)? {
pending.connectors.insert(conn);
}
Ok(())
}
fn remove_connector(&self, connector: connector::Handle) -> Result<(), Error> {
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))
.collect::<Result<Vec<bool>, _>>()?
.iter()
.all(|v| *v)
{
pending.connectors = connectors.iter().cloned().collect();
}
Ok(())
}
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
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));
}
}
pending.mode = mode;
Ok(())
}
}
impl<A: AsRawFd + 'static> RawSurface for LegacyDrmSurfaceInternal<A> {
fn commit_pending(&self) -> bool {
*self.pending.read().unwrap() != *self.state.read().unwrap()
}
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();
{
let removed = current.connectors.difference(&pending.connectors);
let added = pending.connectors.difference(&current.connectors);
let mut conn_removed = false;
for conn in removed.clone() {
if let Ok(info) = self.get_connector(*conn) {
info!(self.logger, "Removing connector: {:?}", info.interface());
} else {
info!(self.logger, "Removing unknown connector");
}
// if the connector was mapped to our crtc, we need to ack the disconnect.
// the graphics pipeline will not be freed otherwise
conn_removed = true;
}
self.dev.set_connector_state(removed.copied(), false)?;
if conn_removed {
// null commit (necessary to trigger removal on the kernel side with the legacy api.)
self.set_crtc(self.crtc, None, (0, 0), &[], None)
.compat()
.map_err(|source| Error::Access {
errmsg: "Error setting crtc",
dev: self.dev_path(),
source,
})?;
}
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.name());
}
}
debug!(self.logger, "Setting screen");
// do a modeset and attach the given framebuffer
self.set_crtc(
self.crtc,
Some(framebuffer),
(0, 0),
&pending
.connectors
.iter()
.copied()
.collect::<Vec<connector::Handle>>(),
Some(pending.mode),
)
.compat()
.map_err(|source| Error::Access {
errmsg: "Error setting crtc",
dev: self.dev_path(),
source,
})?;
*current = pending.clone();
// set crtc does not trigger page_flip events, so we immediately queue a flip
// with the same framebuffer.
// this will result in wasting a frame, because this flip will need to wait
// for `set_crtc`, but is necessary to drive the event loop and thus provide
// a more consistent api.
ControlDevice::page_flip(
self,
self.crtc,
framebuffer,
&[PageFlipFlags::PageFlipEvent],
None,
)
.map_err(|source| Error::Access {
errmsg: "Failed to queue page flip",
dev: self.dev_path(),
source: source.compat(),
})
}
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,
framebuffer,
&[PageFlipFlags::PageFlipEvent],
None,
)
.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: Arc<Dev<A>>,
crtc: crtc::Handle, crtc: crtc::Handle,
mode: Mode, mode: Mode,
connectors: &[connector::Handle], connectors: &[connector::Handle],
logger: ::slog::Logger, logger: ::slog::Logger,
) -> Result<LegacyDrmSurfaceInternal<A>, Error> { ) -> Result<Self, Error> {
let logger = logger.new(o!("smithay_module" => "backend_drm_legacy", "drm_module" => "surface"));
info!( info!(
logger, logger,
"Initializing drm surface with mode {:?} and connectors {:?}", mode, connectors "Initializing drm surface with mode {:?} and connectors {:?}", mode, connectors
@ -336,32 +45,31 @@ impl<A: AsRawFd + 'static> LegacyDrmSurfaceInternal<A> {
// Try to enumarate the current state to set the initial state variable correctly. // Try to enumarate the current state to set the initial state variable correctly.
// We need an accurate state to handle `commit_pending`. // We need an accurate state to handle `commit_pending`.
let crtc_info = dev.get_crtc(crtc).compat().map_err(|source| Error::Access { let crtc_info = fd.get_crtc(crtc).map_err(|source| Error::Access {
errmsg: "Error loading crtc info", errmsg: "Error loading crtc info",
dev: dev.dev_path(), dev: fd.dev_path(),
source, source,
})?; })?;
let current_mode = crtc_info.mode(); let current_mode = crtc_info.mode();
let mut current_connectors = HashSet::new(); let mut current_connectors = HashSet::new();
let res_handles = ControlDevice::resource_handles(&*dev) let res_handles = fd.resource_handles()
.compat()
.map_err(|source| Error::Access { .map_err(|source| Error::Access {
errmsg: "Error loading drm resources", errmsg: "Error loading drm resources",
dev: dev.dev_path(), dev: fd.dev_path(),
source, source,
})?; })?;
for &con in res_handles.connectors() { for &con in res_handles.connectors() {
let con_info = dev.get_connector(con).compat().map_err(|source| Error::Access { let con_info = fd.get_connector(con).map_err(|source| Error::Access {
errmsg: "Error loading connector info", errmsg: "Error loading connector info",
dev: dev.dev_path(), dev: fd.dev_path(),
source, source,
})?; })?;
if let Some(enc) = con_info.current_encoder() { if let Some(enc) = con_info.current_encoder() {
let enc_info = dev.get_encoder(enc).compat().map_err(|source| Error::Access { let enc_info = fd.get_encoder(enc).map_err(|source| Error::Access {
errmsg: "Error loading encoder info", errmsg: "Error loading encoder info",
dev: dev.dev_path(), dev: fd.dev_path(),
source, source,
})?; })?;
if let Some(current_crtc) = enc_info.crtc() { if let Some(current_crtc) = enc_info.crtc() {
@ -385,8 +93,9 @@ impl<A: AsRawFd + 'static> LegacyDrmSurfaceInternal<A> {
connectors: connectors.iter().copied().collect(), connectors: connectors.iter().copied().collect(),
}; };
let surface = LegacyDrmSurfaceInternal { let surface = LegacyDrmSurface {
dev, fd,
active,
crtc, crtc,
state: RwLock::new(state), state: RwLock::new(state),
pending: RwLock::new(pending), pending: RwLock::new(pending),
@ -396,6 +105,213 @@ impl<A: AsRawFd + 'static> LegacyDrmSurfaceInternal<A> {
Ok(surface) Ok(surface)
} }
pub fn current_connectors(&self) -> HashSet<connector::Handle> {
self.state.read().unwrap().connectors.clone()
}
pub fn pending_connectors(&self) -> HashSet<connector::Handle> {
self.pending.read().unwrap().connectors.clone()
}
pub fn current_mode(&self) -> Mode {
self.state.read().unwrap().mode
}
pub fn pending_mode(&self) -> Mode {
self.pending.read().unwrap().mode
}
pub fn add_connector(&self, conn: connector::Handle) -> Result<(), Error> {
if !self.active.load(Ordering::SeqCst) {
return Err(Error::DeviceInactive);
}
let mut pending = self.pending.write().unwrap();
if self.check_connector(conn, &pending.mode)? {
pending.connectors.insert(conn);
}
Ok(())
}
pub fn remove_connector(&self, connector: connector::Handle) -> Result<(), Error> {
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(())
}
pub fn set_connectors(&self, connectors: &[connector::Handle]) -> Result<(), Error> {
if connectors.is_empty() {
return Err(Error::SurfaceWithoutConnectors(self.crtc));
}
if !self.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))
.collect::<Result<Vec<bool>, _>>()?
.iter()
.all(|v| *v)
{
pending.connectors = connectors.iter().cloned().collect();
}
Ok(())
}
pub fn use_mode(&self, mode: Mode) -> Result<(), Error> {
if !self.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
for connector in &pending.connectors {
if !self.fd
.get_connector(*connector)
.map_err(|source| Error::Access {
errmsg: "Error loading connector info",
dev: self.fd.dev_path(),
source,
})?
.modes()
.contains(&mode)
{
return Err(Error::ModeNotSuitable(mode));
}
}
pending.mode = mode;
Ok(())
}
pub fn commit_pending(&self) -> bool {
*self.pending.read().unwrap() != *self.state.read().unwrap()
}
pub fn commit(&self, framebuffer: framebuffer::Handle) -> Result<(), Error> {
if !self.active.load(Ordering::SeqCst) {
return Err(Error::DeviceInactive);
}
let mut current = self.state.write().unwrap();
let pending = self.pending.read().unwrap();
{
let removed = current.connectors.difference(&pending.connectors);
let added = pending.connectors.difference(&current.connectors);
let mut conn_removed = false;
for conn in removed.clone() {
if let Ok(info) = self.fd.get_connector(*conn) {
info!(self.logger, "Removing connector: {:?}", info.interface());
} else {
info!(self.logger, "Removing unknown connector");
}
// if the connector was mapped to our crtc, we need to ack the disconnect.
// the graphics pipeline will not be freed otherwise
conn_removed = true;
}
set_connector_state(&*self.fd, removed.copied(), false)?;
if conn_removed {
// null commit (necessary to trigger removal on the kernel side with the legacy api.)
self.fd.set_crtc(self.crtc, None, (0, 0), &[], None)
.map_err(|source| Error::Access {
errmsg: "Error setting crtc",
dev: self.fd.dev_path(),
source,
})?;
}
for conn in added.clone() {
if let Ok(info) = self.fd.get_connector(*conn) {
info!(self.logger, "Adding connector: {:?}", info.interface());
} else {
info!(self.logger, "Adding unknown connector");
}
}
set_connector_state(&*self.fd, added.copied(), true)?;
if current.mode != pending.mode {
info!(self.logger, "Setting new mode: {:?}", pending.mode.name());
}
}
debug!(self.logger, "Setting screen");
// do a modeset and attach the given framebuffer
self.fd.set_crtc(
self.crtc,
Some(framebuffer),
(0, 0),
&pending
.connectors
.iter()
.copied()
.collect::<Vec<connector::Handle>>(),
Some(pending.mode),
)
.map_err(|source| Error::Access {
errmsg: "Error setting crtc",
dev: self.fd.dev_path(),
source,
})?;
*current = pending.clone();
// set crtc does not trigger page_flip events, so we immediately queue a flip
// with the same framebuffer.
// this will result in wasting a frame, because this flip will need to wait
// for `set_crtc`, but is necessary to drive the event loop and thus provide
// a more consistent api.
ControlDevice::page_flip(
&*self.fd,
self.crtc,
framebuffer,
&[PageFlipFlags::PageFlipEvent],
None,
)
.map_err(|source| Error::Access {
errmsg: "Failed to queue page flip",
dev: self.fd.dev_path(),
source,
})
}
pub fn page_flip(&self, framebuffer: framebuffer::Handle) -> Result<(), Error> {
trace!(self.logger, "Queueing Page flip");
if !self.active.load(Ordering::SeqCst) {
return Err(Error::DeviceInactive);
}
ControlDevice::page_flip(
&*self.fd,
self.crtc,
framebuffer,
&[PageFlipFlags::PageFlipEvent],
None,
)
.map_err(|source| Error::Access {
errmsg: "Failed to page flip",
dev: self.fd.dev_path(),
source,
})
}
// we use this function to verify, if a certain connector/mode combination // we use this function to verify, if a certain connector/mode combination
// is valid on our crtc. We do this with the most basic information we have: // is valid on our crtc. We do this with the most basic information we have:
// - is there a matching encoder // - is there a matching encoder
@ -404,12 +320,11 @@ impl<A: AsRawFd + 'static> LegacyDrmSurfaceInternal<A> {
// Better would be some kind of test commit to ask the driver, // Better would be some kind of test commit to ask the driver,
// but that only exists for the atomic api. // but that only exists for the atomic api.
fn check_connector(&self, conn: connector::Handle, mode: &Mode) -> Result<bool, Error> { fn check_connector(&self, conn: connector::Handle, mode: &Mode) -> Result<bool, Error> {
let info = self let info = self.fd
.get_connector(conn) .get_connector(conn)
.compat()
.map_err(|source| Error::Access { .map_err(|source| Error::Access {
errmsg: "Error loading connector info", errmsg: "Error loading connector info",
dev: self.dev_path(), dev: self.fd.dev_path(),
source, source,
})?; })?;
@ -422,20 +337,19 @@ impl<A: AsRawFd + 'static> LegacyDrmSurfaceInternal<A> {
.filter(|enc| enc.is_some()) .filter(|enc| enc.is_some())
.map(|enc| enc.unwrap()) .map(|enc| enc.unwrap())
.map(|encoder| { .map(|encoder| {
self.get_encoder(encoder) self.fd.get_encoder(encoder)
.compat()
.map_err(|source| Error::Access { .map_err(|source| Error::Access {
errmsg: "Error loading encoder info", errmsg: "Error loading encoder info",
dev: self.dev_path(), dev: self.fd.dev_path(),
source, source,
}) })
}) })
.collect::<Result<Vec<encoder::Info>, _>>()?; .collect::<Result<Vec<encoder::Info>, _>>()?;
// and if any encoder supports the selected crtc // and if any encoder supports the selected crtc
let resource_handles = self.resource_handles().compat().map_err(|source| Error::Access { let resource_handles = self.fd.resource_handles().map_err(|source| Error::Access {
errmsg: "Error loading resources", errmsg: "Error loading resources",
dev: self.dev_path(), dev: self.fd.dev_path(),
source, source,
})?; })?;
if !encoders if !encoders
@ -453,11 +367,11 @@ impl<A: AsRawFd + 'static> LegacyDrmSurfaceInternal<A> {
} }
} }
impl<A: AsRawFd + 'static> Drop for LegacyDrmSurfaceInternal<A> { impl<A: AsRawFd + 'static> Drop for LegacyDrmSurface<A> {
fn drop(&mut self) { fn drop(&mut self) {
// ignore failure at this point // ignore failure at this point
if !self.dev.active.load(Ordering::SeqCst) { if !self.active.load(Ordering::SeqCst) {
// the device is gone or we are on another tty // the device is gone or we are on another tty
// old state has been restored, we shouldn't touch it. // old state has been restored, we shouldn't touch it.
// if we are on another tty the connectors will get disabled // if we are on another tty the connectors will get disabled
@ -465,110 +379,17 @@ impl<A: AsRawFd + 'static> Drop for LegacyDrmSurfaceInternal<A> {
return; return;
} }
let _ = self.set_cursor(self.crtc, Option::<&DumbBuffer>::None);
// disable connectors again // disable connectors again
let current = self.state.read().unwrap(); let current = self.state.read().unwrap();
if self if set_connector_state(&*self.fd, current.connectors.iter().copied(), false)
.dev
.set_connector_state(current.connectors.iter().copied(), false)
.is_ok() .is_ok()
{ {
// null commit // null commit
let _ = self.set_crtc(self.crtc, None, (0, 0), &[], None); let _ = self.fd.set_crtc(self.crtc, None, (0, 0), &[], None);
} }
} }
} }
/// Open raw crtc utilizing legacy mode-setting
pub struct LegacyDrmSurface<A: AsRawFd + 'static>(
pub(in crate::backend::drm) Arc<LegacyDrmSurfaceInternal<A>>,
);
impl<A: AsRawFd + 'static> AsRawFd for LegacyDrmSurface<A> {
fn as_raw_fd(&self) -> RawFd {
self.0.as_raw_fd()
}
}
impl<A: AsRawFd + 'static> BasicDevice for LegacyDrmSurface<A> {}
impl<A: AsRawFd + 'static> ControlDevice for LegacyDrmSurface<A> {}
impl<A: AsRawFd + 'static> CursorBackend for LegacyDrmSurface<A> {
type CursorFormat = dyn Buffer;
type Error = Error;
fn set_cursor_position(&self, x: u32, y: u32) -> Result<(), Error> {
self.0.set_cursor_position(x, y)
}
fn set_cursor_representation(
&self,
buffer: &Self::CursorFormat,
hotspot: (u32, u32),
) -> Result<(), Error> {
self.0.set_cursor_representation(buffer, hotspot)
}
fn clear_cursor_representation(&self) -> Result<(), Self::Error> {
self.0.clear_cursor_representation()
}
}
impl<A: AsRawFd + 'static> Surface for LegacyDrmSurface<A> {
type Error = Error;
type Connectors = HashSet<connector::Handle>;
fn crtc(&self) -> crtc::Handle {
self.0.crtc()
}
fn current_connectors(&self) -> Self::Connectors {
self.0.current_connectors()
}
fn pending_connectors(&self) -> Self::Connectors {
self.0.pending_connectors()
}
fn current_mode(&self) -> Mode {
self.0.current_mode()
}
fn pending_mode(&self) -> Mode {
self.0.pending_mode()
}
fn add_connector(&self, connector: connector::Handle) -> Result<(), Error> {
self.0.add_connector(connector)
}
fn remove_connector(&self, connector: connector::Handle) -> Result<(), Error> {
self.0.remove_connector(connector)
}
fn set_connectors(&self, connectors: &[connector::Handle]) -> Result<(), Self::Error> {
self.0.set_connectors(connectors)
}
fn use_mode(&self, mode: Mode) -> Result<(), Error> {
self.0.use_mode(mode)
}
}
impl<A: AsRawFd + 'static> RawSurface for LegacyDrmSurface<A> {
fn commit_pending(&self) -> bool {
self.0.commit_pending()
}
fn commit(&self, framebuffer: framebuffer::Handle) -> Result<(), Error> {
self.0.commit(framebuffer)
}
fn page_flip(&self, framebuffer: framebuffer::Handle) -> Result<(), Error> {
RawSurface::page_flip(&*self.0, framebuffer)
}
}
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use super::LegacyDrmSurface; use super::LegacyDrmSurface;

View File

@ -0,0 +1,180 @@
use std::os::unix::io::{AsRawFd, RawFd};
use std::sync::Arc;
use drm::Device as BasicDevice;
use drm::control::{Device as ControlDevice, Mode, crtc, connector, framebuffer, plane};
pub(super) mod atomic;
pub(super) mod legacy;
use super::error::Error;
use atomic::AtomicDrmSurface;
use legacy::LegacyDrmSurface;
pub struct DrmSurface<A: AsRawFd + 'static>
{
pub(super) crtc: crtc::Handle,
pub(super) plane: plane::Handle,
pub(super) internal: Arc<DrmSurfaceInternal<A>>,
}
pub enum DrmSurfaceInternal<A: AsRawFd + 'static> {
Atomic(AtomicDrmSurface<A>),
Legacy(LegacyDrmSurface<A>),
}
impl<A: AsRawFd + 'static> AsRawFd for DrmSurface<A> {
fn as_raw_fd(&self) -> RawFd {
match &*self.internal {
DrmSurfaceInternal::Atomic(surf) => surf.fd.as_raw_fd(),
DrmSurfaceInternal::Legacy(surf) => surf.fd.as_raw_fd(),
}
}
}
impl<A: AsRawFd + 'static> BasicDevice for DrmSurface<A> {}
impl<A: AsRawFd + 'static> ControlDevice for DrmSurface<A> {}
impl<A: AsRawFd + 'static> DrmSurface<A> {
/// Returns the underlying [`crtc`](drm::control::crtc) of this surface
pub fn crtc(&self) -> crtc::Handle {
self.crtc
}
/// Returns the underlying [`plane`](drm::control::plane) of this surface
pub fn plane(&self) -> plane::Handle {
self.plane
}
/// Currently used [`connector`](drm::control::connector)s of this `Surface`
pub fn current_connectors(&self) -> impl IntoIterator<Item = connector::Handle> {
match &*self.internal {
DrmSurfaceInternal::Atomic(surf) => surf.current_connectors(),
DrmSurfaceInternal::Legacy(surf) => surf.current_connectors(),
}
}
/// Returns the pending [`connector`](drm::control::connector)s
/// used after the next [`commit`](Surface::commit) of this [`Surface`]
pub fn pending_connectors(&self) -> impl IntoIterator<Item = connector::Handle> {
match &*self.internal {
DrmSurfaceInternal::Atomic(surf) => surf.pending_connectors(),
DrmSurfaceInternal::Legacy(surf) => surf.pending_connectors(),
}
}
/// 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
/// [`Mode`](drm::control::Mode).
pub fn add_connector(&self, connector: connector::Handle) -> Result<(), Error> {
match &*self.internal {
DrmSurfaceInternal::Atomic(surf) => surf.add_connector(connector),
DrmSurfaceInternal::Legacy(surf) => surf.add_connector(connector),
}
}
/// Tries to mark a [`connector`](drm::control::connector)
/// for removal on the next commit.
pub fn remove_connector(&self, connector: connector::Handle) -> Result<(), Error> {
match &*self.internal {
DrmSurfaceInternal::Atomic(surf) => surf.remove_connector(connector),
DrmSurfaceInternal::Legacy(surf) => surf.remove_connector(connector),
}
}
/// Tries to replace the current connector set with the newly provided one on the next commit.
///
/// Fails if one new `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
/// [`Mode`](drm::control::Mode).
pub fn set_connectors(&self, connectors: &[connector::Handle]) -> Result<(), Error> {
match &*self.internal {
DrmSurfaceInternal::Atomic(surf) => surf.set_connectors(connectors),
DrmSurfaceInternal::Legacy(surf) => surf.set_connectors(connectors),
}
}
/// Returns the currently active [`Mode`](drm::control::Mode)
/// of the underlying [`crtc`](drm::control::crtc)
pub fn current_mode(&self) -> Mode {
match &*self.internal {
DrmSurfaceInternal::Atomic(surf) => surf.current_mode(),
DrmSurfaceInternal::Legacy(surf) => surf.current_mode(),
}
}
/// Returns the currently pending [`Mode`](drm::control::Mode)
/// to be used after the next commit.
pub fn pending_mode(&self) -> Mode {
match &*self.internal {
DrmSurfaceInternal::Atomic(surf) => surf.pending_mode(),
DrmSurfaceInternal::Legacy(surf) => surf.pending_mode(),
}
}
/// Tries to set a new [`Mode`](drm::control::Mode)
/// to be used after the next commit.
///
/// Fails if the mode is not compatible with the underlying
/// [`crtc`](drm::control::crtc) or any of the
/// pending [`connector`](drm::control::connector)s.
pub fn use_mode(&self, mode: Mode) -> Result<(), Error> {
match &*self.internal {
DrmSurfaceInternal::Atomic(surf) => surf.use_mode(mode),
DrmSurfaceInternal::Legacy(surf) => surf.use_mode(mode),
}
}
/// Returns true whenever any state changes are pending to be commited
///
/// The following functions may trigger a pending commit:
/// - [`add_connector`](Surface::add_connector)
/// - [`remove_connector`](Surface::remove_connector)
/// - [`use_mode`](Surface::use_mode)
pub fn commit_pending(&self) -> bool {
match &*self.internal {
DrmSurfaceInternal::Atomic(surf) => surf.commit_pending(),
DrmSurfaceInternal::Legacy(surf) => surf.commit_pending(),
}
}
/// Commit the pending state rendering a given framebuffer.
///
/// *Note*: This will trigger a full modeset on the underlying device,
/// potentially causing some flickering. Check before performing this
/// operation if a commit really is necessary using [`commit_pending`](RawSurface::commit_pending).
///
/// This operation is not necessarily blocking until the crtc is in the desired state,
/// but will trigger a `vblank` event once done.
/// Make sure to [set a `DeviceHandler`](Device::set_handler) and
/// [register the belonging `Device`](device_bind) before to receive the event in time.
pub fn commit(&self, framebuffer: framebuffer::Handle) -> Result<(), Error> {
match &*self.internal {
DrmSurfaceInternal::Atomic(surf) => surf.commit(framebuffer),
DrmSurfaceInternal::Legacy(surf) => surf.commit(framebuffer),
}
}
/// Page-flip the underlying [`crtc`](drm::control::crtc)
/// to a new given [`framebuffer`].
///
/// This will not cause the crtc to modeset.
///
/// 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.
pub fn page_flip(&self, framebuffer: framebuffer::Handle) -> Result<(), Error> {
match &*self.internal {
DrmSurfaceInternal::Atomic(surf) => surf.page_flip(framebuffer),
DrmSurfaceInternal::Legacy(surf) => surf.page_flip(framebuffer)
}
}
}