🔒 Repository is read-only – file editing is disabled.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545
use std::io;
use std::os::unix::io::{AsFd, BorrowedFd};
use std::sync::atomic::Ordering;
use std::sync::Arc;
use drm::control::{connector, crtc, framebuffer, plane, Device as ControlDevice, Mode};
use drm::Device as BasicDevice;
use libc::dev_t;
pub(super) mod atomic;
#[cfg(feature = "backend_gbm")]
pub(super) mod gbm;
pub(super) mod legacy;
use super::{
device::PlaneClaimStorage, error::Error, plane_type, DrmDeviceFd, PlaneClaim, PlaneInfo, PlaneType,
Planes,
};
use crate::utils::DevPath;
use crate::utils::{Buffer, Physical, Point, Rectangle, Transform};
use atomic::AtomicDrmSurface;
use legacy::LegacyDrmSurface;
/// An open crtc + plane combination that can be used for scan-out
#[derive(Debug)]
pub struct DrmSurface {
// This field is only read when 'backend_session' is enabled
#[allow(dead_code)]
pub(super) dev_id: dev_t,
pub(super) crtc: crtc::Handle,
pub(super) planes: Planes,
pub(super) internal: Arc<DrmSurfaceInternal>,
pub(super) plane_claim_storage: PlaneClaimStorage,
pub(super) primary_plane: (PlaneInfo, PlaneClaim),
}
#[derive(Debug)]
struct PlaneDamageInner {
drm: DrmDeviceFd,
blob: Option<drm::control::property::Value<'static>>,
}
impl Drop for PlaneDamageInner {
fn drop(&mut self) {
// There is nothing we can do if that fails
if let Some(drm::control::property::Value::Blob(id)) = self.blob.take() {
let _ = self.drm.destroy_property_blob(id);
}
}
}
#[derive(Debug)]
/// Helper for `FB_DAMAGE_CLIPS`
pub struct PlaneDamageClips {
inner: Arc<PlaneDamageInner>,
}
impl PlaneDamageClips {
/// Returns the underlying blob
pub fn blob(&self) -> drm::control::property::Value<'_> {
self.inner.blob.unwrap()
}
}
impl PlaneDamageClips {
/// Initialize damage clips for a a plane
#[profiling::function]
pub fn from_damage(
device: &DrmDeviceFd,
src: Rectangle<f64, Buffer>,
dst: Rectangle<i32, Physical>,
damage: impl IntoIterator<Item = Rectangle<i32, Physical>>,
) -> io::Result<Option<Self>> {
let scale = src.size / dst.size.to_logical(1).to_buffer(1, Transform::Normal).to_f64();
let mut rects = damage
.into_iter()
.map(|rect| {
let mut rect = rect
.to_f64()
.to_logical(1f64)
.to_buffer(
1f64,
Transform::Normal,
&src.size.to_logical(1f64, Transform::Normal),
)
.upscale(scale);
rect.loc += src.loc;
let rect = rect.to_i32_up();
drm_ffi::drm_mode_rect {
x1: rect.loc.x,
y1: rect.loc.y,
x2: rect.loc.x.saturating_add(rect.size.w),
y2: rect.loc.y.saturating_add(rect.size.h),
}
})
.collect::<Vec<_>>();
if rects.is_empty() {
return Ok(None);
}
let data = unsafe {
std::slice::from_raw_parts_mut(
rects.as_mut_ptr() as *mut u8,
std::mem::size_of::<drm_ffi::drm_mode_rect>() * rects.len(),
)
};
let blob = drm_ffi::mode::create_property_blob(device.as_fd(), data)?;
Ok(Some(PlaneDamageClips {
inner: Arc::new(PlaneDamageInner {
drm: device.clone(),
blob: Some(drm::control::property::Value::Blob(blob.blob_id as u64)),
}),
}))
}
}
impl Clone for PlaneDamageClips {
fn clone(&self) -> Self {
Self {
inner: self.inner.clone(),
}
}
}
/// State of a single plane
#[derive(Debug, Clone)]
pub struct PlaneState<'a> {
/// Handle of the plane
pub handle: plane::Handle,
/// Configuration for that plane
///
/// Can be `None` if nothing is attached
pub config: Option<PlaneConfig<'a>>,
}
/// Configuration for a single plane
#[derive(Debug, Clone)]
pub struct PlaneConfig<'a> {
/// Source [`Rectangle`] of the attached framebuffer
pub src: Rectangle<f64, Buffer>,
/// Destination [`Rectangle`] on the CRTC
pub dst: Rectangle<i32, Physical>,
/// Transform for the attached framebuffer
pub transform: Transform,
/// Alpha value for the plane
pub alpha: f32,
/// Damage clips of the attached framebuffer
pub damage_clips: Option<drm::control::property::Value<'a>>,
/// Framebuffer handle
pub fb: framebuffer::Handle,
/// Optional fence
pub fence: Option<BorrowedFd<'a>>,
}
/// VRR support state
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum VrrSupport {
/// VRR is generally not supported
NotSupported,
/// VRR is supported, but changing it requires a modeset
RequiresModeset,
/// VRR is supported and can be changed without a modeset
Supported,
}
#[derive(Debug)]
#[allow(clippy::large_enum_variant)]
pub enum DrmSurfaceInternal {
Atomic(AtomicDrmSurface),
Legacy(LegacyDrmSurface),
}
impl AsFd for DrmSurface {
fn as_fd(&self) -> BorrowedFd<'_> {
match &*self.internal {
DrmSurfaceInternal::Atomic(surf) => surf.fd.as_fd(),
DrmSurfaceInternal::Legacy(surf) => surf.fd.as_fd(),
}
}
}
impl BasicDevice for DrmSurface {}
impl ControlDevice for DrmSurface {}
impl DrmSurface {
/// Returns the underlying [`DrmDeviceFd`]
pub fn device_fd(&self) -> &DrmDeviceFd {
match &*self.internal {
DrmSurfaceInternal::Atomic(surf) => surf.device_fd(),
DrmSurfaceInternal::Legacy(surf) => surf.device_fd(),
}
}
/// Returns whether this surface is using the legacy api
pub fn is_legacy(&self) -> bool {
matches!(&*self.internal, DrmSurfaceInternal::Legacy(_))
}
/// Returns the underlying [`crtc`](drm::control::crtc) of this surface
pub fn crtc(&self) -> crtc::Handle {
self.crtc
}
/// Returns the underlying primary [`plane`](drm::control::plane) of this surface
pub fn plane(&self) -> plane::Handle {
self.primary_plane.0.handle
}
/// Returns the [`PlaneInfo`] of the underlying primary [`plane`](drm::control::plane) of this surface
pub fn plane_info(&self) -> &PlaneInfo {
&self.primary_plane.0
}
/// 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`](DrmSurface::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 if Variable Refresh Rate is advertised as supported by the given connector.
///
/// Note: This will always return [`VrrSupport::NotSupported`] if the underlying
/// implementation is using the legacy DRM api.
pub fn vrr_supported(&self, conn: connector::Handle) -> Result<VrrSupport, Error> {
match &*self.internal {
DrmSurfaceInternal::Atomic(surf) => surf.vrr_supported(conn),
DrmSurfaceInternal::Legacy(_) => Ok(VrrSupport::NotSupported),
}
}
/// Returns whether the next frame state would set Variable Refresh Rate as enabled.
///
/// Note: This will always return `false` if the underlying implementation is using the
/// legacy DRM api.
pub fn vrr_enabled(&self) -> bool {
match &*self.internal {
DrmSurfaceInternal::Atomic(surf) => surf.vrr_enabled(),
DrmSurfaceInternal::Legacy(_) => false,
}
}
/// Tries to set Variable Refresh Rate (VRR) for the next frame.
///
/// Doing so might cause [`DrmSurface::commit_pending`] to return `true`.
/// Check [`DrmSurface::vrr_supported`], which indicates if VRR can be
/// used without a modeset on the attached connectors.
pub fn use_vrr(&self, vrr: bool) -> Result<(), Error> {
match &*self.internal {
DrmSurfaceInternal::Atomic(surf) => surf.use_vrr(vrr),
DrmSurfaceInternal::Legacy(_) => Err(Error::UnknownProperty {
handle: self.crtc.into(),
name: "VRR_ENABLED",
}),
}
}
/// Disables the given plane.
///
/// Errors if the plane is not supported by this crtc or if the underlying
/// implementation does not support the use of planes.
pub fn clear_plane(&self, plane: plane::Handle) -> Result<(), Error> {
match &*self.internal {
DrmSurfaceInternal::Atomic(surf) => surf.clear_plane(plane),
DrmSurfaceInternal::Legacy(_) => Err(Error::NonPrimaryPlane(plane)),
}
}
/// Returns true whenever any state changes are pending to be commited
///
/// The following functions may trigger a pending commit:
/// - [`add_connector`](DrmSurface::add_connector)
/// - [`remove_connector`](DrmSurface::remove_connector)
/// - [`use_mode`](DrmSurface::use_mode)
pub fn commit_pending(&self) -> bool {
match &*self.internal {
DrmSurfaceInternal::Atomic(surf) => surf.commit_pending(),
DrmSurfaceInternal::Legacy(surf) => surf.commit_pending(),
}
}
/// Test a state given a set of framebuffers.
///
/// *Note*: This will always return `Ok` for legacy devices if `allow_modeset = false`.
/// The legacy drm api has no way to test a buffer without triggering a modeset.
#[profiling::function]
pub fn test_state<'a>(
&self,
planes: impl IntoIterator<Item = PlaneState<'a>>,
allow_modeset: bool,
) -> Result<(), Error> {
match &*self.internal {
DrmSurfaceInternal::Atomic(surf) => surf.test_state(planes, allow_modeset),
DrmSurfaceInternal::Legacy(surf) => {
let fb = ensure_legacy_planes(self, planes)?;
if allow_modeset {
surf.test_buffer(fb, &self.pending_mode())
} else {
// Legacy can not test a buffer without triggering a modeset, so we can
// only assume it works and hope for the best. A later call to commit or
// page_flip will show the correct result
Ok(())
}
}
}
}
/// Commit the pending state rendering a given set of framebuffers.
///
/// *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`](DrmSurface::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 have the device registered in your event loop prior to invoking this, to not miss
/// any generated event.
#[profiling::function]
pub fn commit<'a>(
&self,
planes: impl IntoIterator<Item = PlaneState<'a>>,
event: bool,
) -> Result<(), Error> {
match &*self.internal {
DrmSurfaceInternal::Atomic(surf) => surf.commit(planes, event),
DrmSurfaceInternal::Legacy(surf) => {
let fb = ensure_legacy_planes(self, planes)?;
surf.commit(fb, event)
}
}
}
/// Page-flip the underlying [`crtc`](drm::control::crtc)
/// to a new given set of [`framebuffer`]s.
///
/// 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 have the device registered in your event loop to not miss the event.
#[profiling::function]
pub fn page_flip<'a>(
&self,
planes: impl IntoIterator<Item = PlaneState<'a>>,
event: bool,
) -> Result<(), Error> {
match &*self.internal {
DrmSurfaceInternal::Atomic(surf) => surf.page_flip(planes, event),
DrmSurfaceInternal::Legacy(surf) => {
let fb = ensure_legacy_planes(self, planes)?;
surf.page_flip(fb, event)
}
}
}
/// Returns a set of available planes for this surface
pub fn planes(&self) -> &Planes {
&self.planes
}
/// Claim a plane so that it won't be used by a different crtc
///
/// Returns `None` if the plane could not be claimed
pub fn claim_plane(&self, plane: plane::Handle) -> Option<PlaneClaim> {
// Validate that we are called with an plane that belongs to us
if self.planes.primary.iter().any(|p| p.handle == plane)
|| self.planes.cursor.iter().any(|p| p.handle == plane)
|| self.planes.overlay.iter().any(|p| p.handle == plane)
{
self.plane_claim_storage.claim(plane, self.crtc)
} else {
None
}
}
/// Re-evaluates the current state of the crtc.
///
/// It is recommended to call this function after this used [`Session`](crate::backend::session::Session)
/// gets re-activated / VT switched to.
///
/// Usually you do not need to call this in other circumstances, but if
/// the state of the crtc is modified elsewhere, you may call this function
/// to reset it's internal state.
pub fn reset_state(&self) -> Result<(), Error> {
match &*self.internal {
DrmSurfaceInternal::Atomic(surf) => surf.reset_state::<Self>(None),
DrmSurfaceInternal::Legacy(surf) => surf.reset_state::<Self>(None),
}
}
/// Returns if the underlying device is currently paused or not.
pub fn is_active(&self) -> bool {
match &*self.internal {
DrmSurfaceInternal::Atomic(surf) => surf.active.load(Ordering::SeqCst),
DrmSurfaceInternal::Legacy(surf) => surf.active.load(Ordering::SeqCst),
}
}
#[cfg(feature = "backend_gbm")]
pub(super) fn span(&self) -> &tracing::Span {
match &*self.internal {
DrmSurfaceInternal::Atomic(surf) => &surf.span,
DrmSurfaceInternal::Legacy(surf) => &surf.span,
}
}
/// Clear the surface, setting DPMS state to off and disabling all planes.
///
/// The surface will be re-enabled on the next [`page_flip`][Self::page_flip] or
/// [`commit`][Self::commit].
pub fn clear(&self) -> Result<(), Error> {
match &*self.internal {
DrmSurfaceInternal::Atomic(surf) => surf.clear(),
DrmSurfaceInternal::Legacy(surf) => surf.clear(),
}
}
}
fn ensure_legacy_planes<'a>(
dev: &(impl ControlDevice + DevPath),
planes: impl IntoIterator<Item = PlaneState<'a>>,
) -> Result<framebuffer::Handle, Error> {
let state = planes.into_iter().next().ok_or(Error::NoPlane)?;
if plane_type(dev, state.handle)? != PlaneType::Primary {
return Err(Error::NonPrimaryPlane(state.handle));
}
let Some(config) = state.config else {
// we need a config on the primary plane
return Err(Error::NoFramebuffer(state.handle));
};
if config.dst.loc != Point::default() {
// legacy does not support crtc position (technically we could do it,
// but the position can only be changed by commit, not by page-flip,
// so we just not allow it)
return Err(Error::UnsupportedPlaneConfiguration(state.handle));
}
if config.src.loc != Point::default()
|| config
.src
.size
.to_logical(1.0, Transform::Normal)
.to_physical(1.0)
!= config.dst.size.to_f64()
{
// legacy does not support crop nor scale
return Err(Error::UnsupportedPlaneConfiguration(state.handle));
}
if config.transform != Transform::Normal {
// legacy does not support transform
return Err(Error::UnsupportedPlaneConfiguration(state.handle));
}
Ok(config.fb)
}