From 243afb10306b180d7041112d39a12d31afc5cf75 Mon Sep 17 00:00:00 2001 From: Victor Brekenfeld Date: Sun, 25 Apr 2021 23:43:28 +0200 Subject: [PATCH] drm: Introduce RenderSurface for compatibility/ease-of-use --- src/backend/drm/mod.rs | 6 +- src/backend/drm/render.rs | 484 ++++++++++++++++++++++++++++++++++++-- 2 files changed, 472 insertions(+), 18 deletions(-) diff --git a/src/backend/drm/mod.rs b/src/backend/drm/mod.rs index 370295f..c8b3c31 100644 --- a/src/backend/drm/mod.rs +++ b/src/backend/drm/mod.rs @@ -2,8 +2,10 @@ pub(crate) mod device; pub(self) mod surface; pub(self) mod error; pub(self) mod session; +mod render; -pub use device::{DrmDevice, DrmSource, device_bind}; +pub use device::{DrmDevice, DrmSource, DeviceHandler, device_bind, Planes}; pub use surface::DrmSurface; pub use error::Error as DrmError; -pub use session::DrmDeviceObserver; \ No newline at end of file +pub use session::DrmDeviceObserver; +pub use render::{DrmRenderSurface, Error as DrmRenderError}; \ No newline at end of file diff --git a/src/backend/drm/render.rs b/src/backend/drm/render.rs index 9a77d40..954c877 100644 --- a/src/backend/drm/render.rs +++ b/src/backend/drm/render.rs @@ -1,27 +1,479 @@ +use std::os::unix::io::AsRawFd; +use std::collections::HashSet; +use std::convert::TryInto; +use std::sync::Arc; + +use cgmath::Matrix3; +use drm::buffer::PlanarBuffer; +use drm::control::{Device, Mode, crtc, connector, framebuffer, plane}; +use gbm::{Device as GbmDevice, BufferObject, BufferObjectFlags}; +#[cfg(feature = "wayland_frontend")] +use wayland_server::protocol::{wl_shm, wl_buffer}; + +use crate::backend::SwapBuffersError; +use crate::backend::allocator::{Allocator, Format, Fourcc, Modifier, Swapchain, SwapchainError, Slot, Buffer, dmabuf::Dmabuf}; +use crate::backend::renderer::{Renderer, Bind, Transform, Texture}; +use super::{DrmSurface, DrmError, device::DevPath, surface::DrmSurfaceInternal}; + pub struct DrmRenderSurface< D: AsRawFd + 'static, - A: Allocator, - S: Buffer, - D: Buffer + TryFrom, - E: Error, - T, F, - R: Renderer, + A: Allocator, + R: Bind, + B: Buffer + TryInto, > { - drm: DrmSurface, - allocator: A, + format: Fourcc, + buffers: Buffers, + current_buffer: Option>>>, + swapchain: Swapchain>, Dmabuf>, renderer: R, - swapchain: Swapchain, + drm: Arc>, } -impl DrmRenderSurface +impl DrmRenderSurface where D: AsRawFd + 'static, - A: Allocator, - S: Buffer, - D: Buffer + TryFrom, - E: Error, - T, F, - R: Renderer, + A: Allocator, + B: Buffer + TryInto, + R: Bind + Renderer, + E1: std::error::Error + 'static, + E2: std::error::Error + 'static, + E3: std::error::Error + 'static, { + pub fn new>>(drm: DrmSurface, allocator: A, renderer: R, log: L) -> Result, Error> + { + // we cannot simply pick the first supported format of the intersection of *all* formats, because: + // - we do not want something like Abgr4444, which looses color information + // - some formats might perform terribly + // - we might need some work-arounds, if one supports modifiers, but the other does not + // + // So lets just pick `ARGB8888` for now, it is widely supported. + // Once we have proper color management and possibly HDR support, + // we need to have a more sophisticated picker. + // (Or maybe just pick ARGB2101010, if available, we will see.) + let code = Fourcc::Argb8888; + let logger = crate::slog_or_fallback(log).new(o!("backend" => "drm_render")); + + // select a format + let plane_formats = drm.supported_formats().iter().filter(|fmt| fmt.code == code).cloned().collect::>(); + let mut renderer_formats = Bind::::supported_formats(&renderer).expect("Dmabuf renderer without formats") + .iter().filter(|fmt| fmt.code == code).cloned().collect::>(); + + trace!(logger, "Remaining plane formats: {:?}", plane_formats); + trace!(logger, "Remaining renderer formats: {:?}", renderer_formats); + debug!(logger, "Remaining intersected formats: {:?}", plane_formats.intersection(&renderer_formats).collect::>()); + + if plane_formats.is_empty() { + return Err(Error::NoSupportedPlaneFormat); + } else if renderer_formats.is_empty() { + return Err(Error::NoSupportedRendererFormat); + } + + let formats = { + // Special case: if a format supports explicit LINEAR (but no implicit Modifiers) + // and the other doesn't support any modifier, force LINEAR. This will force the allocator to + // create a buffer with a LINEAR layout instead of an implicit modifier. + if + (plane_formats.len() == 1 && + plane_formats.iter().next().unwrap().modifier == Modifier::Invalid + && renderer_formats.iter().all(|x| x.modifier != Modifier::Invalid) + && renderer_formats.iter().any(|x| x.modifier == Modifier::Linear) + ) || (renderer_formats.len() == 1 && + renderer_formats.iter().next().unwrap().modifier == Modifier::Invalid + && plane_formats.iter().all(|x| x.modifier != Modifier::Invalid) + && plane_formats.iter().any(|x| x.modifier == Modifier::Linear) + ) { + vec![Format { + code, + modifier: Modifier::Linear, + }] + } else { + plane_formats.intersection(&renderer_formats).cloned().collect::>() + } + }; + debug!(logger, "Testing Formats: {:?}", formats); + + // Test explicit formats first + let drm = Arc::new(drm); + let iter = formats.iter().filter(|x| x.modifier != Modifier::Invalid && x.modifier != Modifier::Linear) + .chain(formats.iter().find(|x| x.modifier == Modifier::Linear)) + .chain(formats.iter().find(|x| x.modifier == Modifier::Invalid)).cloned(); + + DrmRenderSurface::new_internal(drm, allocator, renderer, iter, logger) + } + + fn new_internal(drm: Arc>, allocator: A, mut renderer: R, mut formats: impl Iterator, logger: ::slog::Logger) -> Result, Error> + { + let format = formats.next().ok_or(Error::NoSupportedPlaneFormat)?; + let mode = drm.pending_mode(); + + let gbm = unsafe { GbmDevice::new_from_fd(drm.as_raw_fd())? }; + let mut swapchain = Swapchain::new(allocator, mode.size().0 as u32, mode.size().1 as u32, format); + + // Test format + let buffer = swapchain.acquire()?.unwrap(); + + { + let dmabuf: Dmabuf = (*buffer).clone(); + match renderer.bind(dmabuf).map_err(Error::::RenderError) + .and_then(|_| renderer.begin(mode.size().0 as u32, mode.size().1 as u32, Transform::Normal).map_err(Error::RenderError)) + .and_then(|_| renderer.clear([0.0, 0.0, 0.0, 1.0]).map_err(Error::RenderError)) + .and_then(|_| renderer.finish().map_err(|_| Error::InitialRenderingError)) + .and_then(|_| renderer.unbind().map_err(Error::RenderError)) + { + Ok(_) => {}, + Err(err) => { + warn!(logger, "Rendering failed with format {:?}: {}", format, err); + return DrmRenderSurface::new_internal(drm, swapchain.allocator, renderer, formats, logger); + } + } + } + + let bo = import_dmabuf(&drm, &gbm, &*buffer)?; + let fb = bo.userdata().unwrap().unwrap().fb; + buffer.set_userdata(bo); + + match drm.test_buffer(fb, &mode, true) + { + Ok(_) => { + debug!(logger, "Success, choosen format: {:?}", format); + let buffers = Buffers::new(drm.clone(), gbm, buffer); + Ok(DrmRenderSurface { + drm, + format: format.code, + renderer, + swapchain, + buffers, + current_buffer: None, + }) + }, + Err(err) => { + warn!(logger, "Mode-setting failed with buffer format {:?}: {}", format, err); + DrmRenderSurface::new_internal(drm, swapchain.allocator, renderer, formats, logger) + } + } + } + pub fn queue_frame(&mut self) -> Result<(), Error> { + let mode = self.drm.pending_mode(); + let (width, height) = (mode.size().0 as u32, mode.size().1 as u32); + self.begin(width, height, Transform::Flipped180/* TODO */) + } + + pub fn drop_frame(&mut self) -> Result<(), SwapBuffersError> { + if self.current_buffer.is_none() { + return Ok(()); + } + + // finish the renderer in case it needs it + let result = self.renderer.finish(); + // but do not queue the buffer, drop it in any case + let _ = self.current_buffer.take(); + result + } + + pub fn frame_submitted(&mut self) -> Result<(), Error> { + self.buffers.submitted() + } + + pub fn crtc(&self) -> crtc::Handle { + self.drm.crtc() + } + + pub fn plane(&self) -> plane::Handle { + self.drm.plane() + } + + pub fn current_connectors(&self) -> impl IntoIterator { + self.drm.current_connectors() + } + + pub fn pending_connectors(&self) -> impl IntoIterator { + self.drm.pending_connectors() + } + + pub fn add_connector(&self, connector: connector::Handle) -> Result<(), Error> { + self.drm.add_connector(connector).map_err(Error::DrmError) + } + + pub fn remove_connector(&self, connector: connector::Handle) -> Result<(), Error> { + self.drm.remove_connector(connector).map_err(Error::DrmError) + } + + pub fn set_connectors(&self, connectors: &[connector::Handle]) -> Result<(), Error> { + self.drm.set_connectors(connectors).map_err(Error::DrmError) + } + + pub fn current_mode(&self) -> Mode { + self.drm.current_mode() + } + + pub fn pending_mode(&self) -> Mode { + self.drm.pending_mode() + } + + pub fn use_mode(&self, mode: Mode) -> Result<(), Error> { + self.drm.use_mode(mode).map_err(Error::DrmError) + } +} + + +impl Renderer for DrmRenderSurface +where + D: AsRawFd + 'static, + A: Allocator, + B: Buffer + TryInto, + R: Bind + Renderer, + T: Texture, + E1: std::error::Error + 'static, + E2: std::error::Error + 'static, + E3: std::error::Error + 'static, +{ + type Error = Error; + type Texture = T; + + #[cfg(feature = "wayland_frontend")] + fn shm_formats(&self) -> &[wl_shm::Format] { + self.renderer.shm_formats() + } + + #[cfg(feature = "wayland_frontend")] + fn import_shm(&mut self, buffer: &wl_buffer::WlBuffer) -> Result { + self.renderer.import_shm(buffer).map_err(Error::RenderError) + } + + fn begin(&mut self, width: u32, height: u32, transform: Transform) -> Result<(), Error> { + if self.current_buffer.is_some() { + return Ok(()); + } + + let slot = self.swapchain.acquire()?.ok_or(Error::NoFreeSlotsError)?; + self.renderer.bind((*slot).clone()).map_err(Error::RenderError)?; + self.current_buffer = Some(slot); + self.renderer.begin(width, height, transform).map_err(Error::RenderError) + } + + fn clear(&mut self, color: [f32; 4]) -> Result<(), Self::Error> { + self.renderer.clear(color).map_err(Error::RenderError) + } + + fn render_texture(&mut self, texture: &Self::Texture, matrix: Matrix3, alpha: f32) -> Result<(), Self::Error> { + self.renderer.render_texture(texture, matrix, alpha).map_err(Error::RenderError) + } + + fn finish(&mut self) -> Result<(), SwapBuffersError> { + if self.current_buffer.is_none() { + return Err(SwapBuffersError::AlreadySwapped); + } + + let result = self.renderer.finish(); + if result.is_ok() { + match self.buffers.queue::(self.current_buffer.take().unwrap()) { + Ok(()) => {} + Err(Error::DrmError(drm)) => return Err(drm.into()), + Err(Error::GbmError(err)) => return Err(SwapBuffersError::ContextLost(Box::new(err))), + _ => unreachable!(), + } + } + result + } +} + +struct FbHandle { + drm: Arc>, + fb: framebuffer::Handle, +} + +impl Drop for FbHandle { + fn drop(&mut self) { + let _ = self.drm.destroy_framebuffer(self.fb); + } +} + +struct Buffers { + gbm: GbmDevice, + drm: Arc>, + _current_fb: Slot>>, + pending_fb: Option>>>, + queued_fb: Option>>>, +} + +impl Buffers +where + D: AsRawFd + 'static, +{ + pub fn new(drm: Arc>, gbm: GbmDevice, slot: Slot>>) -> Buffers { + Buffers { + drm, + gbm, + _current_fb: slot, + pending_fb: None, + queued_fb: None, + } + } + + pub fn queue(&mut self, slot: Slot>>) -> Result<(), Error> + where + E1: std::error::Error + 'static, + E2: std::error::Error + 'static, + E3: std::error::Error + 'static, + { + if slot.userdata().is_none() { + let bo = import_dmabuf(&self.drm, &self.gbm, &*slot)?; + slot.set_userdata(bo); + } + + self.queued_fb = Some(slot); + if self.pending_fb.is_none() { + self.submit() + } else { + Ok(()) + } + } + + pub fn submitted(&mut self) -> Result<(), Error> + where + E1: std::error::Error + 'static, + E2: std::error::Error + 'static, + E3: std::error::Error + 'static, + { + if self.pending_fb.is_none() { + return Ok(()); + } + self._current_fb = self.pending_fb.take().unwrap(); + if self.queued_fb.is_some() { + self.submit() + } else { + Ok(()) + } + } + + fn submit(&mut self) -> Result<(), Error> + where + E1: std::error::Error + 'static, + E2: std::error::Error + 'static, + E3: std::error::Error + 'static, + { + // yes it does not look like it, but both of these lines should be safe in all cases. + let slot = self.queued_fb.take().unwrap(); + let fb = slot.userdata().as_ref().unwrap().userdata().unwrap().unwrap().fb; + + let flip = if self.drm.commit_pending() { + self.drm.commit(fb, true) + } else { + self.drm.page_flip(fb, true) + }; + if flip.is_ok() { + self.pending_fb = Some(slot); + } + flip.map_err(Error::DrmError) + } +} + +fn import_dmabuf(drm: &Arc>, gbm: &GbmDevice, buffer: &Dmabuf) -> Result>, Error> +where + A: AsRawFd + 'static, + E1: std::error::Error + 'static, + E2: std::error::Error + 'static, + E3: std::error::Error + 'static, +{ + // TODO check userdata and return early + let mut bo = buffer.import(&gbm, BufferObjectFlags::SCANOUT)?; + let modifier = match bo.modifier().unwrap() { + Modifier::Invalid => None, + x => Some(x), + }; + + let logger = match &*(*drm).internal { + DrmSurfaceInternal::Atomic(surf) => surf.logger.clone(), + DrmSurfaceInternal::Legacy(surf) => surf.logger.clone(), + }; + + let fb = match + if modifier.is_some() { + let num = bo.plane_count().unwrap(); + let modifiers = [ + modifier, + if num > 1 { modifier } else { None }, + if num > 2 { modifier } else { None }, + if num > 3 { modifier } else { None }, + ]; + drm.add_planar_framebuffer(&bo, &modifiers, drm_ffi::DRM_MODE_FB_MODIFIERS) + } else { + drm.add_planar_framebuffer(&bo, &[None, None, None, None], 0) + } + { + Ok(fb) => fb, + Err(source) => { + // We only support this as a fallback of last resort for ARGB8888 visuals, + // like xf86-video-modesetting does. + if drm::buffer::Buffer::format(&bo) != Fourcc::Argb8888 + || bo.handles()[1].is_some() { + return Err(Error::DrmError(DrmError::Access { + errmsg: "Failed to add framebuffer", + dev: drm.dev_path(), + source, + })); + } + debug!(logger, "Failed to add framebuffer, trying legacy method"); + drm.add_framebuffer(&bo, 32, 32).map_err(|source| DrmError::Access { + errmsg: "Failed to add framebuffer", + dev: drm.dev_path(), + source, + })? + } + }; + bo.set_userdata(FbHandle { + drm: drm.clone(), + fb, + }).unwrap(); + + Ok(bo) +} + +#[derive(Debug, thiserror::Error)] +pub enum Error +where + E1: std::error::Error + 'static, + E2: std::error::Error + 'static, + E3: std::error::Error + 'static, +{ + #[error("No supported plane buffer format found")] + NoSupportedPlaneFormat, + #[error("No supported renderer buffer format found")] + NoSupportedRendererFormat, + #[error("Supported plane and renderer buffer formats are incompatible")] + FormatsNotCompatible, + #[error("Failed to allocate a new buffer")] + NoFreeSlotsError, + #[error("Failed to render test frame")] + InitialRenderingError, + #[error("The underlying drm surface encounted an error: {0}")] + DrmError(#[from] DrmError), + #[error("The underlying gbm device encounted an error: {0}")] + GbmError(#[from] std::io::Error), + #[error("The swapchain encounted an error: {0}")] + SwapchainError(#[from] SwapchainError), + #[error("The renderer encounted an error: {0}")] + RenderError(#[source] E3) +} + +impl< + E1: std::error::Error + 'static, + E2: std::error::Error + 'static, + E3: std::error::Error + Into + 'static, +> From> for SwapBuffersError { + fn from(err: Error) -> SwapBuffersError { + match err { + x @ Error::NoSupportedPlaneFormat + | x @ Error::NoSupportedRendererFormat + | x @ Error::FormatsNotCompatible + | x @ Error::InitialRenderingError + => SwapBuffersError::ContextLost(Box::new(x)), + x @ Error::NoFreeSlotsError => SwapBuffersError::TemporaryFailure(Box::new(x)), + Error::DrmError(err) => err.into(), + Error::GbmError(err) => SwapBuffersError::ContextLost(Box::new(err)), + Error::SwapchainError(err) => SwapBuffersError::ContextLost(Box::new(err)), + Error::RenderError(err) => err.into(), + } + } } \ No newline at end of file