From 0b672a622cc133b83a8a0d9e05a88a0de1778ba7 Mon Sep 17 00:00:00 2001 From: Christian Meissl Date: Sat, 25 Nov 2023 19:51:18 +0100 Subject: [PATCH 1/8] backend: export dumb buffer as rdwr this allows the resulting dmabuf to be mapped read/write for rendering --- Cargo.toml | 4 ++-- src/backend/allocator/dumb.rs | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index d3de1ba6c6f7..e036361653bf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,8 +34,8 @@ cursor-icon = "1.0.0" cgmath = "0.18.0" downcast-rs = "1.2.0" drm-fourcc = "^2.2.0" -drm = { version = "0.11.0", optional = true } -drm-ffi = { version = "0.7.0", optional = true } +drm = { version = "0.11.1", optional = true } +drm-ffi = { version = "0.7.1", optional = true } errno = "0.3.5" gbm = { version = "0.14.0", optional = true, default-features = false, features = ["drm-support"] } glow = { version = "0.12", optional = true } diff --git a/src/backend/allocator/dumb.rs b/src/backend/allocator/dumb.rs index 7443d1101338..c1950b9d8649 100644 --- a/src/backend/allocator/dumb.rs +++ b/src/backend/allocator/dumb.rs @@ -96,7 +96,9 @@ impl AsDmabuf for DumbBuffer { #[profiling::function] fn export(&self) -> Result { - let fd = self.fd.buffer_to_prime_fd(self.handle.handle(), 0)?; + let fd = self + .fd + .buffer_to_prime_fd(self.handle.handle(), drm::CLOEXEC | drm::RDWR)?; let mut builder = Dmabuf::builder( self.size(), self.format.code, From 910bcc13eaaf11656157dab5146d090a7b42458f Mon Sep 17 00:00:00 2001 From: Christian Meissl Date: Sat, 25 Nov 2023 19:52:20 +0100 Subject: [PATCH 2/8] backend: introduce DumbAllocator this allows to create a dumb buffer allocator from an open drm device fd. --- examples/buffer_test.rs | 8 ++++---- src/backend/allocator/dumb.rs | 23 ++++++++++++++++------- 2 files changed, 20 insertions(+), 11 deletions(-) diff --git a/examples/buffer_test.rs b/examples/buffer_test.rs index 4996f070dbb1..f8217f9ff96e 100644 --- a/examples/buffer_test.rs +++ b/examples/buffer_test.rs @@ -5,11 +5,12 @@ use smithay::{ backend::{ allocator::{ dmabuf::{AnyError, Dmabuf, DmabufAllocator}, + dumb::DumbAllocator, gbm::{GbmAllocator, GbmBufferFlags, GbmDevice}, vulkan::{ImageUsageFlags, VulkanAllocator}, Allocator, Fourcc, Modifier, }, - drm::{DrmDevice, DrmDeviceFd, DrmNode}, + drm::{DrmDeviceFd, DrmNode}, egl::{EGLContext, EGLDevice, EGLDisplay}, renderer::{ gles::{GlesRenderbuffer, GlesRenderer}, @@ -173,9 +174,8 @@ fn buffer_test(args: TestArgs) { let mut allocator = match args.allocator { AllocatorType::DumbBuffer => { let fd = open_device(&path); - Box::new(DmabufAllocator( - DrmDevice::new(fd, false).expect("Failed to init drm device").0, - )) as Box> + let dumb_allocator = DumbAllocator::new(fd); + Box::new(DmabufAllocator(dumb_allocator)) as Box> } AllocatorType::Gbm => { let fd = open_device(&path); diff --git a/src/backend/allocator/dumb.rs b/src/backend/allocator/dumb.rs index c1950b9d8649..8d22fa5874ae 100644 --- a/src/backend/allocator/dumb.rs +++ b/src/backend/allocator/dumb.rs @@ -9,7 +9,6 @@ use tracing::instrument; use super::dmabuf::{AsDmabuf, Dmabuf, DmabufFlags}; use super::{format::get_bpp, Allocator, Buffer, Format, Fourcc, Modifier}; -use crate::backend::drm::device::{DrmDevice, DrmDeviceInternal}; use crate::backend::drm::DrmDeviceFd; use crate::utils::{Buffer as BufferCoords, Size}; @@ -29,7 +28,20 @@ impl fmt::Debug for DumbBuffer { } } -impl Allocator for DrmDevice { +/// Light wrapper around an [`DrmDeviceFd`] to implement the [`Allocator`]-trait +#[derive(Debug)] +pub struct DumbAllocator { + fd: DrmDeviceFd, +} + +impl DumbAllocator { + /// Create a new [`DumbAllocator`] from a [`DrmDeviceFd`]. + pub fn new(fd: DrmDeviceFd) -> Self { + DumbAllocator { fd } + } +} + +impl Allocator for DumbAllocator { type Buffer = DumbBuffer; type Error = io::Error; @@ -50,17 +62,14 @@ impl Allocator for DrmDevice { return Err(rustix::io::Errno::INVAL.into()); } - let handle = self.create_dumb_buffer( + let handle = self.fd.create_dumb_buffer( (width, height), fourcc, get_bpp(fourcc).ok_or(rustix::io::Errno::INVAL)? as u32, )?; Ok(DumbBuffer { - fd: match &*self.internal { - DrmDeviceInternal::Atomic(dev) => dev.fd.clone(), - DrmDeviceInternal::Legacy(dev) => dev.fd.clone(), - }, + fd: self.fd.clone(), handle, format: Format { code: fourcc, From b65f46d28d9815c1f5a26b7c252c8b20c9923310 Mon Sep 17 00:00:00 2001 From: Christian Meissl Date: Sat, 25 Nov 2023 19:53:01 +0100 Subject: [PATCH 3/8] backend: add map and sync operations for dmabuf --- src/backend/allocator/dmabuf.rs | 147 ++++++++++++++++++++++++++++++++ 1 file changed, 147 insertions(+) diff --git a/src/backend/allocator/dmabuf.rs b/src/backend/allocator/dmabuf.rs index 37274936b608..fa07b1f369e6 100644 --- a/src/backend/allocator/dmabuf.rs +++ b/src/backend/allocator/dmabuf.rs @@ -12,6 +12,7 @@ use calloop::generic::Generic; use calloop::{EventSource, Interest, Mode, PostAction}; +use rustix::ioctl::{Setter, WriteOpcode}; use super::{Allocator, Buffer, Format, Fourcc, Modifier}; use crate::utils::{Buffer as BufferCoords, Size}; @@ -245,6 +246,152 @@ impl Dmabuf { let blocker = DmabufBlocker(source.signal.clone()); Ok((blocker, source)) } + + /// Map the plane at specified index with the specified mode + /// + /// Returns `Err` if the plane with the specified index does not exist or + /// mmap failed + pub fn map_plane( + &self, + idx: usize, + mode: DmabufMappingMode, + ) -> Result { + let plane = self + .0 + .planes + .get(idx) + .ok_or(DmabufMappingFailed::PlaneIndexOutOfBound)?; + + let size = rustix::fs::seek(&plane.fd, rustix::fs::SeekFrom::End(0)).map_err(std::io::Error::from)?; + rustix::fs::seek(&plane.fd, rustix::fs::SeekFrom::Start(0)).map_err(std::io::Error::from)?; + + let len = (size - plane.offset as u64) as usize; + let ptr = unsafe { + rustix::mm::mmap( + std::ptr::null_mut(), + len, + mode.into(), + rustix::mm::MapFlags::SHARED, + &plane.fd, + plane.offset as u64, + ) + } + .map_err(std::io::Error::from)?; + Ok(DmabufMapping { len, ptr }) + } + + /// Synchronize access for the plane at the specified index + /// + /// Returns `Err` if the plane with the specified index does not exist or + /// the dmabuf_sync ioctl failed + pub fn sync_plane(&self, idx: usize, flags: DmabufSyncFlags) -> Result<(), DmabufSyncFailed> { + let plane = self + .0 + .planes + .get(idx) + .ok_or(DmabufSyncFailed::PlaneIndexOutOfBound)?; + unsafe { rustix::ioctl::ioctl(&plane.fd, Setter::::new(dma_buf_sync { flags })) } + .map_err(std::io::Error::from)?; + Ok(()) + } +} + +bitflags::bitflags! { + /// Modes of mapping a dmabuf plane + #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] + pub struct DmabufMappingMode: u32 { + /// Map the dmabuf readable + const READ = 0b00000001; + /// Map the dmabuf writable + const WRITE = 0b00000010; + } +} + +impl From for rustix::mm::ProtFlags { + fn from(mode: DmabufMappingMode) -> Self { + let mut flags = rustix::mm::ProtFlags::empty(); + + if mode.contains(DmabufMappingMode::READ) { + flags |= rustix::mm::ProtFlags::READ; + } + + if mode.contains(DmabufMappingMode::WRITE) { + flags |= rustix::mm::ProtFlags::WRITE; + } + + flags + } +} + +/// Dmabuf mapping errors +#[derive(Debug, thiserror::Error)] +#[error("Mapping the dmabuf failed")] +pub enum DmabufMappingFailed { + /// The supplied index for the plane is out of bounds + #[error("The supplied index for the plane is out of bounds")] + PlaneIndexOutOfBound, + /// Io error during map operation + Io(#[from] std::io::Error), +} + +/// Dmabuf sync errors +#[derive(Debug, thiserror::Error)] +#[error("Sync of the dmabuf failed")] +pub enum DmabufSyncFailed { + /// The supplied index for the plane is out of bounds + #[error("The supplied index for the plane is out of bounds")] + PlaneIndexOutOfBound, + /// Io error during sync operation + Io(#[from] std::io::Error), +} + +bitflags::bitflags! { + /// Flags for the [`Dmabuf::sync_plane`](Dmabuf::sync_plane) operation + #[derive(Copy, Clone)] + pub struct DmabufSyncFlags: std::ffi::c_ulonglong { + /// Read from the dmabuf + const READ = 1 << 0; + /// Write to the dmabuf + #[allow(clippy::identity_op)] + const WRITE = 2 << 0; + /// Start of read/write + const START = 0 << 2; + /// End of read/write + const END = 1 << 2; + } +} + +#[repr(C)] +#[allow(non_camel_case_types)] +struct dma_buf_sync { + flags: DmabufSyncFlags, +} + +type DmaBufSync = WriteOpcode; + +/// A mapping into a [`Dmabuf`] +#[derive(Debug)] +pub struct DmabufMapping { + ptr: *mut std::ffi::c_void, + len: usize, +} + +impl DmabufMapping { + /// Access the raw pointer of the mapping + pub fn ptr(&self) -> *mut std::ffi::c_void { + self.ptr + } + + /// Access the length of the mapping + pub fn length(&self) -> usize { + self.len + } +} + +impl Drop for DmabufMapping { + fn drop(&mut self) { + let _ = unsafe { rustix::mm::munmap(self.ptr, self.len) }; + } } impl WeakDmabuf { From 09e315ba72be5d574ca453017aa6a0d951e2f645 Mon Sep 17 00:00:00 2001 From: Christian Meissl Date: Sat, 25 Nov 2023 20:24:17 +0100 Subject: [PATCH 4/8] backend: utilities to export fb from dumb buffers --- anvil/src/udev.rs | 10 +- src/backend/drm/compositor/mod.rs | 6 +- src/backend/drm/device/atomic.rs | 71 +++++++----- src/backend/drm/device/legacy.rs | 102 +++++++++------- src/backend/drm/device/mod.rs | 23 ++-- src/backend/drm/dumb.rs | 185 ++++++++++++++++++++++++++++++ src/backend/drm/error.rs | 34 ++++-- src/backend/drm/gbm.rs | 37 +----- src/backend/drm/mod.rs | 135 +++++++++++++--------- src/backend/drm/surface/atomic.rs | 129 ++++++++++++--------- src/backend/drm/surface/gbm.rs | 5 +- src/backend/drm/surface/legacy.rs | 130 ++++++++++++--------- 12 files changed, 571 insertions(+), 296 deletions(-) create mode 100644 src/backend/drm/dumb.rs diff --git a/anvil/src/udev.rs b/anvil/src/udev.rs index 1f2e7a00268d..cd3b489bc2b8 100644 --- a/anvil/src/udev.rs +++ b/anvil/src/udev.rs @@ -30,8 +30,8 @@ use smithay::{ Allocator, Fourcc, }, drm::{ - compositor::DrmCompositor, CreateDrmNodeError, DrmDevice, DrmDeviceFd, DrmError, DrmEvent, - DrmEventMetadata, DrmNode, DrmSurface, GbmBufferedSurface, NodeType, + compositor::DrmCompositor, CreateDrmNodeError, DrmAccessError, DrmDevice, DrmDeviceFd, DrmError, + DrmEvent, DrmEventMetadata, DrmNode, DrmSurface, GbmBufferedSurface, NodeType, }, egl::{self, context::ContextPriority, EGLDevice, EGLDisplay}, libinput::{LibinputInputBackend, LibinputSessionInterface}, @@ -1293,10 +1293,10 @@ impl AnvilState { } SwapBuffersError::TemporaryFailure(err) => matches!( err.downcast_ref::(), - Some(DrmError::Access { + Some(DrmError::Access(DrmAccessError { source, .. - }) if source.kind() == io::ErrorKind::PermissionDenied + })) if source.kind() == io::ErrorKind::PermissionDenied ), SwapBuffersError::ContextLost(err) => panic!("Rendering loop lost: {}", err), } @@ -1486,7 +1486,7 @@ impl AnvilState { SwapBuffersError::AlreadySwapped => false, SwapBuffersError::TemporaryFailure(err) => match err.downcast_ref::() { Some(DrmError::DeviceInactive) => true, - Some(DrmError::Access { source, .. }) => { + Some(DrmError::Access(DrmAccessError { source, .. })) => { source.kind() == io::ErrorKind::PermissionDenied } _ => false, diff --git a/src/backend/drm/compositor/mod.rs b/src/backend/drm/compositor/mod.rs index 0e191f2381b4..43a13e889cfc 100644 --- a/src/backend/drm/compositor/mod.rs +++ b/src/backend/drm/compositor/mod.rs @@ -166,7 +166,7 @@ use crate::{ utils::{Buffer as BufferCoords, DevPath, Physical, Point, Rectangle, Scale, Size, Transform}, }; -use super::{DrmDeviceFd, DrmSurface, Framebuffer, PlaneClaim, PlaneInfo, Planes}; +use super::{error::AccessError, DrmDeviceFd, DrmSurface, Framebuffer, PlaneClaim, PlaneInfo, Planes}; mod elements; pub mod gbm; @@ -1496,11 +1496,11 @@ where .get_driver_capability(DriverCapability::SyncObj) .map(|val| val != 0) .map_err(|err| { - FrameError::DrmError(DrmError::Access { + FrameError::DrmError(DrmError::Access(AccessError { errmsg: "Failed to query driver capability", dev: surface.dev_path(), source: err, - }) + })) })? && plane_has_property(&*surface, surface.plane(), "IN_FENCE_FD")?; diff --git a/src/backend/drm/device/atomic.rs b/src/backend/drm/device/atomic.rs index 3d2eefccc341..1413ee87c95e 100644 --- a/src/backend/drm/device/atomic.rs +++ b/src/backend/drm/device/atomic.rs @@ -11,6 +11,7 @@ use drm::control::{ }; use super::DrmDeviceFd; +use crate::backend::drm::error::AccessError; use crate::{backend::drm::error::Error, utils::DevPath}; use tracing::{debug, error, info_span, trace}; @@ -49,16 +50,20 @@ impl AtomicDrmDevice { let _guard = dev.span.enter(); // 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 res_handles = dev.fd.resource_handles().map_err(|source| { + Error::Access(AccessError { + errmsg: "Error loading drm resources", + dev: dev.fd.dev_path(), + source, + }) })?; - let planes = dev.fd.plane_handles().map_err(|source| Error::Access { - errmsg: "Error loading planes", - dev: dev.fd.dev_path(), - source, + let planes = dev.fd.plane_handles().map_err(|source| { + Error::Access(AccessError { + errmsg: "Error loading planes", + dev: dev.fd.dev_path(), + source, + }) })?; let mut old_state = dev.old_state.clone(); @@ -110,15 +115,19 @@ impl AtomicDrmDevice { // 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, + let res_handles = self.fd.resource_handles().map_err(|source| { + Error::Access(AccessError { + errmsg: "Error loading drm resources", + dev: self.fd.dev_path(), + source, + }) })?; - let plane_handles = self.fd.plane_handles().map_err(|source| Error::Access { - errmsg: "Error loading drm plane resources", - dev: self.fd.dev_path(), - source, + let plane_handles = self.fd.plane_handles().map_err(|source| { + Error::Access(AccessError { + errmsg: "Error loading drm plane resources", + dev: self.fd.dev_path(), + source, + }) })?; // Disable all connectors (otherwise we might run into conflicting commits when restarting the rendering loop) @@ -175,10 +184,12 @@ impl AtomicDrmDevice { } self.fd .atomic_commit(AtomicCommitFlags::ALLOW_MODESET, req) - .map_err(|source| Error::Access { - errmsg: "Failed to disable connectors", - dev: self.fd.dev_path(), - source, + .map_err(|source| { + Error::Access(AccessError { + errmsg: "Failed to disable connectors", + dev: self.fd.dev_path(), + source, + }) })?; Ok(()) @@ -246,10 +257,12 @@ where } Err(err) => Err(err), }) - .map_err(|source| Error::Access { - errmsg: "Error reading properties", - dev: fd.dev_path(), - source, + .map_err(|source| { + Error::Access(AccessError { + errmsg: "Error reading properties", + dev: fd.dev_path(), + source, + }) }) } @@ -284,9 +297,11 @@ where Err(err) => Err(err), } }) - .map_err(|source| Error::Access { - errmsg: "Error reading properties on {:?}", - dev: fd.dev_path(), - source, + .map_err(|source| { + Error::Access(AccessError { + errmsg: "Error reading properties on {:?}", + dev: fd.dev_path(), + source, + }) }) } diff --git a/src/backend/drm/device/legacy.rs b/src/backend/drm/device/legacy.rs index 299d76710864..dc1a23fa5561 100644 --- a/src/backend/drm/device/legacy.rs +++ b/src/backend/drm/device/legacy.rs @@ -7,7 +7,7 @@ use std::sync::{ use drm::control::{connector, crtc, Device as ControlDevice}; use super::DrmDeviceFd; -use crate::backend::drm::error::Error; +use crate::backend::drm::error::{AccessError, Error}; use crate::utils::DevPath; use tracing::{debug, error, info_span, trace}; @@ -34,28 +34,36 @@ impl LegacyDrmDevice { // 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, false).map_err(|source| Error::Access { - errmsg: "Error loading connector info", + let res_handles = dev.fd.resource_handles().map_err(|source| { + Error::Access(AccessError { + errmsg: "Error loading drm resources", 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", + }) + })?; + for &con in res_handles.connectors() { + let con_info = dev.fd.get_connector(con, false).map_err(|source| { + Error::Access(AccessError { + errmsg: "Error loading connector 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", + }) + })?; + if let Some(enc) = con_info.current_encoder() { + let enc_info = dev.fd.get_encoder(enc).map_err(|source| { + Error::Access(AccessError { + 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(AccessError { + errmsg: "Error loading crtc info", + dev: dev.fd.dev_path(), + source, + }) })?; dev.old_state .entry(crtc) @@ -83,10 +91,12 @@ impl LegacyDrmDevice { } 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, + let res_handles = self.fd.resource_handles().map_err(|source| { + Error::Access(AccessError { + errmsg: "Failed to query resource handles", + dev: self.fd.dev_path(), + source, + }) })?; set_connector_state(&self.fd, res_handles.connectors().iter().copied(), false)?; @@ -98,10 +108,12 @@ impl LegacyDrmDevice { // 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, + .map_err(|source| { + Error::Access(AccessError { + errmsg: "Error setting crtc", + dev: self.fd.dev_path(), + source, + }) })?; } @@ -154,27 +166,33 @@ where { // for every connector... for conn in connectors { - let info = dev.get_connector(conn, false).map_err(|source| Error::Access { - errmsg: "Failed to get connector infos", - dev: dev.dev_path(), - source, + let info = dev.get_connector(conn, false).map_err(|source| { + Error::Access(AccessError { + 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 props = dev.get_properties(conn).map_err(|source| { + Error::Access(AccessError { + 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, + let info = dev.get_property(*handle).map_err(|source| { + Error::Access(AccessError { + 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) { @@ -189,10 +207,12 @@ where 3 /*DRM_MODE_DPMS_OFF*/ }, ) - .map_err(|source| Error::Access { - errmsg: "Failed to set property of connector", - dev: dev.dev_path(), - source, + .map_err(|source| { + Error::Access(AccessError { + errmsg: "Failed to set property of connector", + dev: dev.dev_path(), + source, + }) })?; } } diff --git a/src/backend/drm/device/mod.rs b/src/backend/drm/device/mod.rs index 6a746d92d8e0..42f6a1df39ca 100644 --- a/src/backend/drm/device/mod.rs +++ b/src/backend/drm/device/mod.rs @@ -16,6 +16,7 @@ pub use self::fd::DrmDeviceFd; pub(super) mod legacy; use crate::utils::{Buffer, DevPath, Size}; +use super::error::AccessError; use super::surface::{atomic::AtomicDrmSurface, legacy::LegacyDrmSurface, DrmSurface, DrmSurfaceInternal}; use super::{error::Error, planes, Planes}; use atomic::AtomicDrmDevice; @@ -207,10 +208,12 @@ impl DrmDevice { .get_driver_capability(DriverCapability::CursorHeight) .unwrap_or(64); let cursor_size = Size::from((cursor_width as u32, cursor_height as u32)); - let resources = fd.resource_handles().map_err(|source| Error::Access { - errmsg: "Error loading resource handles", - dev: fd.dev_path(), - source, + let resources = fd.resource_handles().map_err(|source| { + Error::Access(AccessError { + errmsg: "Error loading resource handles", + dev: fd.dev_path(), + source, + }) })?; let internal = Arc::new(DrmDevice::create_internal(fd, active, disable_connectors)?); @@ -321,13 +324,13 @@ impl DrmDevice { } let planes = self.planes(&crtc)?; - let info = self - .get_plane(planes.primary.handle) - .map_err(|source| Error::Access { + let info = self.get_plane(planes.primary.handle).map_err(|source| { + Error::Access(AccessError { 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, planes.primary.handle)); @@ -497,11 +500,11 @@ impl EventSource for DrmDeviceNotifier { } Err(source) => { callback( - DrmEvent::Error(Error::Access { + DrmEvent::Error(Error::Access(AccessError { errmsg: "Error processing drm events", dev: self.internal.dev_path(), source, - }), + })), &mut None, ); } diff --git a/src/backend/drm/dumb.rs b/src/backend/drm/dumb.rs new file mode 100644 index 000000000000..2235f4d5b371 --- /dev/null +++ b/src/backend/drm/dumb.rs @@ -0,0 +1,185 @@ +//! Utilities to attach [`framebuffer::Handle`]s to dumb buffers + +use drm_fourcc::{DrmFormat, DrmModifier}; + +use drm::{ + buffer::{Buffer as DrmBuffer, PlanarBuffer}, + control::{framebuffer, Device, FbCmd2Flags}, +}; +use tracing::{trace, warn}; + +use crate::utils::DevPath; +use crate::{ + backend::{ + allocator::{ + dumb::DumbBuffer, + format::{get_bpp, get_depth, get_opaque}, + Buffer, Fourcc, + }, + drm::DrmDeviceFd, + }, + utils::{Buffer as BufferCoords, Size}, +}; + +use super::{error::AccessError, Framebuffer}; + +/// A GBM backed framebuffer +#[derive(Debug)] +pub struct DumbFramebuffer { + fb: framebuffer::Handle, + format: drm_fourcc::DrmFormat, + drm: DrmDeviceFd, +} + +impl Drop for DumbFramebuffer { + fn drop(&mut self) { + trace!(fb = ?self.fb, "destroying framebuffer"); + if let Err(err) = self.drm.destroy_framebuffer(self.fb) { + warn!(fb = ?self.fb, ?err, "failed to destroy framebuffer"); + } + } +} + +impl AsRef for DumbFramebuffer { + fn as_ref(&self) -> &framebuffer::Handle { + &self.fb + } +} + +impl Framebuffer for DumbFramebuffer { + fn format(&self) -> drm_fourcc::DrmFormat { + self.format + } +} + +struct PlanarDumbBuffer<'a>(&'a DumbBuffer); + +impl<'a> Buffer for PlanarDumbBuffer<'a> { + fn size(&self) -> Size { + self.0.size() + } + + fn format(&self) -> DrmFormat { + self.0.format() + } +} + +impl<'a> PlanarBuffer for PlanarDumbBuffer<'a> { + fn size(&self) -> (u32, u32) { + let size = self.0.size(); + (size.w as u32, size.h as u32) + } + + fn format(&self) -> Fourcc { + self.0.format().code + } + + fn modifier(&self) -> Option { + Some(self.0.format().modifier) + } + + fn pitches(&self) -> [u32; 4] { + [self.0.handle().pitch(), 0, 0, 0] + } + + fn handles(&self) -> [Option; 4] { + [Some(self.0.handle().handle()), None, None, None] + } + + fn offsets(&self) -> [u32; 4] { + [0, 0, 0, 0] + } +} + +struct OpaqueBufferWrapper<'a, B>(&'a B); +impl<'a, B> PlanarBuffer for OpaqueBufferWrapper<'a, B> +where + B: PlanarBuffer, +{ + fn size(&self) -> (u32, u32) { + self.0.size() + } + + fn format(&self) -> Fourcc { + let fmt = self.0.format(); + get_opaque(fmt).unwrap_or(fmt) + } + + fn modifier(&self) -> Option { + self.0.modifier() + } + + fn pitches(&self) -> [u32; 4] { + self.0.pitches() + } + + fn handles(&self) -> [Option; 4] { + self.0.handles() + } + + fn offsets(&self) -> [u32; 4] { + self.0.offsets() + } +} + +/// Attach a [`framebuffer::Handle`] to an [`DumbBuffer`] +#[profiling::function] +pub fn framebuffer_from_dumb_buffer( + drm: &DrmDeviceFd, + buffer: &DumbBuffer, + use_opaque: bool, +) -> Result { + let buffer = PlanarDumbBuffer(buffer); + + let format = Buffer::format(&buffer); + let modifier = buffer.modifier(); + let flags = if modifier.is_some() { + FbCmd2Flags::MODIFIERS + } else { + FbCmd2Flags::empty() + }; + + let ret = if use_opaque { + let opaque_wrapper = OpaqueBufferWrapper(&buffer); + drm.add_planar_framebuffer(&opaque_wrapper, flags).map(|fb| { + ( + fb, + drm_fourcc::DrmFormat { + code: opaque_wrapper.format(), + modifier: modifier.unwrap_or(DrmModifier::Invalid), + }, + ) + }) + } else { + drm.add_planar_framebuffer(&buffer, flags).map(|fb| (fb, format)) + }; + + let (fb, format) = match ret { + Ok(fb) => fb, + Err(source) => { + let fourcc = format.code; + let (depth, bpp) = get_depth(fourcc) + .and_then(|d| get_bpp(fourcc).map(|b| (d, b))) + .ok_or_else(|| AccessError { + errmsg: "Unknown format for legacy framebuffer", + dev: drm.dev_path(), + source, + })?; + + let fb = drm + .add_framebuffer(buffer.0.handle(), depth as u32, bpp as u32) + .map_err(|source| AccessError { + errmsg: "Failed to add framebuffer", + dev: drm.dev_path(), + source, + })?; + (fb, format) + } + }; + + Ok(DumbFramebuffer { + fb, + format, + drm: drm.clone(), + }) +} diff --git a/src/backend/drm/error.rs b/src/backend/drm/error.rs index c7486e082c6e..4b7778e82c0e 100644 --- a/src/backend/drm/error.rs +++ b/src/backend/drm/error.rs @@ -2,6 +2,19 @@ use crate::backend::SwapBuffersError; use drm::control::{connector, crtc, plane, Mode, RawResourceHandle}; use std::{io, path::PathBuf}; +/// DRM access error +#[derive(Debug, thiserror::Error)] +#[error("DRM access error: {errmsg} on device `{dev:?}` ({source:})")] +pub struct AccessError { + /// Error message associated to the access error + pub(crate) errmsg: &'static str, + /// Device on which the error was generated + pub(crate) dev: Option, + /// Underlying device error + #[source] + pub source: io::Error, +} + /// Errors thrown by the [`DrmDevice`](crate::backend::drm::DrmDevice) /// and the [`DrmSurface`](crate::backend::drm::DrmSurface). #[derive(thiserror::Error, Debug)] @@ -10,15 +23,8 @@ pub enum Error { #[error("Failed to aquire DRM master")] DrmMasterFailed, /// The `DrmDevice` encountered an access error - #[error("DRM access error: {errmsg} on device `{dev:?}` ({source:})")] - Access { - /// Error message associated to the access error - errmsg: &'static str, - /// Device on which the error was generated - dev: Option, - /// Underlying device error - source: io::Error, - }, + #[error(transparent)] + Access(#[from] AccessError), /// Unable to determine device id of drm device #[error("Unable to determine device id of drm device")] UnableToGetDeviceId(#[source] rustix::io::Errno), @@ -74,14 +80,18 @@ impl From for SwapBuffersError { fn from(err: Error) -> SwapBuffersError { match err { x @ Error::DeviceInactive => SwapBuffersError::TemporaryFailure(Box::new(x)), - Error::Access { + Error::Access(AccessError { errmsg, dev, source, .. - } if matches!( + }) if matches!( source.raw_os_error(), Some(libc::EPERM | libc::EBUSY | libc::EINTR) ) => { - SwapBuffersError::TemporaryFailure(Box::new(Error::Access { errmsg, dev, source })) + SwapBuffersError::TemporaryFailure(Box::new(Error::Access(AccessError { + errmsg, + dev, + source, + }))) } x => SwapBuffersError::ContextLost(Box::new(x)), } diff --git a/src/backend/drm/gbm.rs b/src/backend/drm/gbm.rs index 9b0645389980..a7f96a2de061 100644 --- a/src/backend/drm/gbm.rs +++ b/src/backend/drm/gbm.rs @@ -1,8 +1,6 @@ //! Utilities to attach [`framebuffer::Handle`]s to gbm backed buffers -use std::io; use std::os::unix::io::AsFd; -use std::path::PathBuf; use thiserror::Error; @@ -28,7 +26,7 @@ use crate::backend::{ }; use crate::utils::DevPath; -use super::Framebuffer; +use super::{error::AccessError, Framebuffer}; /// A GBM backed framebuffer #[derive(Debug)] @@ -177,39 +175,6 @@ pub fn framebuffer_from_dmabuf( }) } -/// Possible errors for attaching a [`framebuffer::Handle`] with [`framebuffer_from_bo`] -#[derive(Debug, Error)] -#[error("failed to add a framebuffer")] -pub struct AccessError { - /// Error message associated to the access error - errmsg: &'static str, - /// Device on which the error was generated - dev: Option, - /// Underlying device error - #[source] - pub source: io::Error, -} - -impl From for super::DrmError { - fn from(err: AccessError) -> Self { - super::DrmError::Access { - errmsg: err.errmsg, - dev: err.dev, - source: err.source, - } - } -} - -impl TryFrom for AccessError { - type Error = super::DrmError; - fn try_from(err: super::DrmError) -> Result { - match err { - super::DrmError::Access { errmsg, dev, source } => Ok(AccessError { errmsg, dev, source }), - err => Err(err), - } - } -} - /// Attach a [`framebuffer::Handle`] to an [`BufferObject`] #[profiling::function] pub fn framebuffer_from_bo( diff --git a/src/backend/drm/mod.rs b/src/backend/drm/mod.rs index 076e1bb585e2..80b9c7dde42d 100644 --- a/src/backend/drm/mod.rs +++ b/src/backend/drm/mod.rs @@ -73,6 +73,8 @@ #[cfg(all(feature = "wayland_frontend", feature = "backend_gbm"))] pub mod compositor; pub(crate) mod device; +#[cfg(feature = "backend_drm")] +pub mod dumb; mod error; #[cfg(feature = "backend_gbm")] pub mod gbm; @@ -88,6 +90,7 @@ pub use device::{ Time as DrmEventTime, }; use drm_fourcc::{DrmFormat, DrmFourcc, DrmModifier}; +pub use error::AccessError as DrmAccessError; pub use error::Error as DrmError; pub use node::{CreateDrmNodeError, DrmNode, NodeType}; #[cfg(feature = "backend_gbm")] @@ -100,6 +103,8 @@ use drm::{ }; use tracing::trace; +use self::error::AccessError; + /// Common framebuffer operations pub trait Framebuffer: AsRef { /// Retrieve the format of the framebuffer @@ -139,23 +144,29 @@ fn planes( let mut cursor = None; let mut overlay = Vec::new(); - let planes = dev.plane_handles().map_err(|source| DrmError::Access { - errmsg: "Error loading plane handles", - dev: dev.dev_path(), - source, + let planes = dev.plane_handles().map_err(|source| { + DrmError::Access(AccessError { + errmsg: "Error loading plane handles", + dev: dev.dev_path(), + source, + }) })?; - let resources = dev.resource_handles().map_err(|source| DrmError::Access { - errmsg: "Error loading resource handles", - dev: dev.dev_path(), - source, + let resources = dev.resource_handles().map_err(|source| { + DrmError::Access(AccessError { + errmsg: "Error loading resource handles", + dev: dev.dev_path(), + source, + }) })?; for plane in planes { - let info = dev.get_plane(plane).map_err(|source| DrmError::Access { - errmsg: "Failed to get plane information", - dev: dev.dev_path(), - source, + let info = dev.get_plane(plane).map_err(|source| { + DrmError::Access(AccessError { + errmsg: "Failed to get plane information", + dev: dev.dev_path(), + source, + }) })?; let filter = info.possible_crtcs(); if resources.filter_crtcs(filter).contains(crtc) { @@ -190,17 +201,21 @@ fn planes( } fn plane_type(dev: &(impl ControlDevice + DevPath), plane: plane::Handle) -> Result { - let props = dev.get_properties(plane).map_err(|source| DrmError::Access { - errmsg: "Failed to get properties of plane", - dev: dev.dev_path(), - source, + let props = dev.get_properties(plane).map_err(|source| { + DrmError::Access(AccessError { + errmsg: "Failed to get properties of plane", + dev: dev.dev_path(), + source, + }) })?; let (ids, vals) = props.as_props_and_values(); for (&id, &val) in ids.iter().zip(vals.iter()) { - let info = dev.get_property(id).map_err(|source| DrmError::Access { - errmsg: "Failed to get property info", - dev: dev.dev_path(), - source, + let info = dev.get_property(id).map_err(|source| { + DrmError::Access(AccessError { + errmsg: "Failed to get property info", + dev: dev.dev_path(), + source, + }) })?; if info.name().to_str().map(|x| x == "type").unwrap_or(false) { return Ok(match val { @@ -214,17 +229,21 @@ fn plane_type(dev: &(impl ControlDevice + DevPath), plane: plane::Handle) -> Res } fn plane_zpos(dev: &(impl ControlDevice + DevPath), plane: plane::Handle) -> Result, DrmError> { - let props = dev.get_properties(plane).map_err(|source| DrmError::Access { - errmsg: "Failed to get properties of plane", - dev: dev.dev_path(), - source, + let props = dev.get_properties(plane).map_err(|source| { + DrmError::Access(AccessError { + errmsg: "Failed to get properties of plane", + dev: dev.dev_path(), + source, + }) })?; let (ids, vals) = props.as_props_and_values(); for (&id, &val) in ids.iter().zip(vals.iter()) { - let info = dev.get_property(id).map_err(|source| DrmError::Access { - errmsg: "Failed to get property info", - dev: dev.dev_path(), - source, + let info = dev.get_property(id).map_err(|source| { + DrmError::Access(AccessError { + errmsg: "Failed to get property info", + dev: dev.dev_path(), + source, + }) })?; if info.name().to_str().map(|x| x == "zpos").unwrap_or(false) { let plane_zpos = match info.value_type().convert_value(val) { @@ -246,10 +265,12 @@ fn plane_formats( plane: plane::Handle, ) -> Result, DrmError> { // get plane formats - let plane_info = dev.get_plane(plane).map_err(|source| DrmError::Access { - errmsg: "Error loading plane info", - dev: dev.dev_path(), - source, + let plane_info = dev.get_plane(plane).map_err(|source| { + DrmError::Access(AccessError { + errmsg: "Error loading plane info", + dev: dev.dev_path(), + source, + }) })?; let mut formats = HashSet::new(); for code in plane_info @@ -264,10 +285,12 @@ fn plane_formats( } if let Ok(1) = dev.get_driver_capability(DriverCapability::AddFB2Modifiers) { - let set = dev.get_properties(plane).map_err(|source| DrmError::Access { - errmsg: "Failed to query properties", - dev: dev.dev_path(), - source, + let set = dev.get_properties(plane).map_err(|source| { + DrmError::Access(AccessError { + errmsg: "Failed to query properties", + dev: dev.dev_path(), + source, + }) })?; let (handles, _) = set.as_props_and_values(); // for every handle ... @@ -286,10 +309,12 @@ fn plane_formats( }) .copied(); if let Some(prop) = prop { - let prop_info = dev.get_property(prop).map_err(|source| DrmError::Access { - errmsg: "Failed to query property", - dev: dev.dev_path(), - source, + let prop_info = dev.get_property(prop).map_err(|source| { + DrmError::Access(AccessError { + errmsg: "Failed to query property", + dev: dev.dev_path(), + source, + }) })?; let (handles, raw_values) = set.as_props_and_values(); let raw_value = raw_values[handles @@ -299,10 +324,12 @@ fn plane_formats( .unwrap()]; if let drm::control::property::Value::Blob(blob) = prop_info.value_type().convert_value(raw_value) { - let data = dev.get_property_blob(blob).map_err(|source| DrmError::Access { - errmsg: "Failed to query property blob data", - dev: dev.dev_path(), - source, + let data = dev.get_property_blob(blob).map_err(|source| { + DrmError::Access(AccessError { + errmsg: "Failed to query property blob data", + dev: dev.dev_path(), + source, + }) })?; // be careful here, we have no idea about the alignment inside the blob, so always copy using `read_unaligned`, // although slice::from_raw_parts would be so much nicer to iterate and to read. @@ -375,17 +402,21 @@ fn plane_has_property( plane: plane::Handle, name: &str, ) -> Result { - let props = dev.get_properties(plane).map_err(|source| DrmError::Access { - errmsg: "Failed to get properties of plane", - dev: dev.dev_path(), - source, + let props = dev.get_properties(plane).map_err(|source| { + DrmError::Access(AccessError { + errmsg: "Failed to get properties of plane", + dev: dev.dev_path(), + source, + }) })?; let (ids, _) = props.as_props_and_values(); for &id in ids { - let info = dev.get_property(id).map_err(|source| DrmError::Access { - errmsg: "Failed to get property info", - dev: dev.dev_path(), - source, + let info = dev.get_property(id).map_err(|source| { + DrmError::Access(AccessError { + errmsg: "Failed to get property info", + dev: dev.dev_path(), + source, + }) })?; if info.name().to_str().map(|x| x == name).unwrap_or(false) { return Ok(true); diff --git a/src/backend/drm/surface/atomic.rs b/src/backend/drm/surface/atomic.rs index 8b813e098101..67f5bec65779 100644 --- a/src/backend/drm/surface/atomic.rs +++ b/src/backend/drm/surface/atomic.rs @@ -12,6 +12,7 @@ use std::sync::{ Arc, RwLock, }; +use crate::backend::drm::error::AccessError; use crate::utils::{Coordinate, Point, Rectangle, Transform}; use crate::{ backend::{ @@ -43,10 +44,12 @@ impl State { crtc: crtc::Handle, prop_mapping: &mut Mapping, ) -> Result { - let crtc_info = fd.get_crtc(crtc).map_err(|source| Error::Access { - errmsg: "Error loading crtc info", - dev: fd.dev_path(), - source, + let crtc_info = fd.get_crtc(crtc).map_err(|source| { + Error::Access(AccessError { + errmsg: "Error loading crtc info", + dev: fd.dev_path(), + source, + }) })?; // If we have no current mode, we create a fake one, which will not match (and thus gets overridden on the commit below). @@ -55,18 +58,22 @@ impl State { // So we cheat, because it works and is easier to handle later. let current_mode = crtc_info.mode().unwrap_or_else(|| unsafe { std::mem::zeroed() }); let current_blob = match crtc_info.mode() { - Some(mode) => fd.create_property_blob(&mode).map_err(|source| Error::Access { - errmsg: "Failed to create Property Blob for mode", - dev: fd.dev_path(), - source, + Some(mode) => fd.create_property_blob(&mode).map_err(|source| { + Error::Access(AccessError { + errmsg: "Failed to create Property Blob for mode", + dev: fd.dev_path(), + source, + }) })?, None => property::Value::Unknown(0), }; - let res_handles = fd.resource_handles().map_err(|source| Error::Access { - errmsg: "Error loading drm resources", - dev: fd.dev_path(), - source, + let res_handles = fd.resource_handles().map_err(|source| { + Error::Access(AccessError { + errmsg: "Error loading drm resources", + dev: fd.dev_path(), + source, + }) })?; // the current set of connectors are those, that already have the correct `CRTC_ID` set. @@ -143,10 +150,12 @@ impl AtomicDrmSurface { ); let state = State::current_state(&*fd, crtc, &mut prop_mapping)?; - let blob = fd.create_property_blob(&mode).map_err(|source| Error::Access { - errmsg: "Failed to create Property Blob for mode", - dev: fd.dev_path(), - source, + let blob = fd.create_property_blob(&mode).map_err(|source| { + Error::Access(AccessError { + errmsg: "Failed to create Property Blob for mode", + dev: fd.dev_path(), + source, + }) })?; let pending = State { mode, @@ -185,10 +194,12 @@ impl AtomicDrmSurface { let db = self .fd .create_dumb_buffer((w as u32, h as u32), format, get_bpp(format).unwrap() as u32) - .map_err(|source| Error::Access { - errmsg: "Failed to create dumb buffer", - dev: self.fd.dev_path(), - source, + .map_err(|source| { + Error::Access(AccessError { + errmsg: "Failed to create dumb buffer", + dev: self.fd.dev_path(), + source, + }) })?; let fb_result = self .fd @@ -197,10 +208,12 @@ impl AtomicDrmSurface { get_depth(format).unwrap() as u32, get_bpp(format).unwrap() as u32, ) - .map_err(|source| Error::Access { - errmsg: "Failed to create framebuffer", - dev: self.fd.dev_path(), - source, + .map_err(|source| { + Error::Access(AccessError { + errmsg: "Failed to create framebuffer", + dev: self.fd.dev_path(), + source, + }) }); match fb_result { @@ -242,10 +255,12 @@ impl AtomicDrmSurface { &*self.fd, self.fd .resource_handles() - .map_err(|source| Error::Access { - errmsg: "Error loading connector info", - dev: self.fd.dev_path(), - source, + .map_err(|source| { + Error::Access(AccessError { + errmsg: "Error loading connector info", + dev: self.fd.dev_path(), + source, + }) })? .connectors(), &mut self.prop_mapping.write().unwrap().0, @@ -261,14 +276,13 @@ impl AtomicDrmSurface { } self.ensure_props_known(&[conn])?; - let info = self - .fd - .get_connector(conn, false) - .map_err(|source| Error::Access { + let info = self.fd.get_connector(conn, false).map_err(|source| { + Error::Access(AccessError { errmsg: "Error loading connector info", dev: self.fd.dev_path(), source, - })?; + }) + })?; let mut pending = self.pending.write().unwrap(); @@ -425,14 +439,13 @@ impl AtomicDrmSurface { let mut pending = self.pending.write().unwrap(); // check if new config is supported - let new_blob = self - .fd - .create_property_blob(&mode) - .map_err(|source| Error::Access { + let new_blob = self.fd.create_property_blob(&mode).map_err(|source| { + Error::Access(AccessError { errmsg: "Failed to create Property Blob for mode", dev: self.fd.dev_path(), source, - })?; + }) + })?; let test_buffer = self.create_test_buffer(mode.size(), self.plane)?; @@ -507,10 +520,12 @@ impl AtomicDrmSurface { } else { AtomicCommitFlags::TEST_ONLY }; - self.fd.atomic_commit(flags, req).map_err(|source| Error::Access { - errmsg: "Error testing state", - dev: self.fd.dev_path(), - source, + self.fd.atomic_commit(flags, req).map_err(|source| { + Error::Access(AccessError { + errmsg: "Error testing state", + dev: self.fd.dev_path(), + source, + }) }) } @@ -607,10 +622,12 @@ impl AtomicDrmSurface { }, req, ) - .map_err(|source| Error::Access { - errmsg: "Error setting crtc", - dev: self.fd.dev_path(), - source, + .map_err(|source| { + Error::Access(AccessError { + errmsg: "Error setting crtc", + dev: self.fd.dev_path(), + source, + }) }); if result.is_ok() { @@ -658,10 +675,12 @@ impl AtomicDrmSurface { }, req, ) - .map_err(|source| Error::Access { - errmsg: "Page flip commit failed", - dev: self.fd.dev_path(), - source, + .map_err(|source| { + Error::Access(AccessError { + errmsg: "Page flip commit failed", + dev: self.fd.dev_path(), + source, + }) }); if res.is_ok() { @@ -1012,10 +1031,12 @@ impl AtomicDrmSurface { let result = self .fd .atomic_commit(AtomicCommitFlags::NONBLOCK, req) - .map_err(|source| Error::Access { - errmsg: "Failed to commit on clear_plane", - dev: self.fd.dev_path(), - source, + .map_err(|source| { + Error::Access(AccessError { + errmsg: "Failed to commit on clear_plane", + dev: self.fd.dev_path(), + source, + }) }); if result.is_ok() { diff --git a/src/backend/drm/surface/gbm.rs b/src/backend/drm/surface/gbm.rs index 49206f9c98da..f663e21dfd91 100644 --- a/src/backend/drm/surface/gbm.rs +++ b/src/backend/drm/surface/gbm.rs @@ -10,6 +10,7 @@ use crate::backend::allocator::dmabuf::{AsDmabuf, Dmabuf}; use crate::backend::allocator::format::get_opaque; use crate::backend::allocator::gbm::GbmConvertError; use crate::backend::allocator::{Allocator, Format, Fourcc, Modifier, Slot, Swapchain}; +use crate::backend::drm::error::AccessError; use crate::backend::drm::gbm::{framebuffer_from_bo, GbmFramebuffer}; use crate::backend::drm::{plane_has_property, DrmError, DrmSurface}; use crate::backend::renderer::sync::SyncPoint; @@ -77,11 +78,11 @@ where .get_driver_capability(DriverCapability::SyncObj) .map(|val| val != 0) .map_err(|err| { - Error::DrmError(DrmError::Access { + Error::DrmError(DrmError::Access(AccessError { errmsg: "Failed to query driver capability", dev: drm.dev_path(), source: err, - }) + })) })? && plane_has_property(&*drm, drm.plane(), "IN_FENCE_FD")?; diff --git a/src/backend/drm/surface/legacy.rs b/src/backend/drm/surface/legacy.rs index 0878d1356e10..ecaa78fae3d5 100644 --- a/src/backend/drm/surface/legacy.rs +++ b/src/backend/drm/surface/legacy.rs @@ -6,6 +6,7 @@ use std::sync::{ Arc, RwLock, }; +use crate::backend::drm::error::AccessError; use crate::{ backend::drm::{ device::legacy::set_connector_state, device::DrmDeviceInternal, error::Error, DrmDeviceFd, @@ -25,31 +26,39 @@ impl State { fn current_state(fd: &A, crtc: crtc::Handle) -> Result { // Try to enumarate the current state to set the initial state variable correctly. // We need an accurate state to handle `commit_pending`. - let crtc_info = fd.get_crtc(crtc).map_err(|source| Error::Access { - errmsg: "Error loading crtc info", - dev: fd.dev_path(), - source, + let crtc_info = fd.get_crtc(crtc).map_err(|source| { + Error::Access(AccessError { + errmsg: "Error loading crtc info", + dev: fd.dev_path(), + source, + }) })?; let current_mode = crtc_info.mode(); let mut current_connectors = HashSet::new(); - let res_handles = fd.resource_handles().map_err(|source| Error::Access { - errmsg: "Error loading drm resources", - dev: fd.dev_path(), - source, - })?; - for &con in res_handles.connectors() { - let con_info = fd.get_connector(con, false).map_err(|source| Error::Access { - errmsg: "Error loading connector info", + let res_handles = fd.resource_handles().map_err(|source| { + Error::Access(AccessError { + errmsg: "Error loading drm resources", dev: fd.dev_path(), source, - })?; - if let Some(enc) = con_info.current_encoder() { - let enc_info = fd.get_encoder(enc).map_err(|source| Error::Access { - errmsg: "Error loading encoder info", + }) + })?; + for &con in res_handles.connectors() { + let con_info = fd.get_connector(con, false).map_err(|source| { + Error::Access(AccessError { + errmsg: "Error loading connector info", dev: fd.dev_path(), source, + }) + })?; + if let Some(enc) = con_info.current_encoder() { + let enc_info = fd.get_encoder(enc).map_err(|source| { + Error::Access(AccessError { + errmsg: "Error loading encoder info", + dev: fd.dev_path(), + source, + }) })?; if let Some(current_crtc) = enc_info.crtc() { if crtc == current_crtc { @@ -192,10 +201,12 @@ impl LegacyDrmSurface { if !self .fd .get_connector(*connector, false) - .map_err(|source| Error::Access { - errmsg: "Error loading connector info", - dev: self.fd.dev_path(), - source, + .map_err(|source| { + Error::Access(AccessError { + errmsg: "Error loading connector info", + dev: self.fd.dev_path(), + source, + }) })? .modes() .contains(&mode) @@ -244,10 +255,12 @@ impl LegacyDrmSurface { // 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, + .map_err(|source| { + Error::Access(AccessError { + errmsg: "Error setting crtc", + dev: self.fd.dev_path(), + source, + }) })?; } @@ -279,10 +292,12 @@ impl LegacyDrmSurface { .collect::>(), Some(pending.mode), ) - .map_err(|source| Error::Access { - errmsg: "Error setting crtc", - dev: self.fd.dev_path(), - source, + .map_err(|source| { + Error::Access(AccessError { + errmsg: "Error setting crtc", + dev: self.fd.dev_path(), + source, + }) })?; *current = pending.clone(); @@ -294,10 +309,12 @@ impl LegacyDrmSurface { // 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::EVENT, None).map_err( - |source| Error::Access { - errmsg: "Failed to queue page flip", - dev: self.fd.dev_path(), - source, + |source| { + Error::Access(AccessError { + errmsg: "Failed to queue page flip", + dev: self.fd.dev_path(), + source, + }) }, )?; } @@ -325,10 +342,12 @@ impl LegacyDrmSurface { }, None, ) - .map_err(|source| Error::Access { - errmsg: "Failed to page flip", - dev: self.fd.dev_path(), - source, + .map_err(|source| { + Error::Access(AccessError { + errmsg: "Failed to page flip", + dev: self.fd.dev_path(), + source, + }) }) } @@ -354,10 +373,12 @@ impl LegacyDrmSurface { .collect::>(), Some(*mode), ) - .map_err(|source| Error::Access { - errmsg: "Failed to test buffer", - dev: self.fd.dev_path(), - source, + .map_err(|source| { + Error::Access(AccessError { + errmsg: "Failed to test buffer", + dev: self.fd.dev_path(), + source, + }) }) } @@ -369,14 +390,13 @@ impl LegacyDrmSurface { // Better would be some kind of test commit to ask the driver, // but that only exists for the atomic api. fn check_connector(&self, conn: connector::Handle, mode: &Mode) -> Result { - let info = self - .fd - .get_connector(conn, false) - .map_err(|source| Error::Access { + let info = self.fd.get_connector(conn, false).map_err(|source| { + Error::Access(AccessError { errmsg: "Error loading connector info", dev: self.fd.dev_path(), source, - })?; + }) + })?; // check if the connector can handle the current mode if info.modes().contains(mode) { @@ -385,19 +405,23 @@ impl LegacyDrmSurface { .encoders() .iter() .map(|encoder| { - self.fd.get_encoder(*encoder).map_err(|source| Error::Access { - errmsg: "Error loading encoder info", - dev: self.fd.dev_path(), - source, + self.fd.get_encoder(*encoder).map_err(|source| { + Error::Access(AccessError { + errmsg: "Error loading encoder info", + dev: self.fd.dev_path(), + source, + }) }) }) .collect::, _>>()?; // and if any encoder supports the selected crtc - let resource_handles = self.fd.resource_handles().map_err(|source| Error::Access { - errmsg: "Error loading resources", - dev: self.fd.dev_path(), - source, + let resource_handles = self.fd.resource_handles().map_err(|source| { + Error::Access(AccessError { + errmsg: "Error loading resources", + dev: self.fd.dev_path(), + source, + }) })?; if !encoders .iter() From ac61b1d158f001eaef721c34c0ec98ba329794d5 Mon Sep 17 00:00:00 2001 From: Christian Meissl Date: Sat, 25 Nov 2023 20:25:00 +0100 Subject: [PATCH 5/8] backend: add dumb buffer framebuffer exporter --- src/backend/drm/compositor/dumb.rs | 45 ++++++++++++++++++++++++++++++ src/backend/drm/compositor/mod.rs | 1 + 2 files changed, 46 insertions(+) create mode 100644 src/backend/drm/compositor/dumb.rs diff --git a/src/backend/drm/compositor/dumb.rs b/src/backend/drm/compositor/dumb.rs new file mode 100644 index 000000000000..4803337634d6 --- /dev/null +++ b/src/backend/drm/compositor/dumb.rs @@ -0,0 +1,45 @@ +//! Implementation for ExportFramebuffer and related utilizies for dumb buffers + +use thiserror::Error; + +use super::{ExportBuffer, ExportFramebuffer}; +use crate::backend::{ + allocator::dumb::DumbBuffer, + drm::{ + dumb::{framebuffer_from_dumb_buffer, DumbFramebuffer}, + error::AccessError, + DrmDeviceFd, + }, +}; + +/// Possible errors for attaching a [`framebuffer::Handle`] +#[derive(Error, Debug)] +pub enum Error { + /// The buffer is not supported + #[error("Unsupported buffer supplied")] + Unsupported, + /// Failed to add a framebuffer for the dumb buffer + #[error("failed to add a framebuffer for the dumb buffer")] + Drm(AccessError), +} + +impl ExportFramebuffer for DrmDeviceFd { + type Framebuffer = DumbFramebuffer; + type Error = Error; + + #[profiling::function] + fn add_framebuffer( + &self, + _drm: &DrmDeviceFd, + buffer: ExportBuffer<'_, DumbBuffer>, + use_opaque: bool, + ) -> Result, Self::Error> { + match buffer { + #[cfg(feature = "wayland_frontend")] + ExportBuffer::Wayland(_) => return Err(Error::Unsupported), + ExportBuffer::Allocator(buffer) => framebuffer_from_dumb_buffer(self, buffer, use_opaque) + .map_err(Error::Drm) + .map(Some), + } + } +} diff --git a/src/backend/drm/compositor/mod.rs b/src/backend/drm/compositor/mod.rs index 43a13e889cfc..21e1c2ddcf33 100644 --- a/src/backend/drm/compositor/mod.rs +++ b/src/backend/drm/compositor/mod.rs @@ -168,6 +168,7 @@ use crate::{ use super::{error::AccessError, DrmDeviceFd, DrmSurface, Framebuffer, PlaneClaim, PlaneInfo, Planes}; +pub mod dumb; mod elements; pub mod gbm; From 71f0c9dc2bc2fa237dcb263c6d7ab3de3e47b1b2 Mon Sep 17 00:00:00 2001 From: Christian Meissl Date: Sat, 25 Nov 2023 20:28:50 +0100 Subject: [PATCH 6/8] backend: add pixman based renderer --- Cargo.toml | 5 +- src/backend/renderer/mod.rs | 3 + src/backend/renderer/pixman/error.rs | 67 ++ src/backend/renderer/pixman/mod.rs | 1199 ++++++++++++++++++++++++++ src/reexports.rs | 2 + 5 files changed, 1275 insertions(+), 1 deletion(-) create mode 100644 src/backend/renderer/pixman/error.rs create mode 100644 src/backend/renderer/pixman/mod.rs diff --git a/Cargo.toml b/Cargo.toml index e036361653bf..00fbf73605a9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -67,6 +67,8 @@ scan_fmt = { version = "0.2.3", default-features = false } encoding_rs = { version = "0.8.33", optional = true } profiling = "1.0" smallvec = "1.11" +pixman = { version = "0.1.0", features = ["drm-fourcc"], optional = true } + [dev-dependencies] clap = { version = "4", features = ["derive"] } @@ -97,13 +99,14 @@ desktop = [] renderer_gl = ["gl_generator", "backend_egl"] renderer_glow = ["renderer_gl", "glow"] renderer_multi = ["backend_drm"] +renderer_pixman = ["pixman"] renderer_test = [] use_system_lib = ["wayland_frontend", "wayland-backend/server_system", "wayland-sys", "gbm?/import-wayland"] use_bindgen = ["drm-ffi/use_bindgen", "gbm/use_bindgen", "input/gen"] wayland_frontend = ["wayland-server", "wayland-protocols", "wayland-protocols-wlr", "wayland-protocols-misc", "tempfile"] x11rb_event_source = ["x11rb"] xwayland = ["encoding_rs", "wayland_frontend", "x11rb/composite", "x11rb/xfixes", "x11rb_event_source", "scopeguard"] -test_all_features = ["default", "use_system_lib", "renderer_glow", "renderer_test"] +test_all_features = ["default", "use_system_lib", "renderer_glow", "renderer_test", "renderer_pixman"] [[example]] name = "minimal" diff --git a/src/backend/renderer/mod.rs b/src/backend/renderer/mod.rs index 40ba77d6f94b..a8c22aa88cf4 100644 --- a/src/backend/renderer/mod.rs +++ b/src/backend/renderer/mod.rs @@ -25,6 +25,9 @@ pub mod gles; #[cfg(feature = "renderer_glow")] pub mod glow; +#[cfg(feature = "renderer_pixman")] +pub mod pixman; + use crate::backend::allocator::{dmabuf::Dmabuf, Format, Fourcc}; #[cfg(all( feature = "wayland_frontend", diff --git a/src/backend/renderer/pixman/error.rs b/src/backend/renderer/pixman/error.rs new file mode 100644 index 000000000000..4fa839bf9435 --- /dev/null +++ b/src/backend/renderer/pixman/error.rs @@ -0,0 +1,67 @@ +use drm_fourcc::{DrmFourcc, DrmModifier}; +use thiserror::Error; + +use crate::backend::{ + allocator::dmabuf::{DmabufMappingFailed, DmabufSyncFailed}, + SwapBuffersError, +}; + +#[cfg(feature = "wayland_frontend")] +use wayland_server::protocol::wl_shm; + +/// Error returned during rendering using pixman +#[derive(Debug, Error)] +pub enum PixmanError { + /// The given buffer has an unsupported number of planes + #[error("Unsupported number of planes")] + UnsupportedNumberOfPlanes, + /// The given buffer has an unsupported pixel format + #[error("Unsupported pixel format: {0:?}")] + UnsupportedPixelFormat(DrmFourcc), + /// The given buffer has an unsupported modifier + #[error("Unsupported modifier: {0:?}")] + UnsupportedModifier(DrmModifier), + /// The given wl buffer has an unsupported pixel format + #[error("Unsupported wl_shm format: {0:?}")] + #[cfg(feature = "wayland_frontend")] + UnsupportedWlPixelFormat(wl_shm::Format), + /// The given buffer is incomplete + #[error("Incomplete buffer {expected} < {actual}")] + IncompleteBuffer { + /// Expected len of the buffer + expected: usize, + /// Actual len of the buffer + actual: usize, + }, + /// The given buffer was not accessible + #[error("Error accessing the buffer ({0:?})")] + #[cfg(feature = "wayland_frontend")] + BufferAccessError(#[from] crate::wayland::shm::BufferAccessError), + /// Failed to import the given buffer + #[error("Import failed")] + ImportFailed, + /// The underlying buffer has been destroyed + #[error("The underlying buffer has been destroyed")] + BufferDestroyed, + /// Mapping the buffer failed + #[error("Mapping the buffer failed: {0}")] + Map(#[from] DmabufMappingFailed), + /// Synchronizing access to the buffer failed + #[error("Synchronizing buffer failed: {0}")] + Sync(#[from] DmabufSyncFailed), + /// The requested operation failed + #[error("The requested operation failed")] + Failed(#[from] pixman::OperationFailed), + /// No target is currently bound + #[error("No target is currently bound")] + NoTargetBound, + /// The requested operation is not supported + #[error("The requested operation is not supported")] + Unsupported, +} + +impl From for SwapBuffersError { + fn from(value: PixmanError) -> Self { + SwapBuffersError::ContextLost(Box::new(value)) + } +} diff --git a/src/backend/renderer/pixman/mod.rs b/src/backend/renderer/pixman/mod.rs new file mode 100644 index 000000000000..e692d9766398 --- /dev/null +++ b/src/backend/renderer/pixman/mod.rs @@ -0,0 +1,1199 @@ +//! Implementation of the rendering traits using pixman +use std::{ + collections::HashSet, + rc::Rc, + sync::atomic::{AtomicBool, Ordering}, +}; + +use drm_fourcc::{DrmFormat, DrmFourcc, DrmModifier}; +use pixman::{Filter, FormatCode, Image, Operation, Repeat}; +use tracing::warn; + +use crate::{ + backend::allocator::{ + dmabuf::{Dmabuf, DmabufMapping, DmabufMappingMode, DmabufSyncFailed, DmabufSyncFlags, WeakDmabuf}, + format::has_alpha, + Buffer, + }, + utils::{Buffer as BufferCoords, Physical, Rectangle, Scale, Size, Transform}, +}; + +#[cfg(feature = "wayland_frontend")] +use crate::{ + backend::renderer::{ImportDmaWl, ImportMemWl}, + wayland::{compositor::SurfaceData, shm}, +}; +#[cfg(feature = "wayland_frontend")] +use wayland_server::{protocol::wl_buffer, Resource, Weak}; + +#[cfg(all( + feature = "wayland_frontend", + feature = "backend_egl", + feature = "use_system_lib" +))] +use super::ImportEgl; +use super::{ + sync::SyncPoint, Bind, DebugFlags, ExportMem, Frame, ImportDma, ImportMem, Offscreen, Renderer, Texture, + TextureFilter, TextureMapping, Unbind, +}; + +mod error; + +pub use error::*; + +lazy_static::lazy_static! { + static ref SUPPORTED_FORMATS: Vec = { + vec![ + #[cfg(target_endian = "little")] + DrmFourcc::Rgb565, + DrmFourcc::Xrgb8888, + DrmFourcc::Argb8888, + DrmFourcc::Xbgr8888, + DrmFourcc::Abgr8888, + DrmFourcc::Rgbx8888, + DrmFourcc::Rgba8888, + DrmFourcc::Bgrx8888, + DrmFourcc::Bgra8888, + #[cfg(target_endian = "little")] + DrmFourcc::Xrgb2101010, + #[cfg(target_endian = "little")] + DrmFourcc::Argb2101010, + #[cfg(target_endian = "little")] + DrmFourcc::Xbgr2101010, + #[cfg(target_endian = "little")] + DrmFourcc::Abgr2101010, + ] + }; +} + +#[derive(Debug)] +enum PixmanTarget { + Image { dmabuf: Dmabuf, image: PixmanImage }, + RenderBuffer(PixmanRenderBuffer), +} + +impl PixmanTarget { + fn image(&self) -> &pixman::Image<'static, 'static> { + match self { + PixmanTarget::Image { image, .. } => &image.0.image, + PixmanTarget::RenderBuffer(render_buffer) => &render_buffer.0, + } + } +} + +/// Offscreen render buffer +#[derive(Debug)] +pub struct PixmanRenderBuffer(pixman::Image<'static, 'static>); + +impl From> for PixmanRenderBuffer { + fn from(value: pixman::Image<'static, 'static>) -> Self { + Self(value) + } +} + +#[derive(Debug)] +struct PixmanDmabufMapping { + dmabuf: WeakDmabuf, + _mapping: DmabufMapping, +} + +#[derive(Debug)] +struct PixmanImageInner { + #[cfg(feature = "wayland_frontend")] + buffer: Option>, + dmabuf: Option, + image: Image<'static, 'static>, + _flipped: bool, /* TODO: What about flipped textures? */ +} + +#[derive(Debug, Clone)] +struct PixmanImage(Rc); + +impl PixmanImage { + #[profiling::function] + fn accessor<'l>(&'l self) -> Result, PixmanError> { + #[cfg(feature = "wayland_frontend")] + let buffer = if let Some(buffer) = self.0.buffer.as_ref() { + Some(buffer.upgrade().map_err(|_| PixmanError::BufferDestroyed)?) + } else { + None + }; + + let guard = if let Some(mapping) = self.0.dmabuf.as_ref() { + let dmabuf = mapping.dmabuf.upgrade().ok_or(PixmanError::BufferDestroyed)?; + Some(DmabufReadGuard::new(dmabuf)?) + } else { + None + }; + + Ok(TextureAccessor { + #[cfg(feature = "wayland_frontend")] + buffer, + image: &self.0.image, + _guard: guard, + }) + } +} + +/// A handle to a pixman texture +#[derive(Debug, Clone)] +pub struct PixmanTexture(PixmanImage); + +impl From> for PixmanTexture { + fn from(image: pixman::Image<'static, 'static>) -> Self { + Self(PixmanImage(Rc::new(PixmanImageInner { + #[cfg(feature = "wayland_frontend")] + buffer: None, + dmabuf: None, + _flipped: false, + image, + }))) + } +} + +struct DmabufReadGuard { + dmabuf: Dmabuf, +} + +impl DmabufReadGuard { + #[profiling::function] + pub fn new(dmabuf: Dmabuf) -> Result { + dmabuf.sync_plane(0, DmabufSyncFlags::START | DmabufSyncFlags::READ)?; + Ok(Self { dmabuf }) + } +} + +impl Drop for DmabufReadGuard { + #[profiling::function] + fn drop(&mut self) { + if let Err(err) = self + .dmabuf + .sync_plane(0, DmabufSyncFlags::END | DmabufSyncFlags::READ) + { + tracing::warn!(?err, "failed to end sync read"); + } + } +} + +struct TextureAccessor<'l> { + #[cfg(feature = "wayland_frontend")] + buffer: Option, + image: &'l Image<'static, 'static>, + _guard: Option, +} + +impl<'l> TextureAccessor<'l> { + fn with_image(&self, f: F) -> Result + where + F: FnOnce(&'l Image<'static, 'static>) -> R, + { + #[cfg(feature = "wayland_frontend")] + if let Some(buffer) = self.buffer.as_ref() { + // We only have a buffer in case the image was created from + // a shm buffer. In this case we need to guard against SIGPIPE + // when accessing the image + return Ok(shm::with_buffer_contents(buffer, |_, _, _| f(self.image))?); + } + + Ok(f(self.image)) + } +} + +impl PixmanTexture { + #[profiling::function] + fn accessor<'l>(&'l self) -> Result, PixmanError> { + self.0.accessor() + } +} + +impl Texture for PixmanTexture { + fn width(&self) -> u32 { + self.0 .0.image.width() as u32 + } + + fn height(&self) -> u32 { + self.0 .0.image.height() as u32 + } + + fn format(&self) -> Option { + DrmFourcc::try_from(self.0 .0.image.format()).ok() + } +} + +/// Handle to the currently rendered frame during [`PixmanRenderer::render`](Renderer::render). +#[derive(Debug)] +pub struct PixmanFrame<'frame> { + renderer: &'frame mut PixmanRenderer, + + transform: Transform, + output_size: Size, + size: Size, + + finished: AtomicBool, +} + +impl<'frame> PixmanFrame<'frame> { + fn draw_solid_color( + &mut self, + dst: Rectangle, + damage: &[Rectangle], + color: [f32; 4], + op: Operation, + debug: DebugFlags, + ) -> Result<(), PixmanError> { + let target_image = self + .renderer + .target + .as_ref() + .ok_or(PixmanError::NoTargetBound)? + .image(); + + let solid = pixman::Solid::new(color).map_err(|_| PixmanError::Unsupported)?; + + let mut clip_region = + pixman::Region32::init_rect(0, 0, self.output_size.w as u32, self.output_size.h as u32); + + let damage_boxes = damage + .iter() + .copied() + .map(|mut rect| { + rect.loc += dst.loc; + + let rect = self.transform.transform_rect_in(rect, &self.size); + + let p1 = rect.loc; + let p2 = p1 + rect.size.to_point(); + pixman::Box32 { + x1: p1.x, + y1: p1.y, + x2: p2.x, + y2: p2.y, + } + }) + .collect::>(); + let damage_region = pixman::Region32::init_rects(&damage_boxes); + clip_region = clip_region.intersect(&damage_region); + + target_image.set_clip_region32(Some(&clip_region))?; + + target_image.composite32( + op, + &solid, + None, + (0, 0), + (0, 0), + (0, 0), + (target_image.width() as i32, target_image.height() as i32), + ); + + if debug.contains(DebugFlags::TINT) { + target_image.composite32( + Operation::Over, + &self.renderer.tint, + None, + (0, 0), + (0, 0), + (0, 0), + (target_image.width() as i32, target_image.height() as i32), + ); + } + + target_image.set_clip_region32(None)?; + + Ok(()) + } +} + +impl<'frame> Frame for PixmanFrame<'frame> { + type Error = PixmanError; + + type TextureId = PixmanTexture; + + fn id(&self) -> usize { + 0 + } + + #[profiling::function] + fn clear(&mut self, color: [f32; 4], at: &[Rectangle]) -> Result<(), Self::Error> { + self.draw_solid_color( + Rectangle::from_loc_and_size((0, 0), self.size), + at, + color, + Operation::Src, + DebugFlags::empty(), + ) + } + + #[profiling::function] + fn draw_solid( + &mut self, + dst: Rectangle, + damage: &[Rectangle], + color: [f32; 4], + ) -> Result<(), Self::Error> { + let op = if color[3] == 1f32 { + Operation::Src + } else { + Operation::Over + }; + self.draw_solid_color(dst, damage, color, op, self.renderer.debug_flags) + } + + #[profiling::function] + fn render_texture_from_to( + &mut self, + texture: &Self::TextureId, + src: Rectangle, + dst: Rectangle, + damage: &[Rectangle], + src_transform: Transform, + alpha: f32, + ) -> Result<(), Self::Error> { + let target_image = self + .renderer + .target + .as_ref() + .ok_or(PixmanError::NoTargetBound)? + .image(); + let src_image_accessor = texture.accessor()?; + + let dst_loc = dst.loc; + let dst = self.transform.transform_rect_in(dst, &self.size); + + // Our renderer works with clock-wise rotation, but the scr_transform in contrast to + // the output transform is specified counter-clock-wise. + let src_transform = src_transform.invert(); + + let src: Rectangle = src.to_i32_up::(); + + let image_transform = match (src_transform, self.transform) { + (Transform::Normal, output_transform) => output_transform, + + (Transform::_90, Transform::Normal) => Transform::_270, + (Transform::_90, Transform::_90) => Transform::Normal, + (Transform::_90, Transform::_180) => Transform::_90, + (Transform::_90, Transform::_270) => Transform::_180, + (Transform::_90, Transform::Flipped) => Transform::Flipped90, + (Transform::_90, Transform::Flipped90) => Transform::Flipped180, + (Transform::_90, Transform::Flipped180) => Transform::Flipped270, + (Transform::_90, Transform::Flipped270) => Transform::Flipped, + + (Transform::_180, Transform::Normal) => Transform::_180, + (Transform::_180, Transform::_90) => Transform::_270, + (Transform::_180, Transform::_180) => Transform::Normal, + (Transform::_180, Transform::_270) => Transform::_90, + (Transform::_180, Transform::Flipped) => Transform::Flipped180, + (Transform::_180, Transform::Flipped90) => Transform::Flipped270, + (Transform::_180, Transform::Flipped180) => Transform::Flipped, + (Transform::_180, Transform::Flipped270) => Transform::Flipped90, + + (Transform::_270, Transform::Normal) => Transform::_90, + (Transform::_270, Transform::_90) => Transform::_180, + (Transform::_270, Transform::_180) => Transform::_270, + (Transform::_270, Transform::_270) => Transform::Normal, + (Transform::_270, Transform::Flipped) => Transform::Flipped270, + (Transform::_270, Transform::Flipped90) => Transform::Flipped, + (Transform::_270, Transform::Flipped180) => Transform::Flipped90, + (Transform::_270, Transform::Flipped270) => Transform::Flipped180, + + (Transform::Flipped, Transform::Normal) => Transform::Flipped, + (Transform::Flipped, Transform::_90) => Transform::Flipped90, + (Transform::Flipped, Transform::_180) => Transform::Flipped180, + (Transform::Flipped, Transform::_270) => Transform::Flipped270, + (Transform::Flipped, Transform::Flipped) => Transform::Normal, + (Transform::Flipped, Transform::Flipped90) => Transform::_90, + (Transform::Flipped, Transform::Flipped180) => Transform::_180, + (Transform::Flipped, Transform::Flipped270) => Transform::_270, + + (Transform::Flipped90, Transform::Normal) => Transform::Flipped90, + (Transform::Flipped90, Transform::_90) => Transform::Flipped180, + (Transform::Flipped90, Transform::_180) => Transform::Flipped270, + (Transform::Flipped90, Transform::_270) => Transform::Flipped, + (Transform::Flipped90, Transform::Flipped) => Transform::_270, + (Transform::Flipped90, Transform::Flipped90) => Transform::Normal, + (Transform::Flipped90, Transform::Flipped180) => Transform::_90, + (Transform::Flipped90, Transform::Flipped270) => Transform::_180, + + (Transform::Flipped180, Transform::Normal) => Transform::Flipped180, + (Transform::Flipped180, Transform::_90) => Transform::Flipped270, + (Transform::Flipped180, Transform::_180) => Transform::Flipped, + (Transform::Flipped180, Transform::_270) => Transform::Flipped90, + (Transform::Flipped180, Transform::Flipped) => Transform::_180, + (Transform::Flipped180, Transform::Flipped90) => Transform::_270, + (Transform::Flipped180, Transform::Flipped180) => Transform::Normal, + (Transform::Flipped180, Transform::Flipped270) => Transform::_90, + + (Transform::Flipped270, Transform::Normal) => Transform::Flipped270, + (Transform::Flipped270, Transform::_90) => Transform::Flipped, + (Transform::Flipped270, Transform::_180) => Transform::Flipped90, + (Transform::Flipped270, Transform::_270) => Transform::Flipped180, + (Transform::Flipped270, Transform::Flipped) => Transform::_90, + (Transform::Flipped270, Transform::Flipped90) => Transform::_180, + (Transform::Flipped270, Transform::Flipped180) => Transform::_270, + (Transform::Flipped270, Transform::Flipped270) => Transform::Normal, + }; + + let dst_src_size = image_transform.transform_size(src.size); + let scale = dst_src_size.to_f64() / dst.size.to_f64(); + + let (src_x, src_y, dest_x, dest_y, width, height, transform) = + if image_transform != Transform::Normal || scale != Scale::from(1f64) { + let mut transform = pixman::Transform::identity(); + + // compensate for offset + transform = transform + .translate(-dst.loc.x, -dst.loc.y, false) + .ok_or(PixmanError::Unsupported)?; + + // scale to src image size + transform = transform + .scale(scale.x, scale.y, false) + .ok_or(PixmanError::Unsupported)?; + + let (cos, sin, x, y) = match image_transform { + Transform::Normal => (1, 0, 0, 0), + Transform::_90 => (0, -1, 0, src.size.h), + Transform::_180 => (-1, 0, src.size.w, src.size.h), + Transform::_270 => (0, 1, src.size.w, 0), + Transform::Flipped => (1, 0, src.size.w, 0), + Transform::Flipped90 => (0, -1, src.size.w, src.size.h), + Transform::Flipped180 => (-1, 0, 0, src.size.h), + Transform::Flipped270 => (0, 1, 0, 0), + }; + + // rotation + transform = transform + .rotate(cos, sin, false) + .ok_or(PixmanError::Unsupported)?; + + // flipped + if image_transform.flipped() { + transform = transform.scale(-1, 1, false).ok_or(PixmanError::Unsupported)?; + } + + // Compensate rotation and flipped + transform = transform.translate(x, y, false).ok_or(PixmanError::Unsupported)?; + + // crop src + transform = transform + .translate(src.loc.x, src.loc.y, false) + .ok_or(PixmanError::Unsupported)?; + + ( + 0, + 0, + 0, + 0, + target_image.width() as i32, + target_image.height() as i32, + Some(transform), + ) + } else { + ( + src.loc.x, src.loc.y, dst.loc.x, dst.loc.y, src.size.w, src.size.h, None, + ) + }; + + let mut clip_region = + pixman::Region32::init_rect(0, 0, self.output_size.w as u32, self.output_size.h as u32) + .intersect(&pixman::Region32::init_rect( + dst.loc.x, + dst.loc.y, + dst.size.w as u32, + dst.size.h as u32, + )); + + let damage_boxes = damage + .iter() + .copied() + .map(|mut rect| { + rect.loc += dst_loc; + + let rect = self.transform.transform_rect_in(rect, &self.size); + + let p1 = rect.loc; + let p2 = p1 + rect.size.to_point(); + pixman::Box32 { + x1: p1.x, + y1: p1.y, + x2: p2.x, + y2: p2.y, + } + }) + .collect::>(); + let damage_region = pixman::Region32::init_rects(&damage_boxes); + clip_region = clip_region.intersect(&damage_region); + + target_image.set_clip_region32(Some(&clip_region))?; + + src_image_accessor.with_image(|src_image| { + if let Some(transform) = transform { + src_image.set_transform(transform)?; + } else { + src_image.clear_transform()?; + } + + let filter = match self.renderer.upscale_filter { + TextureFilter::Linear => Filter::Bilinear, + TextureFilter::Nearest => Filter::Nearest, + }; + + src_image.set_filter(filter, &[])?; + src_image.set_repeat(Repeat::None); + + let has_alpha = DrmFourcc::try_from(src_image.format()) + .ok() + .map(has_alpha) + .unwrap_or(true); + + let op = if has_alpha { + Operation::Over + } else { + Operation::Src + }; + + let mask = if alpha != 1f32 { + Some(pixman::Solid::new([0f32, 0f32, 0f32, alpha]).map_err(|_| PixmanError::Unsupported)?) + } else { + None + }; + + target_image.composite32( + op, + src_image, + mask.as_deref(), + (src_x, src_y), + (0, 0), + (dest_x, dest_y), + (width, height), + ); + + src_image.clear_transform()?; + + Result::<(), PixmanError>::Ok(()) + })??; + + if self.renderer.debug_flags.contains(DebugFlags::TINT) { + target_image.composite32( + Operation::Over, + &self.renderer.tint, + None, + (0, 0), + (0, 0), + (0, 0), + (target_image.width() as i32, target_image.height() as i32), + ); + } + + target_image.set_clip_region32(None)?; + + Ok(()) + } + + fn transformation(&self) -> Transform { + self.transform + } + + #[profiling::function] + fn finish(mut self) -> Result { + self.finish_internal() + } +} + +impl<'frame> PixmanFrame<'frame> { + #[profiling::function] + fn finish_internal(&mut self) -> Result { + if self.finished.swap(true, Ordering::SeqCst) { + return Ok(SyncPoint::signaled()); + } + + if let PixmanTarget::Image { dmabuf, .. } = + self.renderer.target.as_ref().ok_or(PixmanError::NoTargetBound)? + { + dmabuf + .sync_plane( + 0, + DmabufSyncFlags::END | DmabufSyncFlags::READ | DmabufSyncFlags::WRITE, + ) + .map_err(PixmanError::Sync)?; + } + + Ok(SyncPoint::signaled()) + } +} + +impl<'frame> Drop for PixmanFrame<'frame> { + fn drop(&mut self) { + match self.finish_internal() { + Ok(sync) => { + sync.wait(); + } + Err(err) => { + warn!("Ignored error finishing PixmanFrame on drop: {}", err); + } + } + } +} + +/// A renderer utilizing pixman +#[derive(Debug)] +pub struct PixmanRenderer { + target: Option, + downscale_filter: TextureFilter, + upscale_filter: TextureFilter, + debug_flags: DebugFlags, + tint: pixman::Solid<'static>, + + // caches + buffers: Vec, + dmabuf_cache: Vec, +} + +impl PixmanRenderer { + /// Creates a new pixman renderer + pub fn new() -> Result { + let tint = pixman::Solid::new([0.0, 0.3, 0.0, 0.2]).map_err(|_| PixmanError::Unsupported)?; + Ok(Self { + target: None, + downscale_filter: TextureFilter::Linear, + upscale_filter: TextureFilter::Linear, + debug_flags: DebugFlags::empty(), + tint, + + buffers: Default::default(), + dmabuf_cache: Default::default(), + }) + } +} + +impl PixmanRenderer { + fn existing_dmabuf(&self, dmabuf: &Dmabuf) -> Option { + self.dmabuf_cache + .iter() + .find(|image| { + image + .0 + .dmabuf + .as_ref() + .and_then(|map| map.dmabuf.upgrade().map(|buf| &buf == dmabuf)) + .unwrap_or(false) + }) + .cloned() + } + + fn import_dmabuf( + &mut self, + dmabuf: &Dmabuf, + mode: DmabufMappingMode, + ) -> Result { + if dmabuf.num_planes() != 1 { + return Err(PixmanError::UnsupportedNumberOfPlanes); + } + + let size = dmabuf.size(); + let format = dmabuf.format(); + + if format.modifier != DrmModifier::Linear { + return Err(PixmanError::UnsupportedModifier(format.modifier)); + } + let format = pixman::FormatCode::try_from(format.code) + .map_err(|_| PixmanError::UnsupportedPixelFormat(format.code))?; + + let dmabuf_mapping = dmabuf.map_plane(0, mode)?; + let stride = dmabuf.strides().next().expect("already checked") as usize; + let expected_len = stride * size.h as usize; + + if dmabuf_mapping.length() < expected_len { + return Err(PixmanError::IncompleteBuffer { + expected: expected_len, + actual: dmabuf_mapping.length(), + }); + } + + let image: Image<'_, '_> = unsafe { + pixman::Image::from_raw_mut( + format, + size.w as usize, + size.h as usize, + dmabuf_mapping.ptr() as *mut u32, + stride, + false, + ) + } + .map_err(|_| PixmanError::ImportFailed)?; + + Ok(PixmanImage(Rc::new(PixmanImageInner { + #[cfg(feature = "wayland_frontend")] + buffer: None, + dmabuf: Some(PixmanDmabufMapping { + dmabuf: dmabuf.weak(), + _mapping: dmabuf_mapping, + }), + image, + _flipped: false, + }))) + } + + fn cleanup(&mut self) { + self.dmabuf_cache.retain(|image| { + image + .0 + .dmabuf + .as_ref() + .map(|map| map.dmabuf.upgrade().is_some()) + .unwrap_or(false) + }); + self.buffers.retain(|image| { + image + .0 + .dmabuf + .as_ref() + .map(|map| map.dmabuf.upgrade().is_some()) + .unwrap_or(false) + }); + } +} + +impl Renderer for PixmanRenderer { + type Error = PixmanError; + + type TextureId = PixmanTexture; + + type Frame<'frame> = PixmanFrame<'frame>; + + fn id(&self) -> usize { + 0 + } + + fn downscale_filter(&mut self, filter: TextureFilter) -> Result<(), Self::Error> { + self.downscale_filter = filter; + Ok(()) + } + + fn upscale_filter(&mut self, filter: TextureFilter) -> Result<(), Self::Error> { + self.upscale_filter = filter; + Ok(()) + } + + fn set_debug_flags(&mut self, flags: DebugFlags) { + self.debug_flags = flags; + } + + fn debug_flags(&self) -> DebugFlags { + self.debug_flags + } + + #[profiling::function] + fn render( + &mut self, + output_size: Size, + dst_transform: Transform, + ) -> Result, Self::Error> { + self.cleanup(); + + if let PixmanTarget::Image { dmabuf, .. } = self.target.as_ref().ok_or(PixmanError::NoTargetBound)? { + dmabuf + .sync_plane( + 0, + DmabufSyncFlags::START | DmabufSyncFlags::READ | DmabufSyncFlags::WRITE, + ) + .map_err(PixmanError::Sync)?; + } + Ok(PixmanFrame { + renderer: self, + + transform: dst_transform, + output_size, + size: dst_transform.transform_size(output_size), + + finished: AtomicBool::new(false), + }) + } +} + +impl ImportMem for PixmanRenderer { + #[profiling::function] + fn import_memory( + &mut self, + data: &[u8], + format: drm_fourcc::DrmFourcc, + size: Size, + flipped: bool, + ) -> Result<::TextureId, ::Error> { + let format = + pixman::FormatCode::try_from(format).map_err(|_| PixmanError::UnsupportedPixelFormat(format))?; + let image = pixman::Image::new(format, size.w as usize, size.h as usize, false) + .map_err(|_| PixmanError::Unsupported)?; + let expected_len = image.stride() * image.height(); + if data.len() < expected_len { + return Err(PixmanError::IncompleteBuffer { + expected: expected_len, + actual: data.len(), + }); + } + unsafe { + std::ptr::copy_nonoverlapping(data.as_ptr(), image.data() as *mut u8, expected_len); + } + Ok(PixmanTexture(PixmanImage(Rc::new(PixmanImageInner { + #[cfg(feature = "wayland_frontend")] + buffer: None, + dmabuf: None, + image, + _flipped: flipped, + })))) + } + + #[profiling::function] + fn update_memory( + &mut self, + texture: &::TextureId, + data: &[u8], + region: Rectangle, + ) -> Result<(), ::Error> { + #[cfg(feature = "wayland_frontend")] + if texture.0 .0.buffer.is_some() { + return Err(PixmanError::ImportFailed); + } + + if texture.0 .0.dmabuf.is_some() { + return Err(PixmanError::ImportFailed); + } + + let stride = texture.0 .0.image.stride(); + let expected_len = stride * texture.0 .0.image.height(); + + if data.len() < expected_len { + return Err(PixmanError::IncompleteBuffer { + expected: expected_len, + actual: data.len(), + }); + } + + let src_image = unsafe { + // SAFETY: As we are never going to write to this image + // it is safe to cast the passed slice to a mut pointer + pixman::Image::from_raw_mut( + texture.0 .0.image.format(), + texture.0 .0.image.width(), + texture.0 .0.image.height(), + data.as_ptr() as *mut _, + stride, + false, + ) + } + .map_err(|_| PixmanError::ImportFailed)?; + + texture.0 .0.image.composite32( + Operation::Src, + &src_image, + None, + region.loc.into(), + (0, 0), + region.loc.into(), + region.size.into(), + ); + + Ok(()) + } + + fn mem_formats(&self) -> Box> { + Box::new(SUPPORTED_FORMATS.iter().copied()) + } +} + +/// Texture mapping of a pixman texture +#[derive(Debug)] +pub struct PixmanMapping(pixman::Image<'static, 'static>); + +impl Texture for PixmanMapping { + fn width(&self) -> u32 { + self.0.width() as u32 + } + + fn height(&self) -> u32 { + self.0.height() as u32 + } + + fn format(&self) -> Option { + DrmFourcc::try_from(self.0.format()).ok() + } +} + +impl TextureMapping for PixmanMapping { + fn flipped(&self) -> bool { + false + } +} + +impl ExportMem for PixmanRenderer { + type TextureMapping = PixmanMapping; + + #[profiling::function] + fn copy_framebuffer( + &mut self, + region: Rectangle, + format: DrmFourcc, + ) -> Result::Error> { + let format_code = + pixman::FormatCode::try_from(format).map_err(|_| PixmanError::UnsupportedPixelFormat(format))?; + let copy_image = + pixman::Image::new(format_code, region.size.w as usize, region.size.h as usize, false) + .map_err(|_| PixmanError::Unsupported)?; + + if let Some(target) = self.target.as_ref() { + if let PixmanTarget::Image { dmabuf, .. } = target { + dmabuf.sync_plane(0, DmabufSyncFlags::START | DmabufSyncFlags::READ)?; + }; + copy_image.composite32( + Operation::Src, + target.image(), + None, + region.loc.into(), + (0, 0), + (0, 0), + region.size.into(), + ); + if let PixmanTarget::Image { dmabuf, .. } = target { + dmabuf.sync_plane(0, DmabufSyncFlags::END | DmabufSyncFlags::READ)?; + }; + } + + Ok(PixmanMapping(copy_image)) + } + + #[profiling::function] + fn copy_texture( + &mut self, + texture: &Self::TextureId, + region: Rectangle, + format: DrmFourcc, + ) -> Result { + let accessor = texture.accessor()?; + let format_code = + pixman::FormatCode::try_from(format).map_err(|_| PixmanError::UnsupportedPixelFormat(format))?; + let copy_image = + pixman::Image::new(format_code, region.size.w as usize, region.size.h as usize, false) + .map_err(|_| PixmanError::Unsupported)?; + accessor.with_image(|image| { + copy_image.composite32( + Operation::Src, + image, + None, + region.loc.into(), + (0, 0), + (0, 0), + region.size.into(), + ); + })?; + Ok(PixmanMapping(copy_image)) + } + + #[profiling::function] + fn map_texture<'a>( + &mut self, + texture_mapping: &'a Self::TextureMapping, + ) -> Result<&'a [u8], ::Error> { + Ok(unsafe { + std::slice::from_raw_parts( + texture_mapping.0.data() as *const u8, + texture_mapping.0.stride() * texture_mapping.0.height(), + ) + }) + } +} + +#[cfg(all( + feature = "wayland_frontend", + feature = "backend_egl", + feature = "use_system_lib" +))] +impl ImportEgl for PixmanRenderer { + fn bind_wl_display( + &mut self, + _display: &wayland_server::DisplayHandle, + ) -> Result<(), crate::backend::egl::Error> { + Err(crate::backend::egl::Error::NoEGLDisplayBound) + } + + fn unbind_wl_display(&mut self) {} + + fn egl_reader(&self) -> Option<&crate::backend::egl::display::EGLBufferReader> { + None + } + + fn import_egl_buffer( + &mut self, + _buffer: &wl_buffer::WlBuffer, + _surface: Option<&SurfaceData>, + _damage: &[Rectangle], + ) -> Result<::TextureId, ::Error> { + Err(PixmanError::Unsupported) + } +} + +#[cfg(feature = "wayland_frontend")] +impl ImportMemWl for PixmanRenderer { + #[profiling::function] + fn import_shm_buffer( + &mut self, + buffer: &wl_buffer::WlBuffer, + _surface: Option<&SurfaceData>, + _damage: &[Rectangle], + ) -> Result { + let image = shm::with_buffer_contents(buffer, |ptr, len, data| { + let format = FormatCode::try_from( + shm::shm_format_to_fourcc(data.format) + .ok_or(PixmanError::UnsupportedWlPixelFormat(data.format))?, + ) + .map_err(|_| PixmanError::UnsupportedWlPixelFormat(data.format))?; + + let expected_len = (data.offset + data.stride * data.height) as usize; + if len < expected_len { + return Err(PixmanError::IncompleteBuffer { + expected: expected_len, + actual: len, + }); + } + + let image = unsafe { + // SAFETY: We guarantee that this image is only used for reading, + // so it is safe to cast the ptr to *mut + Image::from_raw_mut( + format, + data.width as usize, + data.height as usize, + ptr.offset(data.offset as isize) as *mut u32, + data.stride as usize, + false, + ) + } + .map_err(|_| PixmanError::ImportFailed)?; + std::result::Result::<_, PixmanError>::Ok(image) + })??; + Ok(PixmanTexture(PixmanImage(Rc::new(PixmanImageInner { + buffer: Some(buffer.downgrade()), + dmabuf: None, + image, + _flipped: false, + })))) + } +} + +impl ImportDma for PixmanRenderer { + #[profiling::function] + fn import_dmabuf( + &mut self, + dmabuf: &Dmabuf, + _damage: Option<&[Rectangle]>, + ) -> Result<::TextureId, ::Error> { + if let Some(image) = self.existing_dmabuf(dmabuf) { + return Ok(PixmanTexture(image)); + }; + + let image = self.import_dmabuf(dmabuf, DmabufMappingMode::READ)?; + self.dmabuf_cache.push(image.clone()); + Ok(PixmanTexture(image)) + } + + fn dmabuf_formats(&self) -> Box> { + lazy_static::lazy_static! { + static ref DMABUF_FORMATS: Vec = { + SUPPORTED_FORMATS.iter().copied().map(|code| DrmFormat { + code, + modifier: drm_fourcc::DrmModifier::Linear, + }).collect() + }; + } + Box::new(DMABUF_FORMATS.iter().copied()) + } +} + +#[cfg(feature = "wayland_frontend")] +impl ImportDmaWl for PixmanRenderer {} + +impl Unbind for PixmanRenderer { + #[profiling::function] + fn unbind(&mut self) -> Result<(), ::Error> { + self.target = None; + Ok(()) + } +} + +impl Bind for PixmanRenderer { + #[profiling::function] + fn bind(&mut self, target: Dmabuf) -> Result<(), ::Error> { + let existing_image = self + .buffers + .iter() + .find(|image| { + image + .0 + .dmabuf + .as_ref() + .and_then(|map| map.dmabuf.upgrade().map(|buf| buf == target)) + .unwrap_or(false) + }) + .cloned(); + + let image = if let Some(image) = existing_image { + image + } else { + let image = self.import_dmabuf(&target, DmabufMappingMode::READ | DmabufMappingMode::WRITE)?; + self.buffers.push(image.clone()); + image + }; + + self.target = Some(PixmanTarget::Image { + dmabuf: target, + image, + }); + Ok(()) + } + + fn supported_formats(&self) -> Option> { + lazy_static::lazy_static! { + static ref DMABUF_FORMATS: HashSet = { + SUPPORTED_FORMATS.iter().copied().map(|code| DrmFormat { + code, + modifier: DrmModifier::Linear, + }).collect() + }; + } + Some(DMABUF_FORMATS.clone()) + } +} + +impl Offscreen for PixmanRenderer { + #[profiling::function] + fn create_buffer( + &mut self, + format: DrmFourcc, + size: Size, + ) -> Result::Error> { + let format_code = + FormatCode::try_from(format).map_err(|_| PixmanError::UnsupportedPixelFormat(format))?; + let image = pixman::Image::new(format_code, size.w as usize, size.h as usize, true) + .map_err(|_| PixmanError::Unsupported)?; + Ok(PixmanRenderBuffer::from(image)) + } +} + +impl Bind for PixmanRenderer { + #[profiling::function] + fn bind(&mut self, target: PixmanRenderBuffer) -> Result<(), ::Error> { + self.target = Some(PixmanTarget::RenderBuffer(target)); + Ok(()) + } + + fn supported_formats(&self) -> Option> { + lazy_static::lazy_static! { + static ref RENDER_BUFFER_FORMATS: HashSet = { + SUPPORTED_FORMATS.iter().copied().map(|code| DrmFormat { + code, + modifier: DrmModifier::Linear, + }).collect() + }; + } + Some(RENDER_BUFFER_FORMATS.clone()) + } +} diff --git a/src/reexports.rs b/src/reexports.rs index c48ce42e180f..eaa1ba1cea40 100644 --- a/src/reexports.rs +++ b/src/reexports.rs @@ -11,6 +11,8 @@ pub use drm; pub use gbm; #[cfg(feature = "backend_libinput")] pub use input; +#[cfg(feature = "renderer_pixman")] +pub use pixman; pub use rustix; #[cfg(feature = "backend_udev")] pub use udev; From 7ad8bea8b3859f3e02584ec05ad279160137fac2 Mon Sep 17 00:00:00 2001 From: Christian Meissl Date: Sat, 25 Nov 2023 20:30:54 +0100 Subject: [PATCH 7/8] anvil: include flipped in rotation tests --- anvil/src/input_handler.rs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/anvil/src/input_handler.rs b/anvil/src/input_handler.rs index d34e422ec671..ffc18244220d 100644 --- a/anvil/src/input_handler.rs +++ b/anvil/src/input_handler.rs @@ -477,8 +477,11 @@ impl AnvilState { Transform::Normal => Transform::_90, Transform::_90 => Transform::_180, Transform::_180 => Transform::_270, - Transform::_270 => Transform::Normal, - _ => Transform::Normal, + Transform::_270 => Transform::Flipped, + Transform::Flipped => Transform::Flipped90, + Transform::Flipped90 => Transform::Flipped180, + Transform::Flipped180 => Transform::Flipped270, + Transform::Flipped270 => Transform::Normal, }; output.change_current_state(None, Some(new_transform), None, None); crate::shell::fixup_positions(&mut self.space, self.pointer.current_location()); @@ -670,8 +673,11 @@ impl AnvilState { Transform::Normal => Transform::_90, Transform::_90 => Transform::_180, Transform::_180 => Transform::_270, - Transform::_270 => Transform::Normal, - _ => Transform::Normal, + Transform::_270 => Transform::Flipped, + Transform::Flipped => Transform::Flipped90, + Transform::Flipped90 => Transform::Flipped180, + Transform::Flipped180 => Transform::Flipped270, + Transform::Flipped270 => Transform::Normal, }; output.change_current_state(None, Some(new_transform), None, None); crate::shell::fixup_positions(&mut self.space, self.pointer.current_location()); From 6ce537c683b2a8229db3a2a2ef486a3c1af3ae79 Mon Sep 17 00:00:00 2001 From: Christian Meissl Date: Tue, 26 Dec 2023 19:28:07 +0100 Subject: [PATCH 8/8] drm: warn once about the use of legacy fbadd --- src/backend/drm/dumb.rs | 4 +++- src/backend/drm/gbm.rs | 4 +++- src/backend/drm/mod.rs | 8 ++++++++ 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/backend/drm/dumb.rs b/src/backend/drm/dumb.rs index 2235f4d5b371..201ca05ce6e0 100644 --- a/src/backend/drm/dumb.rs +++ b/src/backend/drm/dumb.rs @@ -21,7 +21,7 @@ use crate::{ utils::{Buffer as BufferCoords, Size}, }; -use super::{error::AccessError, Framebuffer}; +use super::{error::AccessError, warn_legacy_fb_export, Framebuffer}; /// A GBM backed framebuffer #[derive(Debug)] @@ -157,6 +157,8 @@ pub fn framebuffer_from_dumb_buffer( let (fb, format) = match ret { Ok(fb) => fb, Err(source) => { + warn_legacy_fb_export(); + let fourcc = format.code; let (depth, bpp) = get_depth(fourcc) .and_then(|d| get_bpp(fourcc).map(|b| (d, b))) diff --git a/src/backend/drm/gbm.rs b/src/backend/drm/gbm.rs index a7f96a2de061..5484296ada1f 100644 --- a/src/backend/drm/gbm.rs +++ b/src/backend/drm/gbm.rs @@ -26,7 +26,7 @@ use crate::backend::{ }; use crate::utils::DevPath; -use super::{error::AccessError, Framebuffer}; +use super::{error::AccessError, warn_legacy_fb_export, Framebuffer}; /// A GBM backed framebuffer #[derive(Debug)] @@ -315,6 +315,8 @@ where let (fb, format) = match ret { Ok(fb) => fb, Err(source) => { + warn_legacy_fb_export(); + // We only support this as a fallback of last resort like xf86-video-modesetting does. if bo.plane_count().unwrap() > 1 { return Err(AccessError { diff --git a/src/backend/drm/mod.rs b/src/backend/drm/mod.rs index 80b9c7dde42d..74809c6e8e73 100644 --- a/src/backend/drm/mod.rs +++ b/src/backend/drm/mod.rs @@ -83,6 +83,7 @@ pub mod node; mod surface; use std::collections::HashSet; +use std::sync::Once; use crate::utils::DevPath; pub use device::{ @@ -105,6 +106,13 @@ use tracing::trace; use self::error::AccessError; +fn warn_legacy_fb_export() { + static WARN_LEGACY_FB_EXPORT: Once = Once::new(); + WARN_LEGACY_FB_EXPORT.call_once(|| { + tracing::warn!("using legacy fbadd"); + }); +} + /// Common framebuffer operations pub trait Framebuffer: AsRef { /// Retrieve the format of the framebuffer