From b10a37349d84a57f2007c95c02319b3a33541ed6 Mon Sep 17 00:00:00 2001 From: Christian Meissl Date: Thu, 24 Oct 2024 12:33:37 +0200 Subject: [PATCH] wip --- anvil/src/drawing.rs | 7 +- anvil/src/render.rs | 8 +- anvil/src/udev.rs | 519 +++---- src/backend/allocator/gbm.rs | 2 + src/backend/drm/compositor/frame_result.rs | 452 ++++++ src/backend/drm/compositor/mod.rs | 1352 +++++++---------- .../drm/{compositor => exporter}/dumb.rs | 1 + .../drm/{compositor => exporter}/gbm.rs | 9 + src/backend/drm/exporter/mod.rs | 106 ++ src/backend/drm/format_selection.rs | 40 + src/backend/drm/mod.rs | 5 +- src/backend/drm/output.rs | 458 ++++++ src/backend/renderer/pixman/mod.rs | 2 + 13 files changed, 1798 insertions(+), 1163 deletions(-) create mode 100644 src/backend/drm/compositor/frame_result.rs rename src/backend/drm/{compositor => exporter}/dumb.rs (97%) rename src/backend/drm/{compositor => exporter}/gbm.rs (87%) create mode 100644 src/backend/drm/exporter/mod.rs create mode 100644 src/backend/drm/format_selection.rs create mode 100644 src/backend/drm/output.rs diff --git a/anvil/src/drawing.rs b/anvil/src/drawing.rs index 0fa62201ed02..0dfe1edc725b 100644 --- a/anvil/src/drawing.rs +++ b/anvil/src/drawing.rs @@ -6,8 +6,7 @@ use smithay::{ memory::{MemoryRenderBuffer, MemoryRenderBufferRenderElement}, surface::WaylandSurfaceRenderElement, AsRenderElements, Kind, - }, - ImportAll, ImportMem, Renderer, Texture, + }, Color32F, ImportAll, ImportMem, Renderer, Texture }, input::pointer::CursorImageStatus, render_elements, @@ -23,8 +22,8 @@ use smithay::{ utils::{Buffer, Logical, Rectangle, Size, Transform}, }; -pub static CLEAR_COLOR: [f32; 4] = [0.8, 0.8, 0.9, 1.0]; -pub static CLEAR_COLOR_FULLSCREEN: [f32; 4] = [0.0, 0.0, 0.0, 0.0]; +pub static CLEAR_COLOR: Color32F = Color32F::new(0.8, 0.8, 0.9, 1.0); +pub static CLEAR_COLOR_FULLSCREEN: Color32F = Color32F::new(0.0, 0.0, 0.0, 0.0); pub struct PointerElement { buffer: Option, diff --git a/anvil/src/render.rs b/anvil/src/render.rs index 6346799a71be..1d70e6525f5c 100644 --- a/anvil/src/render.rs +++ b/anvil/src/render.rs @@ -1,15 +1,13 @@ use smithay::{ backend::renderer::{ - damage::{Error as OutputDamageTrackerError, OutputDamageTracker, RenderOutputResult}, - element::{ + damage::{Error as OutputDamageTrackerError, OutputDamageTracker, RenderOutputResult}, element::{ surface::WaylandSurfaceRenderElement, utils::{ ConstrainAlign, ConstrainScaleBehavior, CropRenderElement, RelocateRenderElement, RescaleRenderElement, }, AsRenderElements, RenderElement, Wrap, - }, - ImportAll, ImportMem, Renderer, + }, Color32F, ImportAll, ImportMem, Renderer }, desktop::space::{ constrain_space_element, ConstrainBehavior, ConstrainReference, Space, SpaceRenderElements, @@ -142,7 +140,7 @@ pub fn output_elements( custom_elements: impl IntoIterator>, renderer: &mut R, show_window_preview: bool, -) -> (Vec>>, [f32; 4]) +) -> (Vec>>, Color32F) where R: Renderer + ImportAll + ImportMem, R::TextureId: Clone + 'static, diff --git a/anvil/src/udev.rs b/anvil/src/udev.rs index 76f590b26de9..98926337929d 100644 --- a/anvil/src/udev.rs +++ b/anvil/src/udev.rs @@ -6,13 +6,16 @@ use std::{ time::{Duration, Instant}, }; -use crate::state::{DndIcon, SurfaceDmabufFeedback}; use crate::{ drawing::*, render::*, shell::WindowElement, state::{post_repaint, take_presentation_feedback, AnvilState, Backend}, }; +use crate::{ + shell::WindowRenderElement, + state::{DndIcon, SurfaceDmabufFeedback}, +}; #[cfg(feature = "renderer_sync")] use smithay::backend::drm::compositor::PrimaryPlaneElement; #[cfg(feature = "egl")] @@ -28,19 +31,16 @@ use smithay::{ Fourcc, }, drm::{ - compositor::DrmCompositor, CreateDrmNodeError, DrmAccessError, DrmDevice, DrmDeviceFd, DrmError, - DrmEvent, DrmEventMetadata, DrmNode, DrmSurface, GbmBufferedSurface, NodeType, + compositor::{DrmCompositor, FrameMode}, + output::{DrmOutput, DrmOutputManager}, + CreateDrmNodeError, DrmAccessError, DrmDevice, DrmDeviceFd, DrmError, DrmEvent, DrmEventMetadata, + DrmNode, DrmSurface, GbmBufferedSurface, NodeType, }, egl::{self, context::ContextPriority, EGLDevice, EGLDisplay}, input::InputEvent, libinput::{LibinputInputBackend, LibinputSessionInterface}, renderer::{ - damage::{Error as OutputDamageTrackerError, OutputDamageTracker}, - element::{memory::MemoryRenderBuffer, AsRenderElements, RenderElement, RenderElementStates}, - gles::{GlesRenderer, GlesTexture}, - multigpu::{gbm::GbmGlesBackend, GpuManager, MultiRenderer}, - sync::SyncPoint, - Bind, DebugFlags, ExportMem, ImportDma, ImportMemWl, Offscreen, Renderer, + damage::Error as OutputDamageTrackerError, element::{memory::MemoryRenderBuffer, AsRenderElements}, gles::GlesRenderer, multigpu::{gbm::GbmGlesBackend, GpuManager, MultiRenderer}, Color32F, DebugFlags, ImportDma, ImportMemWl }, session::{ libseat::{self, LibSeatSession}, @@ -62,7 +62,7 @@ use smithay::{ reexports::{ calloop::{ timer::{TimeoutAction, Timer}, - EventLoop, LoopHandle, RegistrationToken, + EventLoop, RegistrationToken, }, drm::{ control::{connector, crtc, Device, ModeTypeFlags}, @@ -76,7 +76,7 @@ use smithay::{ }, wayland_server::{backend::GlobalId, protocol::wl_surface, Display, DisplayHandle}, }, - utils::{Clock, DeviceFd, IsAlive, Logical, Monotonic, Physical, Point, Rectangle, Scale, Transform}, + utils::{Clock, DeviceFd, IsAlive, Logical, Monotonic, Point, Scale, Transform}, wayland::{ compositor, dmabuf::{ @@ -145,7 +145,7 @@ impl UdevData { for (_, backend) in self.backends.iter_mut() { for (_, surface) in backend.surfaces.iter_mut() { - surface.compositor.set_debug_flags(flags); + surface.drm_output.set_debug_flags(flags); } } } @@ -190,7 +190,7 @@ impl Backend for UdevData { if let Some(id) = output.user_data().get::() { if let Some(gpu) = self.backends.get_mut(&id.device_id) { if let Some(surface) = gpu.surfaces.get_mut(&id.crtc) { - surface.compositor.reset_buffers(); + surface.drm_output.reset_buffers(); } } } @@ -317,7 +317,7 @@ pub fn run_udev() { info!("pausing session"); for backend in data.backend_data.backends.values_mut() { - backend.drm.pause(); + backend.drm_output_manager.pause(); backend.active_leases.clear(); if let Some(lease_global) = backend.leasing_global.as_mut() { lease_global.suspend(); @@ -343,17 +343,12 @@ pub fn run_udev() { // state as is and assume it will just work. If this assumption fails // we will try to reset the state when trying to queue a frame. backend - .drm + .drm_output_manager .activate(false) .expect("failed to activate drm backend"); if let Some(lease_global) = backend.leasing_global.as_mut() { lease_global.resume::>(); } - for surface in backend.surfaces.values_mut() { - if let Err(err) = surface.compositor.reset_state() { - warn!("Failed to reset drm surface state: {}", err); - } - } handle.insert_idle(move |data| data.render(node, None)); } } @@ -427,12 +422,14 @@ pub fn run_udev() { // Update the per drm surface dmabuf feedback backend_data.surfaces.values_mut().for_each(|surface_data| { surface_data.dmabuf_feedback = surface_data.dmabuf_feedback.take().or_else(|| { - get_surface_dmabuf_feedback( - primary_gpu, - surface_data.render_node, - gpus, - &surface_data.compositor, - ) + surface_data.drm_output.with_compositor(|compositor| { + get_surface_dmabuf_feedback( + primary_gpu, + surface_data.render_node, + gpus, + &compositor.surface(), + ) + }) }); }); }); @@ -445,7 +442,7 @@ pub fn run_udev() { .and_then(|x| x.ok()) { if let Some(backend) = state.backend_data.backends.get(&primary_node) { - let import_device = backend.drm.device_fd().clone(); + let import_device = backend.drm_output_manager.device().device_fd().clone(); if supports_syncobj_eventfd(&import_device) { let syncobj_state = DrmSyncobjState::new::>(&display_handle, import_device); @@ -522,7 +519,8 @@ impl DrmLeaseHandler for AnvilState { .get(&node) .ok_or(LeaseRejected::default())?; - let mut builder = DrmLeaseBuilder::new(&backend.drm); + let drm_device = backend.drm_output_manager.device(); + let mut builder = DrmLeaseBuilder::new(drm_device); for conn in request.connectors { if let Some((_, crtc)) = backend .non_desktop_connectors @@ -531,21 +529,19 @@ impl DrmLeaseHandler for AnvilState { { builder.add_connector(conn); builder.add_crtc(*crtc); - let planes = backend.drm.planes(crtc).map_err(LeaseRejected::with_cause)?; + let planes = drm_device.planes(crtc).map_err(LeaseRejected::with_cause)?; let (primary_plane, primary_plane_claim) = planes .primary .iter() .find_map(|plane| { - backend - .drm + drm_device .claim_plane(plane.handle, *crtc) .map(|claim| (plane, claim)) }) .ok_or_else(LeaseRejected::default)?; builder.add_plane(primary_plane.handle, primary_plane_claim); if let Some((cursor, claim)) = planes.cursor.iter().find_map(|plane| { - backend - .drm + drm_device .claim_plane(plane.handle, *crtc) .map(|claim| (plane, claim)) }) { @@ -589,161 +585,6 @@ pub type GbmDrmCompositor = DrmCompositor< DrmDeviceFd, >; -enum SurfaceComposition { - Surface { - surface: RenderSurface, - damage_tracker: OutputDamageTracker, - debug_flags: DebugFlags, - }, - Compositor(GbmDrmCompositor), -} - -struct SurfaceCompositorRenderResult<'a> { - rendered: bool, - states: RenderElementStates, - sync: Option, - damage: Option<&'a Vec>>, -} - -impl SurfaceComposition { - #[profiling::function] - fn frame_submitted(&mut self) -> Result>, SwapBuffersError> { - match self { - SurfaceComposition::Compositor(c) => c.frame_submitted().map_err(Into::::into), - SurfaceComposition::Surface { surface, .. } => { - surface.frame_submitted().map_err(Into::::into) - } - } - } - - fn format(&self) -> smithay::reexports::gbm::Format { - match self { - SurfaceComposition::Compositor(c) => c.format(), - SurfaceComposition::Surface { surface, .. } => surface.format(), - } - } - - fn surface(&self) -> &DrmSurface { - match self { - SurfaceComposition::Compositor(c) => c.surface(), - SurfaceComposition::Surface { surface, .. } => surface.surface(), - } - } - - fn reset_buffers(&mut self) { - match self { - SurfaceComposition::Compositor(c) => c.reset_buffers(), - SurfaceComposition::Surface { surface, .. } => surface.reset_buffers(), - } - } - - fn reset_state(&mut self) -> Result<(), SwapBuffersError> { - match self { - SurfaceComposition::Compositor(c) => c.reset_state().map_err(Into::::into), - SurfaceComposition::Surface { surface, .. } => surface - .surface() - .reset_state() - .map_err(Into::::into), - } - } - - #[profiling::function] - fn queue_frame( - &mut self, - sync: Option, - damage: Option>>, - user_data: Option, - ) -> Result<(), SwapBuffersError> { - match self { - SurfaceComposition::Surface { surface, .. } => surface - .queue_buffer(sync, damage, user_data) - .map_err(Into::::into), - SurfaceComposition::Compositor(c) => { - c.queue_frame(user_data).map_err(Into::::into) - } - } - } - - #[profiling::function] - fn render_frame( - &mut self, - renderer: &mut R, - elements: &[E], - clear_color: [f32; 4], - ) -> Result, SwapBuffersError> - where - R: Renderer + Bind + Bind + Offscreen + ExportMem, - ::TextureId: 'static, - ::Error: Into, - E: RenderElement, - { - match self { - SurfaceComposition::Surface { - surface, - damage_tracker, - debug_flags, - } => { - let (dmabuf, age) = surface.next_buffer().map_err(Into::::into)?; - renderer.bind(dmabuf).map_err(Into::::into)?; - let current_debug_flags = renderer.debug_flags(); - renderer.set_debug_flags(*debug_flags); - let res = damage_tracker - .render_output(renderer, age.into(), elements, clear_color) - .map(|res| { - #[cfg(feature = "renderer_sync")] - res.sync.wait(); - let rendered = res.damage.is_some(); - SurfaceCompositorRenderResult { - rendered, - damage: res.damage, - states: res.states, - sync: rendered.then_some(res.sync), - } - }) - .map_err(|err| match err { - OutputDamageTrackerError::Rendering(err) => err.into(), - _ => unreachable!(), - }); - renderer.set_debug_flags(current_debug_flags); - res - } - SurfaceComposition::Compositor(compositor) => compositor - .render_frame(renderer, elements, clear_color) - .map(|render_frame_result| { - #[cfg(feature = "renderer_sync")] - if let PrimaryPlaneElement::Swapchain(element) = render_frame_result.primary_element { - element.sync.wait(); - } - SurfaceCompositorRenderResult { - rendered: !render_frame_result.is_empty, - damage: None, - states: render_frame_result.states, - sync: None, - } - }) - .map_err(|err| match err { - smithay::backend::drm::compositor::RenderFrameError::PrepareFrame(err) => err.into(), - smithay::backend::drm::compositor::RenderFrameError::RenderFrame( - OutputDamageTrackerError::Rendering(err), - ) => err.into(), - _ => unreachable!(), - }), - } - } - - fn set_debug_flags(&mut self, flags: DebugFlags) { - match self { - SurfaceComposition::Surface { - surface, debug_flags, .. - } => { - *debug_flags = flags; - surface.reset_buffers(); - } - SurfaceComposition::Compositor(c) => c.set_debug_flags(flags), - } - } -} - struct DrmSurfaceDmabufFeedback { render_feedback: DmabufFeedback, scanout_feedback: DmabufFeedback, @@ -754,7 +595,13 @@ struct SurfaceData { device_id: DrmNode, render_node: DrmNode, global: Option, - compositor: SurfaceComposition, + drm_output: DrmOutput< + GbmAllocator, + GbmDevice, + Option, + DrmDeviceFd, + >, + disable_direct_scanout: bool, #[cfg(feature = "debug")] fps: fps_ticker::Fps, #[cfg(feature = "debug")] @@ -775,8 +622,12 @@ struct BackendData { non_desktop_connectors: Vec<(connector::Handle, crtc::Handle)>, leasing_global: Option, active_leases: Vec, - gbm: GbmDevice, - drm: DrmDevice, + drm_output_manager: DrmOutputManager< + GbmAllocator, + GbmDevice, + Option, + DrmDeviceFd, + >, drm_scanner: DrmScanner, render_node: DrmNode, registration_token: RegistrationToken, @@ -800,7 +651,7 @@ fn get_surface_dmabuf_feedback( primary_gpu: DrmNode, render_node: DrmNode, gpus: &mut GpuManager>, - composition: &SurfaceComposition, + surface: &DrmSurface, ) -> Option { let primary_formats = gpus.single_renderer(&primary_gpu).ok()?.dmabuf_formats(); let render_formats = gpus.single_renderer(&render_node).ok()?.dmabuf_formats(); @@ -811,7 +662,6 @@ fn get_surface_dmabuf_feedback( .copied() .collect::(); - let surface = composition.surface(); let planes = surface.planes().clone(); // We limit the scan-out tranche to formats we can also render from @@ -895,12 +745,29 @@ impl AnvilState { .add_node(render_node, gbm.clone()) .map_err(DeviceAddError::AddNode)?; + let allocator = GbmAllocator::new(gbm.clone(), GbmBufferFlags::RENDERING | GbmBufferFlags::SCANOUT); + let color_formats = if std::env::var("ANVIL_DISABLE_10BIT").is_ok() { + SUPPORTED_FORMATS_8BIT_ONLY + } else { + SUPPORTED_FORMATS + }; + let mut renderer = self.backend_data.gpus.single_renderer(&render_node).unwrap(); + let render_formats = renderer.as_mut().egl_context().dmabuf_render_formats().clone(); + + let drm_device_manager = DrmOutputManager::new( + drm, + allocator, + gbm.clone(), + Some(gbm), + color_formats.iter().copied(), + render_formats, + ); + self.backend_data.backends.insert( node, BackendData { registration_token, - gbm, - drm, + drm_output_manager: drm_device_manager, drm_scanner: DrmScanner::new(), non_desktop_connectors: Vec::new(), render_node, @@ -933,20 +800,20 @@ impl AnvilState { .gpus .single_renderer(&device.render_node) .unwrap(); - let render_formats = renderer.as_mut().egl_context().dmabuf_render_formats().clone(); let output_name = format!("{}-{}", connector.interface().as_str(), connector.interface_id()); info!(?crtc, "Trying to setup connector {}", output_name,); - let non_desktop = device - .drm + let drm_device = device.drm_output_manager.device(); + + let non_desktop = drm_device .get_properties(connector.handle()) .ok() .and_then(|props| { let (info, value) = props .into_iter() .filter_map(|(handle, value)| { - let info = device.drm.get_property(handle).ok()?; + let info = drm_device.get_property(handle).ok()?; Some((info, value)) }) @@ -956,7 +823,7 @@ impl AnvilState { }) .unwrap_or(false); - let display_info = display_info::for_connector(&device.drm, connector.handle()); + let display_info = display_info::for_connector(drm_device, connector.handle()); let make = display_info .as_ref() @@ -988,14 +855,6 @@ impl AnvilState { let drm_mode = connector.modes()[mode_id]; let wl_mode = WlMode::from(drm_mode); - let surface = match device.drm.create_surface(crtc, drm_mode, &[connector.handle()]) { - Ok(surface) => surface, - Err(err) => { - warn!("Failed to create drm surface: {}", err); - return; - } - }; - let (phys_w, phys_h) = connector.size().unwrap_or((0, 0)); let output = Output::new( output_name, @@ -1026,90 +885,75 @@ impl AnvilState { #[cfg(feature = "debug")] let fps_element = self.backend_data.fps_texture.clone().map(FpsElement::new); - let allocator = GbmAllocator::new( - device.gbm.clone(), - GbmBufferFlags::RENDERING | GbmBufferFlags::SCANOUT, - ); - - let color_formats = if std::env::var("ANVIL_DISABLE_10BIT").is_ok() { - SUPPORTED_FORMATS_8BIT_ONLY - } else { - SUPPORTED_FORMATS + let driver = match drm_device.get_driver() { + Ok(driver) => driver, + Err(err) => { + warn!("Failed to query drm driver: {}", err); + return; + } }; - let compositor = if std::env::var("ANVIL_DISABLE_DRM_COMPOSITOR").is_ok() { - let gbm_surface = - match GbmBufferedSurface::new(surface, allocator, color_formats, render_formats) { - Ok(renderer) => renderer, - Err(err) => { - warn!("Failed to create rendering surface: {}", err); - return; - } - }; - SurfaceComposition::Surface { - surface: gbm_surface, - damage_tracker: OutputDamageTracker::from_output(&output), - debug_flags: self.backend_data.debug_flags, + let mut planes = match drm_device.planes(&crtc) { + Ok(planes) => planes, + Err(err) => { + warn!("Failed to query crtc planes: {}", err); + return; } - } else { - let driver = match device.drm.get_driver() { - Ok(driver) => driver, - Err(err) => { - warn!("Failed to query drm driver: {}", err); - return; - } - }; - - let mut planes = surface.planes().clone(); + }; - // Using an overlay plane on a nvidia card breaks - if driver.name().to_string_lossy().to_lowercase().contains("nvidia") - || driver - .description() - .to_string_lossy() - .to_lowercase() - .contains("nvidia") - { - planes.overlay = vec![]; - } + // Using an overlay plane on a nvidia card breaks + if driver.name().to_string_lossy().to_lowercase().contains("nvidia") + || driver + .description() + .to_string_lossy() + .to_lowercase() + .contains("nvidia") + { + planes.overlay = vec![]; + } - let mut compositor = match DrmCompositor::new( + let drm_output = match device + .drm_output_manager + .initialize_output::<_, OutputRenderElements, WindowRenderElement>>, _>( + &mut renderer, + crtc, + drm_mode, + &[connector.handle()], &output, - surface, Some(planes), - allocator, - device.gbm.clone(), - color_formats, - render_formats, - device.drm.cursor_size(), - Some(device.gbm.clone()), + |_| { + // FIXME: For a flicker free operation we should return the actual elements for this output.. + // Instead we just use black to "simulate" a modeset :) + (&[], Color32F::BLACK) + }, ) { - Ok(compositor) => compositor, - Err(err) => { - warn!("Failed to create drm compositor: {}", err); - return; - } - }; - compositor.set_debug_flags(self.backend_data.debug_flags); - - let disable_direct_scanout = std::env::var("ANVIL_DISABLE_DIRECT_SCANOUT").is_ok(); - compositor.use_direct_scanout(!disable_direct_scanout); - SurfaceComposition::Compositor(compositor) + Ok(drm_output) => drm_output, + Err(err) => { + warn!("Failed to initialize drm output: {}", err); + return; + } }; - let dmabuf_feedback = get_surface_dmabuf_feedback( - self.backend_data.primary_gpu, - device.render_node, - &mut self.backend_data.gpus, - &compositor, - ); + let disable_direct_scanout = std::env::var("ANVIL_DISABLE_DIRECT_SCANOUT").is_ok(); + + let dmabuf_feedback = drm_output.with_compositor(|compositor| { + compositor.set_debug_flags(self.backend_data.debug_flags); + + get_surface_dmabuf_feedback( + self.backend_data.primary_gpu, + device.render_node, + &mut self.backend_data.gpus, + compositor.surface(), + ) + }); let surface = SurfaceData { dh: self.display_handle.clone(), device_id: node, render_node: device.render_node, global: Some(global), - compositor, + drm_output, + disable_direct_scanout, #[cfg(feature = "debug")] fps: fps_ticker::Fps::default(), #[cfg(feature = "debug")] @@ -1118,8 +962,6 @@ impl AnvilState { }; device.surfaces.insert(crtc, surface); - - self.schedule_initial_render(node, crtc, self.handle.clone()); } } @@ -1157,6 +999,17 @@ impl AnvilState { self.space.unmap_output(&output); } } + + let mut renderer = self + .backend_data + .gpus + .single_renderer(&device.render_node) + .unwrap(); + let _ = device.drm_output_manager.reset_format::<_, OutputRenderElements, WindowRenderElement>>, _>(&mut renderer, |_| { + // FIXME: For a flicker free operation we should return the actual elements for this output.. + // Instead we just use black to "simulate" a modeset :) + (&[], Color32F::BLACK) + }); } fn device_changed(&mut self, node: DrmNode) { @@ -1166,7 +1019,10 @@ impl AnvilState { return; }; - let scan_result = match device.drm_scanner.scan_connectors(&device.drm) { + let scan_result = match device + .drm_scanner + .scan_connectors(device.drm_output_manager.device()) + { Ok(scan_result) => scan_result, Err(err) => { tracing::warn!(?err, "Failed to scan connectors"); @@ -1266,11 +1122,12 @@ impl AnvilState { return; }; - let schedule_render = match surface - .compositor + let submit_result = surface + .drm_output .frame_submitted() - .map_err(Into::::into) - { + .map_err(Into::::into); + + let schedule_render = match submit_result { Ok(user_data) => { if let Some(mut feedback) = user_data.flatten() { let tp = metadata.as_ref().and_then(|metadata| match metadata.time { @@ -1433,7 +1290,7 @@ impl AnvilState { let mut renderer = if primary_gpu == render_node { self.backend_data.gpus.single_renderer(&render_node) } else { - let format = surface.compositor.format(); + let format = surface.drm_output.format(); self.backend_data .gpus .renderer(&primary_gpu, &render_node, format) @@ -1507,7 +1364,11 @@ impl AnvilState { // reset the complete state, disabling all connectors and planes in case we hit a test failed // most likely we hit this after a tty switch when a foreign master changed CRTC <-> connector bindings // and we run in a mismatch - device.drm.reset_state().expect("failed to reset drm device"); + device + .drm_output_manager + .device_mut() + .reset_state() + .expect("failed to reset drm device"); true } _ => panic!("Rendering loop lost: {}", err), @@ -1544,44 +1405,6 @@ impl AnvilState { profiling::finish_frame!(); } - - fn schedule_initial_render( - &mut self, - node: DrmNode, - crtc: crtc::Handle, - evt_handle: LoopHandle<'static, AnvilState>, - ) { - let device = if let Some(device) = self.backend_data.backends.get_mut(&node) { - device - } else { - return; - }; - - let surface = if let Some(surface) = device.surfaces.get_mut(&crtc) { - surface - } else { - return; - }; - - let node = surface.render_node; - let result = { - let mut renderer = self.backend_data.gpus.single_renderer(&node).unwrap(); - initial_render(surface, &mut renderer) - }; - - if let Err(err) = result { - match err { - SwapBuffersError::AlreadySwapped => {} - SwapBuffersError::TemporaryFailure(err) => { - // TODO dont reschedule after 3(?) retries - warn!("Failed to submit page_flip: {}", err); - let handle = evt_handle.clone(); - evt_handle.insert_idle(move |data| data.schedule_initial_render(node, crtc, handle)); - } - SwapBuffersError::ContextLost(err) => panic!("Rendering loop lost: {}", err), - } - } - } } #[allow(clippy::too_many_arguments)] @@ -1676,14 +1499,30 @@ fn render_surface<'a>( let (elements, clear_color) = output_elements(output, space, custom_elements, renderer, show_window_preview); - let SurfaceCompositorRenderResult { - rendered, - states, - sync, - damage, - } = surface - .compositor - .render_frame::<_, _, GlesTexture>(renderer, &elements, clear_color)?; + + let frame_mode = surface + .disable_direct_scanout + .then_some(FrameMode::COMPOSITE) + .unwrap_or(FrameMode::ALL); + let (rendered, states) = surface + .drm_output + .render_frame(renderer, &elements, clear_color, frame_mode) + .map(|render_frame_result| { + #[cfg(feature = "renderer_sync")] + if let PrimaryPlaneElement::Swapchain(element) = render_frame_result.primary_element { + element.sync.wait(); + } + (!render_frame_result.is_empty, render_frame_result.states) + }) + .map_err(|err| match err { + smithay::backend::drm::compositor::RenderFrameError::PrepareFrame(err) => { + SwapBuffersError::from(err) + } + smithay::backend::drm::compositor::RenderFrameError::RenderFrame( + OutputDamageTrackerError::Rendering(err), + ) => SwapBuffersError::from(err), + _ => unreachable!(), + })?; post_repaint( output, @@ -1701,25 +1540,11 @@ fn render_surface<'a>( if rendered { let output_presentation_feedback = take_presentation_feedback(output, space, &states); - let damage = damage.cloned(); surface - .compositor - .queue_frame(sync, damage, Some(output_presentation_feedback)) + .drm_output + .queue_frame(Some(output_presentation_feedback)) .map_err(Into::::into)?; } Ok(rendered) } - -fn initial_render( - surface: &mut SurfaceData, - renderer: &mut UdevRenderer<'_>, -) -> Result<(), SwapBuffersError> { - surface - .compositor - .render_frame::<_, CustomRenderElements<_>, GlesTexture>(renderer, &[], CLEAR_COLOR)?; - surface.compositor.queue_frame(None, None, None)?; - surface.compositor.reset_buffers(); - - Ok(()) -} diff --git a/src/backend/allocator/gbm.rs b/src/backend/allocator/gbm.rs index fe10ff7e059a..c6ce96f9afbc 100644 --- a/src/backend/allocator/gbm.rs +++ b/src/backend/allocator/gbm.rs @@ -26,6 +26,8 @@ pub struct GbmBuffer { format: Format, } +unsafe impl Sync for GbmBuffer {} + #[cfg(feature = "backend_drm")] impl PlanarBuffer for GbmBuffer { #[inline] diff --git a/src/backend/drm/compositor/frame_result.rs b/src/backend/drm/compositor/frame_result.rs new file mode 100644 index 000000000000..9816258b0b3f --- /dev/null +++ b/src/backend/drm/compositor/frame_result.rs @@ -0,0 +1,452 @@ +use std::collections::HashSet; + +use crate::{ + backend::{ + allocator::{ + dmabuf::{AsDmabuf, Dmabuf}, + Buffer, Slot, + }, + drm::Framebuffer, + renderer::{ + damage::OutputDamageTracker, + element::{Element, Id, RenderElement, RenderElementStates}, + sync::SyncPoint, + utils::{CommitCounter, DamageSet, DamageSnapshot, OpaqueRegions}, + Blit, Color32F, Frame, Renderer, + }, + }, + output::OutputNoMode, + utils::{Buffer as BufferCoords, Physical, Point, Rectangle, Scale, Size, Transform}, +}; + +use super::{DrmScanoutBuffer, ScanoutBuffer}; + +/// Result for [`DrmCompositor::render_frame`] +/// +/// **Note**: This struct may contain a reference to the composited buffer +/// of the primary display plane. Dropping it will remove said reference and +/// allows the buffer to be reused. +/// +/// Keeping the buffer longer may cause the following issues: +/// - **Too much damage** - until the buffer is marked free it is not considered +/// submitted by the swapchain, causing the age value of newly queried buffers +/// to be lower than necessary, potentially resulting in more rendering than necessary. +/// To avoid this make sure the buffer is dropped before starting the next render. +/// - **Exhaustion of swapchain images** - Continuing rendering while holding on +/// to too many buffers may cause the swapchain to run out of images, returning errors +/// on rendering until buffers are freed again. The exact amount of images in a +/// swapchain is an implementation detail, but should generally be expect to be +/// large enough to hold onto at least one `RenderFrameResult`. +pub struct RenderFrameResult<'a, B: Buffer, F: Framebuffer, E> { + /// If this frame contains any changes and should be submitted + pub is_empty: bool, + /// The render element states of this frame + pub states: RenderElementStates, + /// Element for the primary plane + pub primary_element: PrimaryPlaneElement<'a, B, F, E>, + /// Overlay elements in front to back order + pub overlay_elements: Vec<&'a E>, + /// Optional cursor plane element + /// + /// If set always above all other elements + pub cursor_element: Option<&'a E>, + + pub(super) primary_plane_element_id: Id, + pub(super) supports_fencing: bool, +} + +impl<'a, B: Buffer, F: Framebuffer, E> RenderFrameResult<'a, B, F, E> { + /// Returns if synchronization with kms submission can't be guaranteed through the available apis. + pub fn needs_sync(&self) -> bool { + if let PrimaryPlaneElement::Swapchain(ref element) = self.primary_element { + !self.supports_fencing || !element.sync.is_exportable() + } else { + false + } + } +} + +struct SwapchainElement<'a, 'b, B: Buffer> { + id: Id, + slot: &'a Slot, + transform: Transform, + damage: &'b DamageSnapshot, +} + +impl<'a, 'b, B: Buffer> Element for SwapchainElement<'a, 'b, B> { + fn id(&self) -> &Id { + &self.id + } + + fn current_commit(&self) -> CommitCounter { + self.damage.current_commit() + } + + fn src(&self) -> Rectangle { + Rectangle::from_loc_and_size((0, 0), self.slot.size()).to_f64() + } + + fn geometry(&self, _scale: Scale) -> Rectangle { + Rectangle::from_loc_and_size( + (0, 0), + self.slot.size().to_logical(1, self.transform).to_physical(1), + ) + } + + fn transform(&self) -> Transform { + self.transform + } + + fn damage_since(&self, scale: Scale, commit: Option) -> DamageSet { + self.damage + .damage_since(commit) + .map(|d| { + d.into_iter() + .map(|d| d.to_logical(1, self.transform, &self.slot.size()).to_physical(1)) + .collect() + }) + .unwrap_or_else(|| DamageSet::from_slice(&[self.geometry(scale)])) + } + + fn opaque_regions(&self, scale: Scale) -> OpaqueRegions { + OpaqueRegions::from_slice(&[self.geometry(scale)]) + } +} + +enum FrameResultDamageElement<'a, 'b, E, B: Buffer> { + Element(&'a E), + Swapchain(SwapchainElement<'a, 'b, B>), +} + +impl<'a, 'b, E, B> Element for FrameResultDamageElement<'a, 'b, E, B> +where + E: Element, + B: Buffer, +{ + fn id(&self) -> &Id { + match self { + FrameResultDamageElement::Element(e) => e.id(), + FrameResultDamageElement::Swapchain(e) => e.id(), + } + } + + fn current_commit(&self) -> CommitCounter { + match self { + FrameResultDamageElement::Element(e) => e.current_commit(), + FrameResultDamageElement::Swapchain(e) => e.current_commit(), + } + } + + fn src(&self) -> Rectangle { + match self { + FrameResultDamageElement::Element(e) => e.src(), + FrameResultDamageElement::Swapchain(e) => e.src(), + } + } + + fn geometry(&self, scale: Scale) -> Rectangle { + match self { + FrameResultDamageElement::Element(e) => e.geometry(scale), + FrameResultDamageElement::Swapchain(e) => e.geometry(scale), + } + } + + fn location(&self, scale: Scale) -> Point { + match self { + FrameResultDamageElement::Element(e) => e.location(scale), + FrameResultDamageElement::Swapchain(e) => e.location(scale), + } + } + + fn transform(&self) -> Transform { + match self { + FrameResultDamageElement::Element(e) => e.transform(), + FrameResultDamageElement::Swapchain(e) => e.transform(), + } + } + + fn damage_since(&self, scale: Scale, commit: Option) -> DamageSet { + match self { + FrameResultDamageElement::Element(e) => e.damage_since(scale, commit), + FrameResultDamageElement::Swapchain(e) => e.damage_since(scale, commit), + } + } + + fn opaque_regions(&self, scale: Scale) -> OpaqueRegions { + match self { + FrameResultDamageElement::Element(e) => e.opaque_regions(scale), + FrameResultDamageElement::Swapchain(e) => e.opaque_regions(scale), + } + } +} + +#[derive(Debug)] +/// Defines the element for the primary plane +pub enum PrimaryPlaneElement<'a, B: Buffer, F: Framebuffer, E> { + /// A slot from the swapchain was used for rendering + /// the primary plane + Swapchain(PrimarySwapchainElement), + /// An element has been assigned for direct scan-out + Element(&'a E), +} + +/// Error for [`RenderFrameResult::blit_frame_result`] +#[derive(Debug, thiserror::Error)] +pub enum BlitFrameResultError { + /// A render error occurred + #[error(transparent)] + Rendering(R), + /// A error occurred during exporting the buffer + #[error(transparent)] + Export(E), +} + +impl<'a, B, F, E> RenderFrameResult<'a, B, F, E> +where + B: Buffer, + F: Framebuffer, +{ + /// Get the damage of this frame for the specified dtr and age + pub fn damage_from_age<'d>( + &self, + damage_tracker: &'d mut OutputDamageTracker, + age: usize, + filter: impl IntoIterator, + ) -> Result<(Option<&'d Vec>>, RenderElementStates), OutputNoMode> + where + E: Element, + { + #[allow(clippy::mutable_key_type)] + let filter_ids: HashSet = filter.into_iter().collect(); + + let mut elements: Vec> = + Vec::with_capacity(usize::from(self.cursor_element.is_some()) + self.overlay_elements.len() + 1); + if let Some(cursor) = self.cursor_element { + if !filter_ids.contains(cursor.id()) { + elements.push(FrameResultDamageElement::Element(cursor)); + } + } + + elements.extend( + self.overlay_elements + .iter() + .filter(|e| !filter_ids.contains(e.id())) + .map(|e| FrameResultDamageElement::Element(*e)), + ); + + let primary_render_element = match &self.primary_element { + PrimaryPlaneElement::Swapchain(PrimarySwapchainElement { + slot, + transform, + damage, + .. + }) => FrameResultDamageElement::Swapchain(SwapchainElement { + id: self.primary_plane_element_id.clone(), + transform: *transform, + slot: match &slot.buffer { + ScanoutBuffer::Swapchain(slot) => slot, + _ => unreachable!(), + }, + damage, + }), + PrimaryPlaneElement::Element(e) => FrameResultDamageElement::Element(*e), + }; + + elements.push(primary_render_element); + + damage_tracker.damage_output(age, &elements) + } +} + +impl<'a, B, F, E> RenderFrameResult<'a, B, F, E> +where + B: Buffer + AsDmabuf, + ::Error: std::fmt::Debug, + F: Framebuffer, +{ + /// Blit the frame result into a currently bound buffer + #[allow(clippy::too_many_arguments)] + pub fn blit_frame_result( + &self, + size: impl Into>, + transform: Transform, + scale: impl Into>, + renderer: &mut R, + damage: impl IntoIterator>, + filter: impl IntoIterator, + ) -> Result::Error, ::Error>> + where + R: Renderer + Blit, + ::TextureId: 'static, + E: Element + RenderElement, + { + let size = size.into(); + let scale = scale.into(); + #[allow(clippy::mutable_key_type)] + let filter_ids: HashSet = filter.into_iter().collect(); + let damage = damage.into_iter().collect::>(); + + // If we have no damage we can exit early + if damage.is_empty() { + return Ok(SyncPoint::signaled()); + } + + let mut opaque_regions: Vec> = Vec::new(); + + let mut elements_to_render: Vec<&'a E> = + Vec::with_capacity(usize::from(self.cursor_element.is_some()) + self.overlay_elements.len() + 1); + + if let Some(cursor_element) = self.cursor_element.as_ref() { + if !filter_ids.contains(cursor_element.id()) { + elements_to_render.push(*cursor_element); + opaque_regions.extend(cursor_element.opaque_regions(scale)); + } + } + + for element in self + .overlay_elements + .iter() + .filter(|e| !filter_ids.contains(e.id())) + { + elements_to_render.push(element); + opaque_regions.extend(element.opaque_regions(scale)); + } + + let primary_dmabuf = match &self.primary_element { + PrimaryPlaneElement::Swapchain(PrimarySwapchainElement { slot, sync, .. }) => { + let dmabuf = match &slot.buffer { + ScanoutBuffer::Swapchain(slot) => slot.export().map_err(BlitFrameResultError::Export)?, + _ => unreachable!(), + }; + let size = dmabuf.size(); + let geometry = Rectangle::from_loc_and_size( + (0, 0), + size.to_logical(1, Transform::Normal).to_physical(1), + ); + opaque_regions.push(geometry); + Some((sync.clone(), dmabuf, geometry)) + } + PrimaryPlaneElement::Element(e) => { + elements_to_render.push(*e); + opaque_regions.extend(e.opaque_regions(scale)); + None + } + }; + + let clear_damage = + Rectangle::subtract_rects_many_in_place(damage.clone(), opaque_regions.iter().copied()); + + let mut sync: Option = None; + if !clear_damage.is_empty() { + tracing::trace!("clearing frame damage {:#?}", clear_damage); + + let mut frame = renderer + .render(size, transform) + .map_err(BlitFrameResultError::Rendering)?; + + frame + .clear(Color32F::BLACK, &clear_damage) + .map_err(BlitFrameResultError::Rendering)?; + + sync = Some(frame.finish().map_err(BlitFrameResultError::Rendering)?); + } + + // first do the potential blit + if let Some((sync, dmabuf, geometry)) = primary_dmabuf { + let blit_damage = damage + .iter() + .filter_map(|d| d.intersection(geometry)) + .collect::>(); + + tracing::trace!("blitting frame with damage: {:#?}", blit_damage); + + renderer.wait(&sync).map_err(BlitFrameResultError::Rendering)?; + for rect in blit_damage { + renderer + .blit_from( + dmabuf.clone(), + rect, + rect, + crate::backend::renderer::TextureFilter::Linear, + ) + .map_err(BlitFrameResultError::Rendering)?; + } + } + + // then render the remaining elements if any + if !elements_to_render.is_empty() { + tracing::trace!("drawing {} frame element(s)", elements_to_render.len()); + + let mut frame = renderer + .render(size, transform) + .map_err(BlitFrameResultError::Rendering)?; + + for element in elements_to_render.iter().rev() { + let src = element.src(); + let dst = element.geometry(scale); + let element_damage = damage + .iter() + .filter_map(|d| { + d.intersection(dst).map(|mut d| { + d.loc -= dst.loc; + d + }) + }) + .collect::>(); + + // no need to render without damage + if element_damage.is_empty() { + continue; + } + + tracing::trace!("drawing frame element with damage: {:#?}", element_damage); + + element + .draw(&mut frame, src, dst, &element_damage, &[]) + .map_err(BlitFrameResultError::Rendering)?; + } + + Ok(frame.finish().map_err(BlitFrameResultError::Rendering)?) + } else { + Ok(sync.unwrap_or_default()) + } + } +} + +impl<'a, B: Buffer + std::fmt::Debug, F: Framebuffer + std::fmt::Debug, E: std::fmt::Debug> std::fmt::Debug + for RenderFrameResult<'a, B, F, E> +{ + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("RenderFrameResult") + .field("is_empty", &self.is_empty) + .field("states", &self.states) + .field("primary_element", &self.primary_element) + .field("overlay_elements", &self.overlay_elements) + .field("cursor_element", &self.cursor_element) + .finish() + } +} + +#[derive(Debug)] +/// Defines the element for the primary plane in cases where a composited buffer was used. +pub struct PrimarySwapchainElement { + /// The slot from the swapchain + pub(super) slot: DrmScanoutBuffer, + /// Sync point + pub sync: SyncPoint, + /// The transform applied during rendering + pub transform: Transform, + /// The damage on the primary plane + pub damage: DamageSnapshot, +} + +impl PrimarySwapchainElement { + /// Access the underlying swapchain buffer + #[inline] + pub fn buffer(&self) -> &B { + match &self.slot.buffer { + ScanoutBuffer::Swapchain(slot) => slot, + _ => unreachable!(), + } + } +} diff --git a/src/backend/drm/compositor/mod.rs b/src/backend/drm/compositor/mod.rs index 2e7e99fb61e9..c9760d6ef093 100644 --- a/src/backend/drm/compositor/mod.rs +++ b/src/backend/drm/compositor/mod.rs @@ -58,7 +58,7 @@ //! # use std::{collections::HashSet, mem::MaybeUninit}; //! # //! use smithay::{ -//! backend::drm::{compositor::DrmCompositor, DrmSurface}, +//! backend::drm::{compositor::DrmCompositor, DrmSurface, FrameMode}, //! output::{Output, PhysicalProperties, Subpixel}, //! utils::Size, //! }; @@ -104,7 +104,7 @@ //! //! # let elements: Vec> = Vec::new(); //! let render_frame_result = compositor -//! .render_frame::<_, _>(&mut renderer, &elements, CLEAR_COLOR) +//! .render_frame::<_, _>(&mut renderer, &elements, CLEAR_COLOR, FrameMode::ALL) //! .expect("failed to render frame"); //! //! if !render_frame_result.is_empty { @@ -120,14 +120,12 @@ //! } //! ``` use std::{ - cell::RefCell, - collections::{HashMap, HashSet}, + collections::HashMap, fmt::Debug, io::ErrorKind, os::unix::io::{AsFd, OwnedFd}, - rc::Rc, str::FromStr, - sync::{Arc, Mutex}, + sync::Arc, }; use drm::{ @@ -162,23 +160,27 @@ use crate::{ RenderElementStates, RenderingReason, UnderlyingStorage, }, sync::SyncPoint, - utils::{CommitCounter, DamageBag, DamageSet, DamageSnapshot, OpaqueRegions}, - Bind, Blit, Color32F, DebugFlags, Frame as RendererFrame, Renderer, Texture, + utils::{CommitCounter, DamageBag}, + Bind, Color32F, DebugFlags, Frame as RendererFrame, Renderer, Texture, }, SwapBuffersError, }, - output::{OutputModeSource, OutputNoMode}, + output::OutputModeSource, utils::{Buffer as BufferCoords, DevPath, Physical, Point, Rectangle, Scale, Size, Transform}, wayland::{shm, single_pixel_buffer}, }; -use super::{error::AccessError, DrmDeviceFd, DrmSurface, Framebuffer, PlaneClaim, PlaneInfo, Planes}; +use super::{ + error::AccessError, + exporter::{ExportBuffer, ExportFramebuffer}, + DrmSurface, Framebuffer, PlaneClaim, PlaneInfo, Planes, +}; -pub mod dumb; mod elements; -pub mod gbm; +mod frame_result; use elements::*; +pub use frame_result::*; impl RenderElementState { pub(crate) fn zero_copy(visible_area: usize) -> Self { @@ -200,8 +202,18 @@ impl RenderElementState { #[derive(Debug)] enum ScanoutBuffer { Wayland(crate::backend::renderer::utils::Buffer), - Swapchain(Slot), - Cursor(GbmBuffer), + Swapchain(Arc>), + Cursor(Arc), +} + +impl Clone for ScanoutBuffer { + fn clone(&self) -> Self { + match self { + Self::Wayland(arg0) => Self::Wayland(arg0.clone()), + Self::Swapchain(arg0) => Self::Swapchain(arg0.clone()), + Self::Cursor(arg0) => Self::Cursor(arg0.clone()), + } + } } impl ScanoutBuffer { @@ -275,7 +287,16 @@ where struct DrmScanoutBuffer { buffer: ScanoutBuffer, - fb: OwnedFramebuffer>, + fb: CachedDrmFramebuffer, +} + +impl Clone for DrmScanoutBuffer { + fn clone(&self) -> Self { + DrmScanoutBuffer { + buffer: self.buffer.clone(), + fb: self.fb.clone(), + } + } } impl std::fmt::Debug for DrmScanoutBuffer @@ -375,7 +396,7 @@ where fb_cache: SmallVec< [( ElementFramebufferCacheKey, - Result, ExportBufferError>, + Result, ExportBufferError>, ); 4], >, } @@ -388,7 +409,7 @@ where fn get( &self, cache_key: &ElementFramebufferCacheKey, - ) -> Option, ExportBufferError>> { + ) -> Option, ExportBufferError>> { self.fb_cache.iter().find_map(|(k, r)| { if k == cache_key { Some(r.as_ref().map_err(|err| *err)) @@ -402,7 +423,7 @@ where fn insert( &mut self, cache_key: ElementFramebufferCacheKey, - fb: Result, ExportBufferError>, + fb: Result, ExportBufferError>, ) { self.fb_cache.push((cache_key, fb)); } @@ -444,31 +465,31 @@ impl PlaneProperties { } } -struct ElementPlaneConfig<'a, B> { +struct ElementPlaneConfig<'a, B: Buffer, F: Framebuffer> { z_index: usize, geometry: Rectangle, properties: PlaneProperties, - buffer: Owned, + buffer: DrmScanoutBuffer, failed_planes: &'a mut PlanesSnapshot, } #[derive(Debug)] -struct PlaneConfig { +struct PlaneConfig { pub properties: PlaneProperties, - pub buffer: Owned, + pub buffer: DrmScanoutBuffer, pub damage_clips: Option, pub plane_claim: PlaneClaim, pub sync: Option<(SyncPoint, Option>)>, } -impl PlaneConfig { +impl PlaneConfig { #[inline] - pub fn is_compatible(&self, other: &PlaneConfig) -> bool { + pub fn is_compatible(&self, other: &PlaneConfig) -> bool { self.properties.is_compatible(&other.properties) } } -impl Clone for PlaneConfig { +impl Clone for PlaneConfig { #[inline] fn clone(&self) -> Self { Self { @@ -489,14 +510,14 @@ struct PlaneElementState { } #[derive(Debug)] -struct PlaneState { +struct PlaneState { skip: bool, needs_test: bool, element_state: Option, - config: Option>, + config: Option>, } -impl Default for PlaneState { +impl Default for PlaneState { #[inline] fn default() -> Self { Self { @@ -508,10 +529,10 @@ impl Default for PlaneState { } } -impl PlaneState { +impl PlaneState { #[inline] - fn buffer(&self) -> Option<&B> { - self.config.as_ref().map(|config| &*config.buffer) + fn buffer(&self) -> Option<&DrmScanoutBuffer> { + self.config.as_ref().map(|config| &config.buffer) } #[inline] @@ -524,7 +545,7 @@ impl PlaneState { } } -impl Clone for PlaneState { +impl Clone for PlaneState { #[inline] fn clone(&self) -> Self { Self { @@ -537,11 +558,11 @@ impl Clone for PlaneState { } #[derive(Debug)] -struct FrameState> { - planes: SmallVec<[(plane::Handle, PlaneState); 10]>, +struct FrameState { + planes: SmallVec<[(plane::Handle, PlaneState); 10]>, } -impl> FrameState { +impl FrameState { #[inline] fn is_assigned(&self, handle: plane::Handle) -> bool { self.planes @@ -571,14 +592,14 @@ impl> FrameState { } #[inline] - fn plane_state(&self, handle: plane::Handle) -> Option<&PlaneState> { + fn plane_state(&self, handle: plane::Handle) -> Option<&PlaneState> { self.planes .iter() .find_map(|(p, state)| if *p == handle { Some(state) } else { None }) } #[inline] - fn plane_state_mut(&mut self, handle: plane::Handle) -> Option<&mut PlaneState> { + fn plane_state_mut(&mut self, handle: plane::Handle) -> Option<&mut PlaneState> { self.planes .iter_mut() .find_map(|(p, state)| if *p == handle { Some(state) } else { None }) @@ -592,59 +613,13 @@ impl> FrameState { } #[inline] - fn plane_buffer(&self, handle: plane::Handle) -> Option<&B> { + fn plane_buffer(&self, handle: plane::Handle) -> Option<&DrmScanoutBuffer> { self.plane_state(handle) - .and_then(|state| state.config.as_ref().map(|config| &*config.buffer)) + .and_then(|state| state.config.as_ref().map(|config| &config.buffer)) } } -#[derive(Debug)] -struct Owned(Rc); - -impl std::ops::Deref for Owned { - type Target = B; - - #[inline] - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl From for Owned { - #[inline] - fn from(outer: B) -> Self { - Self(Rc::new(outer)) - } -} - -impl AsRef for Owned -where - B: Framebuffer, -{ - #[inline] - fn as_ref(&self) -> &framebuffer::Handle { - (*self.0).as_ref() - } -} - -impl Framebuffer for Owned -where - B: Framebuffer, -{ - #[inline] - fn format(&self) -> drm_fourcc::DrmFormat { - (*self.0).format() - } -} - -impl Clone for Owned { - #[inline] - fn clone(&self) -> Self { - Self(self.0.clone()) - } -} - -impl FrameState { +impl FrameState { fn from_planes(primary_plane: plane::Handle, planes: &Planes) -> Self { let mut tmp = SmallVec::with_capacity(planes.overlay.len() + planes.cursor.len() + 1); tmp.push((primary_plane, PlaneState::default())); @@ -665,10 +640,10 @@ impl FrameState { } } -impl FrameState { +impl FrameState { #[profiling::function] #[inline] - fn set_state(&mut self, plane: plane::Handle, state: PlaneState) { + fn set_state(&mut self, plane: plane::Handle, state: PlaneState) { let current_config = match self.plane_state_mut(plane) { Some(config) => config, None => return, @@ -682,7 +657,7 @@ impl FrameState { surface: &DrmSurface, supports_fencing: bool, plane: plane::Handle, - state: PlaneState, + state: PlaneState, allow_modeset: bool, ) -> Result<(), DrmError> { let current_config = match self.plane_state_mut(plane) { @@ -841,104 +816,8 @@ impl FrameState { } } -/// Possible buffers to export as a framebuffer using [`ExportFramebuffer`] -#[derive(Debug)] -pub enum ExportBuffer<'a, B: Buffer> { - /// A wayland buffer - Wayland(&'a WlBuffer), - /// A [`Allocator`] buffer - Allocator(&'a B), -} - -impl<'a, B: Buffer> ExportBuffer<'a, B> { - #[inline] - fn from_underlying_storage(storage: &'a UnderlyingStorage<'_>) -> Option { - match storage { - UnderlyingStorage::Wayland(buffer) => Some(Self::Wayland(buffer)), - UnderlyingStorage::Memory { .. } => None, - } - } -} - -/// Export a [`ExportBuffer`] as a framebuffer -pub trait ExportFramebuffer -where - B: Buffer, -{ - /// Type of the framebuffer - type Framebuffer: Framebuffer; - - /// Type of the error - type Error: std::error::Error; - - /// Add a framebuffer for the specified buffer - fn add_framebuffer( - &self, - drm: &DrmDeviceFd, - buffer: ExportBuffer<'_, B>, - use_opaque: bool, - ) -> Result, Self::Error>; - - /// Test if the provided buffer is eligible for adding a framebuffer - fn can_add_framebuffer(&self, buffer: &ExportBuffer<'_, B>) -> bool; -} - -impl ExportFramebuffer for Arc> -where - F: ExportFramebuffer, - B: Buffer, -{ - type Framebuffer = >::Framebuffer; - type Error = >::Error; - - #[inline] - fn add_framebuffer( - &self, - drm: &DrmDeviceFd, - buffer: ExportBuffer<'_, B>, - use_opaque: bool, - ) -> Result, Self::Error> { - let guard = self.lock().unwrap(); - guard.add_framebuffer(drm, buffer, use_opaque) - } - - #[inline] - fn can_add_framebuffer(&self, buffer: &ExportBuffer<'_, B>) -> bool { - let guard = self.lock().unwrap(); - guard.can_add_framebuffer(buffer) - } -} - -impl ExportFramebuffer for Rc> -where - F: ExportFramebuffer, - B: Buffer, -{ - type Framebuffer = >::Framebuffer; - type Error = >::Error; - - #[inline] - fn add_framebuffer( - &self, - drm: &DrmDeviceFd, - buffer: ExportBuffer<'_, B>, - use_opaque: bool, - ) -> Result, Self::Error> { - self.borrow().add_framebuffer(drm, buffer, use_opaque) - } - - #[inline] - fn can_add_framebuffer(&self, buffer: &ExportBuffer<'_, B>) -> bool { - self.borrow().can_add_framebuffer(buffer) - } -} - -type Frame = FrameState< - DrmScanoutBuffer< - ::Buffer, - ::Buffer>>::Framebuffer, - >, ->; +type CompositorFrameState = + FrameState<::Buffer, ::Buffer>>::Framebuffer>; type FrameErrorType = FrameError< ::Error, @@ -946,445 +825,14 @@ type FrameErrorType = FrameError< ::Buffer>>::Error, >; -type FrameResult = Result>; +pub(crate) type FrameResult = Result>; -type RenderFrameErrorType = RenderFrameError< +pub(crate) type RenderFrameErrorType = RenderFrameError< ::Error, <::Buffer as AsDmabuf>::Error, ::Buffer>>::Error, R, >; - -#[derive(Debug)] -/// Defines the element for the primary plane in cases where a composited buffer was used. -pub struct PrimarySwapchainElement { - /// The slot from the swapchain - slot: Owned>, - /// Sync point - pub sync: SyncPoint, - /// The transform applied during rendering - pub transform: Transform, - /// The damage on the primary plane - pub damage: DamageSnapshot, -} - -impl PrimarySwapchainElement { - /// Access the underlying swapchain buffer - #[inline] - pub fn buffer(&self) -> &B { - match &self.slot.0.buffer { - ScanoutBuffer::Swapchain(slot) => slot, - _ => unreachable!(), - } - } -} - -#[derive(Debug)] -/// Defines the element for the primary plane -pub enum PrimaryPlaneElement<'a, B: Buffer, F: Framebuffer, E> { - /// A slot from the swapchain was used for rendering - /// the primary plane - Swapchain(PrimarySwapchainElement), - /// An element has been assigned for direct scan-out - Element(&'a E), -} - -/// Result for [`DrmCompositor::render_frame`] -/// -/// **Note**: This struct may contain a reference to the composited buffer -/// of the primary display plane. Dropping it will remove said reference and -/// allows the buffer to be reused. -/// -/// Keeping the buffer longer may cause the following issues: -/// - **Too much damage** - until the buffer is marked free it is not considered -/// submitted by the swapchain, causing the age value of newly queried buffers -/// to be lower than necessary, potentially resulting in more rendering than necessary. -/// To avoid this make sure the buffer is dropped before starting the next render. -/// - **Exhaustion of swapchain images** - Continuing rendering while holding on -/// to too many buffers may cause the swapchain to run out of images, returning errors -/// on rendering until buffers are freed again. The exact amount of images in a -/// swapchain is an implementation detail, but should generally be expect to be -/// large enough to hold onto at least one `RenderFrameResult`. -pub struct RenderFrameResult<'a, B: Buffer, F: Framebuffer, E> { - /// If this frame contains any changes and should be submitted - pub is_empty: bool, - /// The render element states of this frame - pub states: RenderElementStates, - /// Element for the primary plane - pub primary_element: PrimaryPlaneElement<'a, B, F, E>, - /// Overlay elements in front to back order - pub overlay_elements: Vec<&'a E>, - /// Optional cursor plane element - /// - /// If set always above all other elements - pub cursor_element: Option<&'a E>, - - primary_plane_element_id: Id, - supports_fencing: bool, -} - -impl<'a, B: Buffer, F: Framebuffer, E> RenderFrameResult<'a, B, F, E> { - /// Returns if synchronization with kms submission can't be guaranteed through the available apis. - pub fn needs_sync(&self) -> bool { - if let PrimaryPlaneElement::Swapchain(ref element) = self.primary_element { - !self.supports_fencing || !element.sync.is_exportable() - } else { - false - } - } -} - -struct SwapchainElement<'a, 'b, B: Buffer> { - id: Id, - slot: &'a Slot, - transform: Transform, - damage: &'b DamageSnapshot, -} - -impl<'a, 'b, B: Buffer> Element for SwapchainElement<'a, 'b, B> { - fn id(&self) -> &Id { - &self.id - } - - fn current_commit(&self) -> CommitCounter { - self.damage.current_commit() - } - - fn src(&self) -> Rectangle { - Rectangle::from_loc_and_size((0, 0), self.slot.size()).to_f64() - } - - fn geometry(&self, _scale: Scale) -> Rectangle { - Rectangle::from_loc_and_size( - (0, 0), - self.slot.size().to_logical(1, self.transform).to_physical(1), - ) - } - - fn transform(&self) -> Transform { - self.transform - } - - fn damage_since(&self, scale: Scale, commit: Option) -> DamageSet { - self.damage - .damage_since(commit) - .map(|d| { - d.into_iter() - .map(|d| d.to_logical(1, self.transform, &self.slot.size()).to_physical(1)) - .collect() - }) - .unwrap_or_else(|| DamageSet::from_slice(&[self.geometry(scale)])) - } - - fn opaque_regions(&self, scale: Scale) -> OpaqueRegions { - OpaqueRegions::from_slice(&[self.geometry(scale)]) - } -} - -enum FrameResultDamageElement<'a, 'b, E, B: Buffer> { - Element(&'a E), - Swapchain(SwapchainElement<'a, 'b, B>), -} - -impl<'a, 'b, E, B> Element for FrameResultDamageElement<'a, 'b, E, B> -where - E: Element, - B: Buffer, -{ - fn id(&self) -> &Id { - match self { - FrameResultDamageElement::Element(e) => e.id(), - FrameResultDamageElement::Swapchain(e) => e.id(), - } - } - - fn current_commit(&self) -> CommitCounter { - match self { - FrameResultDamageElement::Element(e) => e.current_commit(), - FrameResultDamageElement::Swapchain(e) => e.current_commit(), - } - } - - fn src(&self) -> Rectangle { - match self { - FrameResultDamageElement::Element(e) => e.src(), - FrameResultDamageElement::Swapchain(e) => e.src(), - } - } - - fn geometry(&self, scale: Scale) -> Rectangle { - match self { - FrameResultDamageElement::Element(e) => e.geometry(scale), - FrameResultDamageElement::Swapchain(e) => e.geometry(scale), - } - } - - fn location(&self, scale: Scale) -> Point { - match self { - FrameResultDamageElement::Element(e) => e.location(scale), - FrameResultDamageElement::Swapchain(e) => e.location(scale), - } - } - - fn transform(&self) -> Transform { - match self { - FrameResultDamageElement::Element(e) => e.transform(), - FrameResultDamageElement::Swapchain(e) => e.transform(), - } - } - - fn damage_since(&self, scale: Scale, commit: Option) -> DamageSet { - match self { - FrameResultDamageElement::Element(e) => e.damage_since(scale, commit), - FrameResultDamageElement::Swapchain(e) => e.damage_since(scale, commit), - } - } - - fn opaque_regions(&self, scale: Scale) -> OpaqueRegions { - match self { - FrameResultDamageElement::Element(e) => e.opaque_regions(scale), - FrameResultDamageElement::Swapchain(e) => e.opaque_regions(scale), - } - } -} - -/// Error for [`RenderFrameResult::blit_frame_result`] -#[derive(Debug, thiserror::Error)] -pub enum BlitFrameResultError { - /// A render error occurred - #[error(transparent)] - Rendering(R), - /// A error occurred during exporting the buffer - #[error(transparent)] - Export(E), -} - -impl<'a, B, F, E> RenderFrameResult<'a, B, F, E> -where - B: Buffer, - F: Framebuffer, -{ - /// Get the damage of this frame for the specified dtr and age - pub fn damage_from_age<'d>( - &self, - damage_tracker: &'d mut OutputDamageTracker, - age: usize, - filter: impl IntoIterator, - ) -> Result<(Option<&'d Vec>>, RenderElementStates), OutputNoMode> - where - E: Element, - { - #[allow(clippy::mutable_key_type)] - let filter_ids: HashSet = filter.into_iter().collect(); - - let mut elements: Vec> = - Vec::with_capacity(usize::from(self.cursor_element.is_some()) + self.overlay_elements.len() + 1); - if let Some(cursor) = self.cursor_element { - if !filter_ids.contains(cursor.id()) { - elements.push(FrameResultDamageElement::Element(cursor)); - } - } - - elements.extend( - self.overlay_elements - .iter() - .filter(|e| !filter_ids.contains(e.id())) - .map(|e| FrameResultDamageElement::Element(*e)), - ); - - let primary_render_element = match &self.primary_element { - PrimaryPlaneElement::Swapchain(PrimarySwapchainElement { - slot, - transform, - damage, - .. - }) => FrameResultDamageElement::Swapchain(SwapchainElement { - id: self.primary_plane_element_id.clone(), - transform: *transform, - slot: match &slot.0.buffer { - ScanoutBuffer::Swapchain(slot) => slot, - _ => unreachable!(), - }, - damage, - }), - PrimaryPlaneElement::Element(e) => FrameResultDamageElement::Element(*e), - }; - - elements.push(primary_render_element); - - damage_tracker.damage_output(age, &elements) - } -} - -impl<'a, B, F, E> RenderFrameResult<'a, B, F, E> -where - B: Buffer + AsDmabuf, - ::Error: std::fmt::Debug, - F: Framebuffer, -{ - /// Blit the frame result into a currently bound buffer - #[allow(clippy::too_many_arguments)] - pub fn blit_frame_result( - &self, - size: impl Into>, - transform: Transform, - scale: impl Into>, - renderer: &mut R, - damage: impl IntoIterator>, - filter: impl IntoIterator, - ) -> Result::Error, ::Error>> - where - R: Renderer + Blit, - ::TextureId: 'static, - E: Element + RenderElement, - { - let size = size.into(); - let scale = scale.into(); - #[allow(clippy::mutable_key_type)] - let filter_ids: HashSet = filter.into_iter().collect(); - let damage = damage.into_iter().collect::>(); - - // If we have no damage we can exit early - if damage.is_empty() { - return Ok(SyncPoint::signaled()); - } - - let mut opaque_regions: Vec> = Vec::new(); - - let mut elements_to_render: Vec<&'a E> = - Vec::with_capacity(usize::from(self.cursor_element.is_some()) + self.overlay_elements.len() + 1); - - if let Some(cursor_element) = self.cursor_element.as_ref() { - if !filter_ids.contains(cursor_element.id()) { - elements_to_render.push(*cursor_element); - opaque_regions.extend(cursor_element.opaque_regions(scale)); - } - } - - for element in self - .overlay_elements - .iter() - .filter(|e| !filter_ids.contains(e.id())) - { - elements_to_render.push(element); - opaque_regions.extend(element.opaque_regions(scale)); - } - - let primary_dmabuf = match &self.primary_element { - PrimaryPlaneElement::Swapchain(PrimarySwapchainElement { slot, sync, .. }) => { - let dmabuf = match &slot.0.buffer { - ScanoutBuffer::Swapchain(slot) => slot.export().map_err(BlitFrameResultError::Export)?, - _ => unreachable!(), - }; - let size = dmabuf.size(); - let geometry = Rectangle::from_loc_and_size( - (0, 0), - size.to_logical(1, Transform::Normal).to_physical(1), - ); - opaque_regions.push(geometry); - Some((sync.clone(), dmabuf, geometry)) - } - PrimaryPlaneElement::Element(e) => { - elements_to_render.push(*e); - opaque_regions.extend(e.opaque_regions(scale)); - None - } - }; - - let clear_damage = - Rectangle::subtract_rects_many_in_place(damage.clone(), opaque_regions.iter().copied()); - - let mut sync: Option = None; - if !clear_damage.is_empty() { - trace!("clearing frame damage {:#?}", clear_damage); - - let mut frame = renderer - .render(size, transform) - .map_err(BlitFrameResultError::Rendering)?; - - frame - .clear(Color32F::BLACK, &clear_damage) - .map_err(BlitFrameResultError::Rendering)?; - - sync = Some(frame.finish().map_err(BlitFrameResultError::Rendering)?); - } - - // first do the potential blit - if let Some((sync, dmabuf, geometry)) = primary_dmabuf { - let blit_damage = damage - .iter() - .filter_map(|d| d.intersection(geometry)) - .collect::>(); - - trace!("blitting frame with damage: {:#?}", blit_damage); - - renderer.wait(&sync).map_err(BlitFrameResultError::Rendering)?; - for rect in blit_damage { - renderer - .blit_from( - dmabuf.clone(), - rect, - rect, - crate::backend::renderer::TextureFilter::Linear, - ) - .map_err(BlitFrameResultError::Rendering)?; - } - } - - // then render the remaining elements if any - if !elements_to_render.is_empty() { - trace!("drawing {} frame element(s)", elements_to_render.len()); - - let mut frame = renderer - .render(size, transform) - .map_err(BlitFrameResultError::Rendering)?; - - for element in elements_to_render.iter().rev() { - let src = element.src(); - let dst = element.geometry(scale); - let element_damage = damage - .iter() - .filter_map(|d| { - d.intersection(dst).map(|mut d| { - d.loc -= dst.loc; - d - }) - }) - .collect::>(); - - // no need to render without damage - if element_damage.is_empty() { - continue; - } - - trace!("drawing frame element with damage: {:#?}", element_damage); - - element - .draw(&mut frame, src, dst, &element_damage, &[]) - .map_err(BlitFrameResultError::Rendering)?; - } - - Ok(frame.finish().map_err(BlitFrameResultError::Rendering)?) - } else { - Ok(sync.unwrap_or_default()) - } - } -} - -impl<'a, B: Buffer + std::fmt::Debug, F: Framebuffer + std::fmt::Debug, E: std::fmt::Debug> std::fmt::Debug - for RenderFrameResult<'a, B, F, E> -{ - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("RenderFrameResult") - .field("is_empty", &self.is_empty) - .field("states", &self.states) - .field("primary_element", &self.primary_element) - .field("overlay_elements", &self.overlay_elements) - .field("cursor_element", &self.cursor_element) - .finish() - } -} - #[derive(Debug)] struct CursorState { allocator: GbmAllocator, @@ -1485,7 +933,7 @@ impl From<&PlaneInfo> for PlaneAssignment { } struct PendingFrame::Buffer>, U> { - frame: Frame, + frame: CompositorFrameState, user_data: U, } @@ -1533,7 +981,7 @@ enum PreparedFrameKind { } struct PreparedFrame::Buffer>> { - frame: Frame, + frame: CompositorFrameState, kind: PreparedFrameKind, } @@ -1561,6 +1009,22 @@ where } } +bitflags::bitflags! { + /// Possible flags for a DMA buffer + #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] + pub struct FrameMode: u32 { + /// Allow to realize the frame by compositing elements on the primary plane + const COMPOSITE = 1; + const PRIMARY_PLANE_SCANOUT = 2; + const OVERLAY_PLANE_SCANOUT = 4; + const CURSOR_PLANE_SCANOUT = 8; + /// Allow to realize the frame by assigning elements on planes + const SCANOUT = Self::PRIMARY_PLANE_SCANOUT.bits() | Self::OVERLAY_PLANE_SCANOUT.bits() | Self::CURSOR_PLANE_SCANOUT.bits(); + + const ALL = Self::COMPOSITE.bits() | Self::SCANOUT.bits(); + } +} + /// Composite an output using a combination of planes and rendering /// /// see the [`module docs`](crate::backend::drm::compositor) for more information @@ -1581,13 +1045,12 @@ where primary_plane_element_id: Id, primary_plane_damage_bag: DamageBag, supports_fencing: bool, - direct_scanout: bool, reset_pending: bool, signaled_fence: Option>, framebuffer_exporter: F, - current_frame: Frame, + current_frame: CompositorFrameState, pending_frame: Option>, queued_frame: Option>, next_frame: Option>, @@ -1597,10 +1060,8 @@ where cursor_size: Size, cursor_state: Option>, - element_states: - IndexMap>::Framebuffer>>>, - previous_element_states: - IndexMap>::Framebuffer>>>, + element_states: IndexMap>::Framebuffer>>, + previous_element_states: IndexMap>::Framebuffer>>, opaque_regions: Vec>, element_opaque_regions_workhouse: Vec>, @@ -1645,7 +1106,7 @@ where planes: Option, mut allocator: A, framebuffer_exporter: F, - color_formats: &[DrmFourcc], + color_formats: impl IntoIterator, renderer_formats: impl IntoIterator, cursor_size: Size, gbm: Option>, @@ -1737,9 +1198,9 @@ where allocator, &framebuffer_exporter, renderer_formats.clone(), - *format, + format, ) { - Ok((swapchain, current_frame, is_opaque)) => { + Ok((swapchain, is_opaque)) => { let cursor_state = gbm.map(|gbm| { #[cfg(feature = "renderer_pixman")] let pixman_renderer = match PixmanRenderer::new() { @@ -1763,65 +1224,212 @@ where }); let overlay_plane_element_ids = OverlayPlaneElementIds::from_planes(&planes); + let current_frame = FrameState::from_planes(surface.plane(), &planes); + + let drm_renderer = DrmCompositor { + primary_plane_element_id: Id::new(), + primary_plane_damage_bag: DamageBag::new(4), + primary_is_opaque: is_opaque, + reset_pending: true, + signaled_fence, + current_frame, + pending_frame: None, + queued_frame: None, + next_frame: None, + swapchain, + framebuffer_exporter, + cursor_size, + cursor_state, + surface, + damage_tracker, + output_mode_source, + planes, + overlay_plane_element_ids, + element_states: IndexMap::new(), + previous_element_states: IndexMap::new(), + opaque_regions: Vec::new(), + element_opaque_regions_workhouse: Vec::new(), + supports_fencing, + debug_flags: DebugFlags::empty(), + span, + }; + + return Ok(drm_renderer); + } + Err((alloc, err)) => { + warn!("Preferred format {} not available: {:?}", format, err); + allocator = alloc; + error = Some(err); + } + } + } + Err(error.unwrap()) + } + + pub fn with_format( + output_mode_source: impl Into + Debug, + surface: DrmSurface, + planes: Option, + allocator: A, + framebuffer_exporter: F, + code: DrmFourcc, + modifiers: impl IntoIterator, + cursor_size: Size, + gbm: Option>, + ) -> FrameResult { + let signaled_fence = match surface.create_syncobj(true) { + Ok(signaled_syncobj) => match surface.syncobj_to_fd(signaled_syncobj, true) { + Ok(signaled_fence) => { + let _ = surface.destroy_syncobj(signaled_syncobj); + Some(Arc::new(signaled_fence)) + } + Err(err) => { + tracing::warn!(?err, "failed to export signaled syncobj"); + let _ = surface.destroy_syncobj(signaled_syncobj); + None + } + }, + Err(err) => { + tracing::warn!(?err, "failed to create signaled syncobj"); + None + } + }; + + let span = info_span!( + parent: None, + "drm_compositor", + device = ?surface.dev_path(), + crtc = ?surface.crtc(), + ); + + let output_mode_source = output_mode_source.into(); + + let surface = Arc::new(surface); + let mut planes = match planes { + Some(planes) => planes, + None => surface.planes().clone(), + }; + + // We do not support direct scan-out on legacy + if surface.is_legacy() { + planes.cursor.clear(); + planes.overlay.clear(); + } + + // The selection algorithm expects the planes to be ordered form front to back + planes + .overlay + .sort_by_key(|p| std::cmp::Reverse(p.zpos.unwrap_or_default())); + + let driver = surface.get_driver().map_err(|err| { + FrameError::DrmError(DrmError::Access(AccessError { + errmsg: "Failed to query drm driver", + dev: surface.dev_path(), + source: err, + })) + })?; + // `IN_FENCE_FD` makes commit fail on Nvidia driver + // https://github.com/NVIDIA/open-gpu-kernel-modules/issues/622 + let is_nvidia = driver.name().to_string_lossy().to_lowercase().contains("nvidia") + || driver + .description() + .to_string_lossy() + .to_lowercase() + .contains("nvidia"); + + let cursor_size = Size::from((cursor_size.w as i32, cursor_size.h as i32)); + let damage_tracker = OutputDamageTracker::from_mode_source(output_mode_source.clone()); + let supports_fencing = !surface.is_legacy() + && surface + .get_driver_capability(DriverCapability::SyncObj) + .map(|val| val != 0) + .map_err(|err| { + 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")? + && !(is_nvidia && nvidia_drm_version().unwrap_or((0, 0, 0)) < (560, 35, 3)); - let drm_renderer = DrmCompositor { - primary_plane_element_id: Id::new(), - primary_plane_damage_bag: DamageBag::new(4), - primary_is_opaque: is_opaque, - direct_scanout: true, - reset_pending: true, - signaled_fence, - current_frame, - pending_frame: None, - queued_frame: None, - next_frame: None, - swapchain, - framebuffer_exporter, - cursor_size, - cursor_state, - surface, - damage_tracker, - output_mode_source, - planes, - overlay_plane_element_ids, - element_states: IndexMap::new(), - previous_element_states: IndexMap::new(), - opaque_regions: Vec::new(), - element_opaque_regions_workhouse: Vec::new(), - supports_fencing, - debug_flags: DebugFlags::empty(), - span, - }; + let (swapchain, is_opaque) = Self::test_format( + &surface, + supports_fencing, + &planes, + allocator, + &framebuffer_exporter, + code, + modifiers, + ) + .map_err(|(_, err)| err)?; - return Ok(drm_renderer); - } - Err((alloc, err)) => { - warn!("Preferred format {} not available: {:?}", format, err); - allocator = alloc; - error = Some(err); + let cursor_state = gbm.map(|gbm| { + #[cfg(feature = "renderer_pixman")] + let pixman_renderer = match PixmanRenderer::new() { + Ok(pixman_renderer) => Some(pixman_renderer), + Err(err) => { + tracing::warn!(?err, "failed to initialize pixman renderer for cursor plane"); + None } + }; + + let cursor_allocator = + GbmAllocator::new(gbm.clone(), GbmBufferFlags::CURSOR | GbmBufferFlags::WRITE); + CursorState { + allocator: cursor_allocator, + framebuffer_exporter: gbm, + previous_output_scale: None, + previous_output_transform: None, + #[cfg(feature = "renderer_pixman")] + pixman_renderer, } - } - Err(error.unwrap()) - } + }); - /// Enable or disable direct scanout. - /// - /// This is mostly useful for debugging purposes. - pub fn use_direct_scanout(&mut self, enabled: bool) { - self.direct_scanout = enabled; + let overlay_plane_element_ids = OverlayPlaneElementIds::from_planes(&planes); + let current_frame = FrameState::from_planes(surface.plane(), &planes); + + let drm_renderer = DrmCompositor { + primary_plane_element_id: Id::new(), + primary_plane_damage_bag: DamageBag::new(4), + primary_is_opaque: is_opaque, + reset_pending: true, + signaled_fence, + current_frame, + pending_frame: None, + queued_frame: None, + next_frame: None, + swapchain, + framebuffer_exporter, + cursor_size, + cursor_state, + surface, + damage_tracker, + output_mode_source, + planes, + overlay_plane_element_ids, + element_states: IndexMap::new(), + previous_element_states: IndexMap::new(), + opaque_regions: Vec::new(), + element_opaque_regions_workhouse: Vec::new(), + supports_fencing, + debug_flags: DebugFlags::empty(), + span, + }; + + Ok(drm_renderer) } - fn find_supported_format( - drm: Arc, + fn test_format( + drm: &DrmSurface, supports_fencing: bool, planes: &Planes, allocator: A, framebuffer_exporter: &F, - mut renderer_formats: Vec, code: DrmFourcc, - ) -> Result<(Swapchain, Frame, bool), (A, FrameErrorType)> { - // select a format + modifiers: impl IntoIterator, + ) -> Result<(Swapchain, bool), (A, FrameErrorType)> { + let modifiers = modifiers.into_iter().collect::>(); let mut plane_formats = drm.plane_info().formats.iter().copied().collect::>(); let opaque_code = get_opaque(code).unwrap_or(code); @@ -1832,63 +1440,25 @@ where return Err((allocator, FrameError::NoSupportedPlaneFormat)); } plane_formats.retain(|fmt| fmt.code == code || fmt.code == opaque_code); - renderer_formats.retain(|fmt| fmt.code == code); - trace!("Plane formats: {:?}", plane_formats); - trace!("Renderer formats: {:?}", renderer_formats); + if plane_formats.is_empty() { + return Err((allocator, FrameError::NoSupportedPlaneFormat)); + } let plane_modifiers = plane_formats .iter() .map(|fmt| fmt.modifier) .collect::>(); - let renderer_modifiers = renderer_formats - .iter() - .map(|fmt| fmt.modifier) - .collect::>(); - debug!( - "Remaining intersected modifiers: {:?}", - plane_modifiers - .intersection(&renderer_modifiers) - .collect::>() - ); - if plane_formats.is_empty() { + let swapchain_modifiers = plane_modifiers + .intersection(&modifiers) + .copied() + .collect::>(); + + if swapchain_modifiers.is_empty() { return Err((allocator, FrameError::NoSupportedPlaneFormat)); - } else if renderer_formats.is_empty() { - return Err((allocator, FrameError::NoSupportedRendererFormat)); } - let formats = { - // Special case: if a format supports explicit LINEAR (but no implicit Modifiers) - // and the other doesn't support any modifier, force Implicit. - // This should at least result in a working pipeline possibly with a linear buffer, - // but we cannot be sure. - if (plane_formats.len() == 1 - && plane_formats.iter().next().unwrap().modifier == DrmModifier::Invalid - && renderer_formats - .iter() - .all(|x| x.modifier != DrmModifier::Invalid) - && renderer_formats.iter().any(|x| x.modifier == DrmModifier::Linear)) - || (renderer_formats.len() == 1 - && renderer_formats.first().unwrap().modifier == DrmModifier::Invalid - && plane_formats.iter().all(|x| x.modifier != DrmModifier::Invalid) - && plane_formats.iter().any(|x| x.modifier == DrmModifier::Linear)) - { - vec![DrmFormat { - code, - modifier: DrmModifier::Invalid, - }] - } else { - plane_modifiers - .intersection(&renderer_modifiers) - .cloned() - .map(|modifier| DrmFormat { code, modifier }) - .collect::>() - } - }; - debug!("Testing Formats: {:?}", formats); - - let modifiers = formats.iter().map(|x| x.modifier).collect::>(); let mode = drm.pending_mode(); let mut swapchain: Swapchain = Swapchain::new( @@ -1896,7 +1466,7 @@ where mode.size().0 as u32, mode.size().1 as u32, code, - modifiers, + swapchain_modifiers, ); // Test format @@ -1922,14 +1492,15 @@ where Ok(None) => return Err((swapchain.allocator, FrameError::NoFramebuffer)), Err(err) => return Err((swapchain.allocator, FrameError::FramebufferExport(err))), }; - buffer - .userdata() - .insert_if_missing(|| OwnedFramebuffer::new(DrmFramebuffer::Exporter(fb_buffer))); + buffer.userdata().insert_if_missing(|| { + let init = CachedDrmFramebuffer::new(DrmFramebuffer::Exporter(fb_buffer)); + init + }); let mode = drm.pending_mode(); let handle = buffer .userdata() - .get::>::Framebuffer>>>() + .get::>::Framebuffer>>() .unwrap() .clone(); @@ -1956,10 +1527,10 @@ where alpha: 1.0, format: buffer.format(), }, - buffer: Owned::from(DrmScanoutBuffer { - buffer: ScanoutBuffer::Swapchain(buffer), + buffer: DrmScanoutBuffer { + buffer: ScanoutBuffer::Swapchain(Arc::new(buffer)), fb: handle, - }), + }, damage_clips: None, plane_claim, sync: None, @@ -1967,13 +1538,10 @@ where }; match current_frame_state.test_state(&drm, supports_fencing, drm.plane(), plane_state, true) { - Ok(_) => { - debug!("Chosen format: {:?}", dmabuf.format()); - Ok((swapchain, current_frame_state, use_opaque)) - } + Ok(_) => Ok((swapchain, use_opaque)), Err(err) => { warn!( - "Mode-setting failed with automatically selected buffer format {:?}: {}", + "Mode-setting failed with buffer format {:?}: {}", dmabuf.format(), err ); @@ -1982,6 +1550,98 @@ where } } + fn find_supported_format( + drm: Arc, + supports_fencing: bool, + planes: &Planes, + allocator: A, + framebuffer_exporter: &F, + mut renderer_formats: Vec, + code: DrmFourcc, + ) -> Result<(Swapchain, bool), (A, FrameErrorType)> { + // select a format + let mut plane_formats = drm.plane_info().formats.iter().copied().collect::>(); + + let opaque_code = get_opaque(code).unwrap_or(code); + if !plane_formats + .iter() + .any(|fmt| fmt.code == code || fmt.code == opaque_code) + { + return Err((allocator, FrameError::NoSupportedPlaneFormat)); + } + plane_formats.retain(|fmt| fmt.code == code || fmt.code == opaque_code); + renderer_formats.retain(|fmt| fmt.code == code); + + trace!("Plane formats: {:?}", plane_formats); + trace!("Renderer formats: {:?}", renderer_formats); + + let plane_modifiers = plane_formats + .iter() + .map(|fmt| fmt.modifier) + .collect::>(); + let renderer_modifiers = renderer_formats + .iter() + .map(|fmt| fmt.modifier) + .collect::>(); + debug!( + "Remaining intersected modifiers: {:?}", + plane_modifiers + .intersection(&renderer_modifiers) + .collect::>() + ); + + if plane_formats.is_empty() { + return Err((allocator, FrameError::NoSupportedPlaneFormat)); + } else if renderer_formats.is_empty() { + return Err((allocator, FrameError::NoSupportedRendererFormat)); + } + + let formats = { + // Special case: if a format supports explicit LINEAR (but no implicit Modifiers) + // and the other doesn't support any modifier, force Implicit. + // This should at least result in a working pipeline possibly with a linear buffer, + // but we cannot be sure. + if (plane_formats.len() == 1 + && plane_formats.iter().next().unwrap().modifier == DrmModifier::Invalid + && renderer_formats + .iter() + .all(|x| x.modifier != DrmModifier::Invalid) + && renderer_formats.iter().any(|x| x.modifier == DrmModifier::Linear)) + || (renderer_formats.len() == 1 + && renderer_formats.first().unwrap().modifier == DrmModifier::Invalid + && plane_formats.iter().all(|x| x.modifier != DrmModifier::Invalid) + && plane_formats.iter().any(|x| x.modifier == DrmModifier::Linear)) + { + vec![DrmFormat { + code, + modifier: DrmModifier::Invalid, + }] + } else { + plane_modifiers + .intersection(&renderer_modifiers) + .cloned() + .map(|modifier| DrmFormat { code, modifier }) + .collect::>() + } + }; + + debug!("Testing Formats: {:?}", formats); + + let modifiers = formats.iter().map(|x| x.modifier).collect::>(); + + let (swapchain, use_opaque) = Self::test_format( + &*drm, + supports_fencing, + planes, + allocator, + framebuffer_exporter, + code, + modifiers, + )?; + + Ok((swapchain, use_opaque)) + } + /// Render the next frame /// /// - `elements` for this frame in front-to-back order @@ -1992,6 +1652,7 @@ where renderer: &mut R, elements: &'a [E], clear_color: impl Into, + frame_mode: FrameMode, ) -> Result, RenderFrameErrorType> where E: RenderElement, @@ -2047,7 +1708,7 @@ where // it and use the Slot userdata to cache it let maybe_buffer = primary_plane_buffer .userdata() - .get::>::Framebuffer>>>(); + .get::>::Framebuffer>>(); if maybe_buffer.is_none() { let fb_buffer = self .framebuffer_exporter @@ -2060,13 +1721,13 @@ where .ok_or(FrameError::NoFramebuffer)?; primary_plane_buffer .userdata() - .insert_if_missing(|| OwnedFramebuffer::new(DrmFramebuffer::Exporter(fb_buffer))); + .insert_if_missing(|| CachedDrmFramebuffer::new(DrmFramebuffer::Exporter(fb_buffer))); } // This unwrap is safe as we error out above if we were unable to export a framebuffer let fb = primary_plane_buffer .userdata() - .get::>::Framebuffer>>>() + .get::>::Framebuffer>>() .unwrap() .clone(); @@ -2080,7 +1741,10 @@ where // So first we want to create a clean state, for that we have to reset all overlay and cursor planes // to nothing. We only want to test if the primary plane alone can be used for scan-out. - let mut next_frame_state = { + let mut next_frame_state: FrameState< + ::Buffer, + ::Buffer>>::Framebuffer, + > = { let previous_state = self .pending_frame .as_ref() @@ -2112,7 +1776,10 @@ where error!("failed to claim primary plane"); FrameError::PrimaryPlaneClaimFailed })?; - let primary_plane_state = PlaneState { + let primary_plane_state: PlaneState< + ::Buffer, + ::Buffer>>::Framebuffer, + > = PlaneState { skip: false, needs_test: false, element_state: None, @@ -2125,10 +1792,10 @@ where alpha: 1.0, format: primary_plane_buffer.format(), }, - buffer: Owned::from(DrmScanoutBuffer { - buffer: ScanoutBuffer::Swapchain(primary_plane_buffer), + buffer: DrmScanoutBuffer { + buffer: ScanoutBuffer::Swapchain(Arc::new(primary_plane_buffer)), fb, - }), + }, damage_clips: None, plane_claim, sync: None, @@ -2322,6 +1989,7 @@ where output_transform, output_geometry, try_assign_primary_plane, + frame_mode, ) { Ok(direct_scan_out_plane) => { match direct_scan_out_plane.type_ { @@ -2460,7 +2128,7 @@ where let render = next_frame_state .plane_buffer(self.surface.plane()) - .map(|config| matches!(&config.buffer, ScanoutBuffer::Swapchain(_))) + .map(|config| matches!(config.buffer, ScanoutBuffer::Swapchain(_))) .unwrap_or(false); if render { @@ -2740,6 +2408,41 @@ where Ok(()) } + pub fn commit_frame(&mut self) -> FrameResult<(), A, F> { + if !self.surface.is_active() { + return Err(FrameErrorType::::DrmError(DrmError::DeviceInactive)); + } + + let mut prepared_frame = self.next_frame.take().ok_or(FrameErrorType::::EmptyFrame)?; + if prepared_frame.is_empty() { + return Err(FrameErrorType::::EmptyFrame); + } + + if let Some(plane_state) = prepared_frame.frame.plane_state(self.surface.plane()) { + if !plane_state.skip { + let slot = plane_state.buffer().and_then(|config| match &config.buffer { + ScanoutBuffer::Swapchain(slot) => Some(slot), + _ => None, + }); + + if let Some(slot) = slot { + self.swapchain.submitted(slot); + } + } + } + + let flip = prepared_frame + .frame + .commit(&self.surface, self.supports_fencing, false, true); + + if flip.is_ok() { + self.queued_frame = None; + self.pending_frame = None; + } + + self.handle_flip(prepared_frame, None, flip) + } + /// Re-evaluates the current state of the crtc and forces calls to [`render_frame`](DrmCompositor::render_frame) /// to return `false` for [`RenderFrameResult::is_empty`] until a frame is queued with [`queue_frame`](DrmCompositor::queue_frame). /// @@ -2773,13 +2476,22 @@ where .page_flip(&self.surface, self.supports_fencing, allow_partial_update, true) }; + self.handle_flip(prepared_frame, Some(user_data), flip) + } + + fn handle_flip( + &mut self, + prepared_frame: PreparedFrame, + user_data: Option, + flip: Result<(), crate::backend::drm::error::Error>, + ) -> FrameResult<(), A, F> { match flip { Ok(_) => { if prepared_frame.kind == PreparedFrameKind::Full { self.reset_pending = false; } - self.pending_frame = Some(PendingFrame { + self.pending_frame = user_data.map(|user_data| PendingFrame { frame: prepared_frame.frame, user_data, }); @@ -2956,6 +2668,29 @@ where self.swapchain.format() } + pub fn set_format( + &mut self, + allocator: A, + code: DrmFourcc, + modifiers: impl IntoIterator, + ) -> Result<(), FrameErrorType> { + let (swapchain, is_oapque) = Self::test_format( + &self.surface, + self.supports_fencing, + &self.planes, + allocator, + &self.framebuffer_exporter, + code, + modifiers, + ) + .map_err(|(_, err)| err)?; + + self.swapchain = swapchain; + self.primary_is_opaque = is_oapque; + + Ok(()) + } + /// Change the output mode source. pub fn set_output_mode_source(&mut self, output_mode_source: OutputModeSource) { // Avoid clearing damage if mode source did not change. @@ -2977,23 +2712,21 @@ where element_zindex: usize, element_geometry: Rectangle, element_is_opaque: bool, - element_states: &mut IndexMap< - Id, - ElementState>::Framebuffer>>, - >, + element_states: &mut IndexMap>::Framebuffer>>, primary_plane_elements: &[&'a E], scale: Scale, - frame_state: &mut Frame, + frame_state: &mut CompositorFrameState, output_transform: Transform, output_geometry: Rectangle, try_assign_primary_plane: bool, + frame_mode: FrameMode, ) -> Result> where R: Renderer + Bind, E: RenderElement, { // Check if we have a free plane, otherwise we can exit early - if !self.direct_scanout { + if !frame_mode.intersects(FrameMode::SCANOUT) { trace!( "skipping direct scan-out for element {:?}, no free planes", element.id() @@ -3014,6 +2747,7 @@ where frame_state, output_transform, output_geometry, + frame_mode, ) { Ok(plane) => { trace!( @@ -3036,6 +2770,7 @@ where frame_state, output_transform, output_geometry, + frame_mode, ) { trace!("assigned element {:?} to cursor {:?}", element.id(), plane.handle); return Ok(plane); @@ -3053,6 +2788,7 @@ where frame_state, output_transform, output_geometry, + frame_mode, ) { Ok(plane) => { trace!( @@ -3077,19 +2813,21 @@ where element: &'a E, element_zindex: usize, element_geometry: Rectangle, - element_states: &mut IndexMap< - Id, - ElementState>::Framebuffer>>, - >, + element_states: &mut IndexMap>::Framebuffer>>, scale: Scale, - frame_state: &mut Frame, + frame_state: &mut CompositorFrameState, output_transform: Transform, output_geometry: Rectangle, + frame_mode: FrameMode, ) -> Result> where R: Renderer, E: RenderElement, { + if !frame_mode.contains(FrameMode::PRIMARY_PLANE_SCANOUT) { + return Err(None); + } + if frame_state .plane_state(self.surface.plane()) .map(|state| state.element_state.is_some()) @@ -3157,14 +2895,19 @@ where element_zindex: usize, element_geometry: Rectangle, scale: Scale, - frame_state: &mut Frame, + frame_state: &mut CompositorFrameState, output_transform: Transform, output_geometry: Rectangle, + frame_mode: FrameMode, ) -> Option where R: Renderer, E: RenderElement, { + if !frame_mode.contains(FrameMode::CURSOR_PLANE_SCANOUT) { + return None; + } + let Some(cursor_state) = self.cursor_state.as_mut() else { trace!("no cursor state, skipping cursor rendering"); return None; @@ -3520,10 +3263,10 @@ where transform: Transform::Normal, format: framebuffer.format(), }, - buffer: Owned::from(DrmScanoutBuffer { - buffer: ScanoutBuffer::Cursor(cursor_buffer), - fb: OwnedFramebuffer::new(DrmFramebuffer::Gbm(framebuffer)), - }), + buffer: DrmScanoutBuffer { + buffer: ScanoutBuffer::Cursor(Arc::new(cursor_buffer)), + fb: CachedDrmFramebuffer::new(DrmFramebuffer::Gbm(framebuffer)), + }, damage_clips: None, plane_claim, sync: None, @@ -3597,21 +3340,16 @@ where element: &E, element_zindex: usize, element_geometry: Rectangle, - element_states: &'a mut IndexMap< - Id, - ElementState>::Framebuffer>>, - >, - frame_state: &mut Frame, + element_states: &'a mut IndexMap>::Framebuffer>>, + frame_state: &mut CompositorFrameState, output_transform: Transform, output_geometry: Rectangle, allow_opaque_fallback: bool, ) -> Result< ElementPlaneConfig< 'a, - DrmScanoutBuffer< - ::Buffer, - ::Buffer>>::Framebuffer, - >, + ::Buffer, + ::Buffer>>::Framebuffer, >, ExportBufferError, > @@ -3652,7 +3390,9 @@ where }, ); } - let element_fb_cache = element_states + let element_fb_cache: &mut ElementFramebufferCache< + ::Buffer>>::Framebuffer, + > = element_states .get_mut(element_id) .map(|state| &mut state.fb_cache) .unwrap(); @@ -3677,7 +3417,7 @@ where ExportBufferError::ExportFailed }) .and_then(|fb| { - fb.map(|fb| OwnedFramebuffer::new(DrmFramebuffer::Exporter(fb))) + fb.map(|fb| CachedDrmFramebuffer::new(DrmFramebuffer::Exporter(fb))) .ok_or(ExportBufferError::Unsupported) }); @@ -3698,7 +3438,8 @@ where ); } - let fb = element_fb_cache.get(&element_cache_key).unwrap()?; + let fb: &CachedDrmFramebuffer<::Buffer>>::Framebuffer> = + element_fb_cache.get(&element_cache_key).unwrap()?; let src = element.src(); let dst = output_transform.transform_rect_in(element_geometry, &output_geometry.size); @@ -3717,12 +3458,13 @@ where transform, format: fb.format(), }; - let buffer = ScanoutBuffer::from_underlying_storage(underlying_storage) - .map(|buffer| { - Owned::from(DrmScanoutBuffer { - fb: fb.clone(), - buffer, - }) + let buffer: DrmScanoutBuffer< + ::Buffer, + ::Buffer>>::Framebuffer, + > = ScanoutBuffer::from_underlying_storage(underlying_storage) + .map(|buffer| DrmScanoutBuffer { + fb: fb.clone(), + buffer, }) .ok_or(ExportBufferError::Unsupported)?; @@ -3856,20 +3598,22 @@ where element_zindex: usize, element_geometry: Rectangle, element_is_opaque: bool, - element_states: &mut IndexMap< - Id, - ElementState>::Framebuffer>>, - >, + element_states: &mut IndexMap>::Framebuffer>>, primary_plane_elements: &[&'a E], scale: Scale, - frame_state: &mut Frame, + frame_state: &mut CompositorFrameState, output_transform: Transform, output_geometry: Rectangle, + frame_mode: FrameMode, ) -> Result> where R: Renderer, E: RenderElement, { + if !frame_mode.contains(FrameMode::OVERLAY_PLANE_SCANOUT) { + return Err(None); + } + let element_id = element.id(); // Check if we have a free plane, otherwise we can exit early @@ -3941,10 +3685,8 @@ where let mut test_overlay_plane = |plane: &PlaneInfo, element_config: &ElementPlaneConfig< '_, - DrmScanoutBuffer< - ::Buffer, - ::Buffer>>::Framebuffer, - >, + ::Buffer, + ::Buffer>>::Framebuffer, >| { // something is already assigned to our overlay plane if frame_state.is_assigned(plane.handle) { @@ -4057,14 +3799,12 @@ where element: &E, element_config: &ElementPlaneConfig< '_, - DrmScanoutBuffer< - ::Buffer, - ::Buffer>>::Framebuffer, - >, + ::Buffer, + ::Buffer>>::Framebuffer, >, plane: &PlaneInfo, scale: Scale, - frame_state: &mut Frame, + frame_state: &mut CompositorFrameState, ) -> Result> where R: Renderer, @@ -4424,43 +4164,43 @@ where } } -struct OwnedFramebuffer(Rc); +struct CachedDrmFramebuffer(Arc>); -impl PartialEq for OwnedFramebuffer { +impl PartialEq for CachedDrmFramebuffer { #[inline] fn eq(&self, other: &Self) -> bool { AsRef::::as_ref(&self) == AsRef::::as_ref(&other) } } -impl std::fmt::Debug for OwnedFramebuffer { +impl std::fmt::Debug for CachedDrmFramebuffer { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_tuple("OwnedFramebuffer").field(&self.0).finish() + f.debug_tuple("CachedDrmFramebuffer").field(&self.0).finish() } } -impl OwnedFramebuffer { +impl CachedDrmFramebuffer { #[inline] - fn new(buffer: B) -> Self { - OwnedFramebuffer(Rc::new(buffer)) + fn new(buffer: DrmFramebuffer) -> Self { + CachedDrmFramebuffer(Arc::new(buffer)) } } -impl Clone for OwnedFramebuffer { +impl Clone for CachedDrmFramebuffer { #[inline] fn clone(&self) -> Self { Self(self.0.clone()) } } -impl AsRef for OwnedFramebuffer { +impl AsRef for CachedDrmFramebuffer { #[inline] fn as_ref(&self) -> &framebuffer::Handle { (*self.0).as_ref() } } -impl Framebuffer for OwnedFramebuffer { +impl Framebuffer for CachedDrmFramebuffer { #[inline] fn format(&self) -> drm_fourcc::DrmFormat { (*self.0).format() diff --git a/src/backend/drm/compositor/dumb.rs b/src/backend/drm/exporter/dumb.rs similarity index 97% rename from src/backend/drm/compositor/dumb.rs rename to src/backend/drm/exporter/dumb.rs index 470ef771abf3..7543bfeb3277 100644 --- a/src/backend/drm/compositor/dumb.rs +++ b/src/backend/drm/exporter/dumb.rs @@ -46,6 +46,7 @@ impl ExportFramebuffer for DrmDeviceFd { #[inline] fn can_add_framebuffer(&self, buffer: &ExportBuffer<'_, DumbBuffer>) -> bool { match buffer { + #[cfg(feature = "wayland_frontend")] ExportBuffer::Wayland(_) => false, ExportBuffer::Allocator(_) => true, } diff --git a/src/backend/drm/compositor/gbm.rs b/src/backend/drm/exporter/gbm.rs similarity index 87% rename from src/backend/drm/compositor/gbm.rs rename to src/backend/drm/exporter/gbm.rs index 9ad6fc4d05a3..f10082318e35 100644 --- a/src/backend/drm/compositor/gbm.rs +++ b/src/backend/drm/exporter/gbm.rs @@ -34,6 +34,7 @@ impl ExportFramebuffer for gbm::Device { } #[inline] + #[cfg(feature = "wayland_frontend")] fn can_add_framebuffer(&self, buffer: &ExportBuffer<'_, GbmBuffer>) -> bool { match buffer { #[cfg(not(all(feature = "backend_egl", feature = "use_system_lib")))] @@ -50,4 +51,12 @@ impl ExportFramebuffer for gbm::Device { ExportBuffer::Allocator(_) => true, } } + + #[inline] + #[cfg(not(feature = "wayland_frontend"))] + fn can_add_framebuffer(&self, buffer: &ExportBuffer<'_, GbmBuffer>) -> bool { + match buffer { + ExportBuffer::Allocator(_) => true, + } + } } diff --git a/src/backend/drm/exporter/mod.rs b/src/backend/drm/exporter/mod.rs new file mode 100644 index 000000000000..eac377a13c91 --- /dev/null +++ b/src/backend/drm/exporter/mod.rs @@ -0,0 +1,106 @@ +use std::{cell::RefCell, rc::Rc, sync::{Arc, Mutex}}; + +use wayland_server::protocol::wl_buffer::WlBuffer; + +use crate::backend::{allocator::Buffer, renderer::element::UnderlyingStorage}; + +use super::{DrmDeviceFd, Framebuffer}; + +#[cfg(feature = "backend_drm")] +pub mod dumb; +#[cfg(feature = "backend_gbm")] +pub mod gbm; + +/// Possible buffers to export as a framebuffer using [`ExportFramebuffer`] +#[derive(Debug)] +pub enum ExportBuffer<'a, B: Buffer> { + /// A wayland buffer + Wayland(&'a WlBuffer), + /// A [`Allocator`] buffer + Allocator(&'a B), +} + +impl<'a, B: Buffer> ExportBuffer<'a, B> { + /// Create the export buffer from an [`UnderlyingStorage`] + #[inline] + pub fn from_underlying_storage(storage: &'a UnderlyingStorage<'_>) -> Option { + match storage { + #[cfg(feature = "wayland_frontend")] + UnderlyingStorage::Wayland(buffer) => Some(Self::Wayland(buffer)), + UnderlyingStorage::Memory { .. } => None, + } + } +} + +/// Export a [`ExportBuffer`] as a framebuffer +pub trait ExportFramebuffer +where + B: Buffer, +{ + /// Type of the framebuffer + type Framebuffer: Framebuffer; + + /// Type of the error + type Error: std::error::Error; + + /// Add a framebuffer for the specified buffer + fn add_framebuffer( + &self, + drm: &DrmDeviceFd, + buffer: ExportBuffer<'_, B>, + use_opaque: bool, + ) -> Result, Self::Error>; + + /// Test if the provided buffer is eligible for adding a framebuffer + fn can_add_framebuffer(&self, buffer: &ExportBuffer<'_, B>) -> bool; +} + +impl ExportFramebuffer for Arc> +where + F: ExportFramebuffer, + B: Buffer, +{ + type Framebuffer = >::Framebuffer; + type Error = >::Error; + + #[inline] + fn add_framebuffer( + &self, + drm: &DrmDeviceFd, + buffer: ExportBuffer<'_, B>, + use_opaque: bool, + ) -> Result, Self::Error> { + let guard = self.lock().unwrap(); + guard.add_framebuffer(drm, buffer, use_opaque) + } + + #[inline] + fn can_add_framebuffer(&self, buffer: &ExportBuffer<'_, B>) -> bool { + let guard = self.lock().unwrap(); + guard.can_add_framebuffer(buffer) + } +} + +impl ExportFramebuffer for Rc> +where + F: ExportFramebuffer, + B: Buffer, +{ + type Framebuffer = >::Framebuffer; + type Error = >::Error; + + #[inline] + fn add_framebuffer( + &self, + drm: &DrmDeviceFd, + buffer: ExportBuffer<'_, B>, + use_opaque: bool, + ) -> Result, Self::Error> { + self.borrow().add_framebuffer(drm, buffer, use_opaque) + } + + #[inline] + fn can_add_framebuffer(&self, buffer: &ExportBuffer<'_, B>) -> bool { + self.borrow().can_add_framebuffer(buffer) + } +} \ No newline at end of file diff --git a/src/backend/drm/format_selection.rs b/src/backend/drm/format_selection.rs new file mode 100644 index 000000000000..2742d55a42a0 --- /dev/null +++ b/src/backend/drm/format_selection.rs @@ -0,0 +1,40 @@ +use drm::control::{connector, crtc, Mode}; +use drm_fourcc::{DrmFormat, DrmFourcc, DrmModifier}; + +use crate::backend::allocator::Allocator; + +use super::{exporter::ExportFramebuffer, DrmDevice, DrmSurface}; + +pub struct SurfaceDefinition<'a> { + pub crtc: crtc::Handle, + pub mode: &'a Mode, + pub connectors: &'a [connector::Handle], +} + +pub fn select_formats<'a, A: Allocator, F: ExportFramebuffer>( + device: &DrmDevice, + allocator: &mut A, + framebuffer_exporter: &F, + surfaces: impl IntoIterator>, + color_formats: impl IntoIterator, + renderer_formats: impl IntoIterator, +) -> Vec> { + let surfaces = surfaces.into_iter(); + let color_formats = color_formats.into_iter().collect::>(); + let mut surface_formats: Vec> = Vec::with_capacity(surfaces.size_hint().0); + + // TODO: Okay, so the idea is that we first check if we have a legacy device or atomic + // In case we have a legacy device we just search for supported formats and test them accepting it might just flicker like hell... + // For atomic issue test commits with: + // - All planes except the primaries for the supplied surfaces disabled + // - All crtc disabled except the passed ones + // - Format by format...with some limit and then just use Invalid + + todo!("Implement the actual format selection...") +} + +pub struct SurfaceFormat<'a> { + pub surface: &'a DrmSurface, + pub code: DrmFourcc, + pub modifiers: Vec, +} diff --git a/src/backend/drm/mod.rs b/src/backend/drm/mod.rs index 84efda0d54e4..40b54f575a11 100644 --- a/src/backend/drm/mod.rs +++ b/src/backend/drm/mod.rs @@ -73,11 +73,14 @@ #[cfg(all(feature = "wayland_frontend", feature = "backend_gbm"))] pub mod compositor; pub(crate) mod device; +mod error; +pub mod format_selection; #[cfg(feature = "backend_drm")] pub mod dumb; -mod error; #[cfg(feature = "backend_gbm")] pub mod gbm; +pub mod exporter; +pub mod output; mod surface; diff --git a/src/backend/drm/output.rs b/src/backend/drm/output.rs new file mode 100644 index 000000000000..428dbffce4ee --- /dev/null +++ b/src/backend/drm/output.rs @@ -0,0 +1,458 @@ +use std::{ + collections::HashMap, + os::fd::AsFd, + sync::{Arc, Mutex, RwLock}, +}; + +use drm::control::{self, connector, crtc, Mode}; +use drm_fourcc::{DrmFormat, DrmFourcc, DrmModifier}; + +use crate::{ + backend::{ + allocator::{ + dmabuf::{AsDmabuf, Dmabuf}, + gbm::GbmDevice, + Allocator, + }, + renderer::{element::RenderElement, Bind, Color32F, DebugFlags, Renderer, Texture}, + }, + output::OutputModeSource, +}; + +use super::{ + compositor::{ + DrmCompositor, FrameError, FrameMode, FrameResult, RenderFrameError, RenderFrameErrorType, + RenderFrameResult, + }, + exporter::ExportFramebuffer, + DrmDevice, DrmError, Planes, +}; + +type CompositorList = Arc>>>>; + +pub struct DrmOutputManager +where + A: Allocator, + F: ExportFramebuffer<::Buffer>, + ::Buffer>>::Framebuffer: std::fmt::Debug + 'static, + G: AsFd + 'static, +{ + device: DrmDevice, + allocator: A, + exporter: F, + gbm: Option>, + compositor: CompositorList, + color_formats: Vec, + renderer_formats: Vec, +} + +impl DrmOutputManager +where + A: Allocator, + F: ExportFramebuffer<::Buffer>, + ::Buffer>>::Framebuffer: std::fmt::Debug + 'static, + G: AsFd + 'static, +{ + pub fn device(&self) -> &DrmDevice { + &self.device + } + + pub fn device_mut(&mut self) -> &mut DrmDevice { + &mut self.device + } +} + +impl DrmOutputManager +where + A: Allocator, + F: ExportFramebuffer<::Buffer>, + ::Buffer>>::Framebuffer: std::fmt::Debug + 'static, + G: AsFd + 'static, +{ + pub fn pause(&mut self) { + self.device.pause(); + } +} + +#[derive(thiserror::Error, Debug)] +pub enum DrmOutputManagerError +where + A: std::error::Error + Send + Sync + 'static, + B: std::error::Error + Send + Sync + 'static, + F: std::error::Error + Send + Sync + 'static, + R: Renderer, +{ + #[error("The specified CRTC {0:?} is already in use.")] + DuplicateCrtc(crtc::Handle), + #[error(transparent)] + Drm(#[from] DrmError), + #[error(transparent)] + Frame(FrameError), + #[error(transparent)] + RenderFrame(RenderFrameError), +} + +impl DrmOutputManager +where + A: Allocator + std::clone::Clone + std::fmt::Debug, + ::Buffer: AsDmabuf, + ::Error: Send + Sync + 'static, + <::Buffer as AsDmabuf>::Error: + std::marker::Send + std::marker::Sync + 'static, + F: ExportFramebuffer<::Buffer> + std::clone::Clone, + ::Buffer>>::Framebuffer: std::fmt::Debug + 'static, + ::Buffer>>::Error: + std::marker::Send + std::marker::Sync + 'static, + G: AsFd + std::clone::Clone + 'static, +{ + pub fn new( + device: DrmDevice, + allocator: A, + exporter: F, + gbm: Option>, + color_formats: impl IntoIterator, + renderer_formats: impl IntoIterator, + ) -> Self { + Self { + device, + allocator: allocator, + exporter, + gbm, + compositor: Default::default(), + color_formats: color_formats.into_iter().collect(), + renderer_formats: renderer_formats.into_iter().collect(), + } + } + + pub fn initialize_output( + &mut self, + renderer: &mut R, + crtc: crtc::Handle, + mode: control::Mode, + connectors: &[connector::Handle], + output_mode_source: impl Into + std::fmt::Debug, + planes: Option, + render_elements: RE, + ) -> Result< + DrmOutput, + DrmOutputManagerError< + ::Error, + <::Buffer as AsDmabuf>::Error, + ::Buffer>>::Error, + R, + >, + > + where + E: RenderElement, + R: Renderer + Bind, + ::TextureId: Texture + 'static, + RE: for<'r> Fn(&'r crtc::Handle) -> (&'r [E], Color32F), + { + let output_mode_source = output_mode_source.into(); + + let mut write_guard = self.compositor.write().unwrap(); + if write_guard.contains_key(&crtc) { + return Err(DrmOutputManagerError::DuplicateCrtc(crtc)); + } + + let surface = self + .device + .create_surface(crtc, mode, connectors) + .map_err(DrmOutputManagerError::Drm)?; + + let compositor = DrmCompositor::::new( + output_mode_source.clone(), + surface, + planes.clone(), + self.allocator.clone(), + self.exporter.clone(), + self.color_formats.iter().copied(), + self.renderer_formats.iter().copied(), + self.device.cursor_size(), + self.gbm.clone(), + ); + + match compositor { + Ok(compositor) => { + write_guard.insert(crtc, Mutex::new(compositor)); + } + Err(err) => { + tracing::warn!(?crtc, ?err, "failed to initialize crtc"); + // Okay, so this can fail for various reasons... + // + // Enabling an additional CRTC might fail because the bandwidth + // requirement is higher then supported with the current configuration. + // + // * Bandwidth limitation caused by overlay plane usage: + // Each overlay plane requires some certain bandwidth and we only + // test that during plane assignment implicitly through an atomic test. + // When trying to enable an additional CRTC we might hit some limit and the + // only way to resolve this might be to disable all overlay planes and + // retry enabling the CRTC. + // + // * Bandwidth limitation caused by the primary plane format: + // Different formats (might) require a higher memory bandwidth than others. + // This also applies to the same fourcc with different modifiers. For example + // the Intel CCS Formats use an additional plane to transport meta-data. + // So if we fail to enable an additional CRTC we might be able to resolve + // the issue by using a different format. Again the only way to know is by + // trying out what works. + // + // So the easiest option is to evaluate a new format and re-create all DrmCompositor + // without disabling the CRTCs. + + // TODO: Find out the *best* working combination of formats per active crtc + for (handle, compositor) in write_guard.iter_mut() { + let mut compositor = compositor.lock().unwrap(); + + let (elements, clear_color) = render_elements(handle); + compositor.reset_buffer_ages(); + compositor + .render_frame(renderer, elements, clear_color, FrameMode::COMPOSITE) + .map_err(DrmOutputManagerError::RenderFrame)?; + compositor.commit_frame().map_err(DrmOutputManagerError::Frame)?; + + let current_format = compositor.format(); + if let Err(err) = + compositor.set_format(self.allocator.clone(), current_format, [DrmModifier::Invalid]) + { + tracing::warn!(?err, "failed to set new format"); + } + + compositor + .render_frame(renderer, elements, clear_color, FrameMode::COMPOSITE) + .map_err(DrmOutputManagerError::RenderFrame)?; + compositor.commit_frame().map_err(DrmOutputManagerError::Frame)?; + } + + // :) Use the selected format instead of replicating DrmCompositor format selection... + let mut init_err = None; + for format in self.color_formats.iter() { + let surface = self + .device + .create_surface(crtc, mode, connectors) + .map_err(DrmOutputManagerError::Drm)?; + + let compositor = DrmCompositor::::with_format( + output_mode_source.clone(), + surface, + planes.clone(), + self.allocator.clone(), + self.exporter.clone(), + *format, + [DrmModifier::Invalid], + self.device.cursor_size(), + self.gbm.clone(), + ); + + match compositor { + Ok(compositor) => { + write_guard.insert(crtc, Mutex::new(compositor)); + break; + } + Err(err) => init_err = Some(err), + } + } + + if let Some(err) = init_err { + return Err(DrmOutputManagerError::Frame(err)); + } + } + }; + + let compositor = write_guard.get_mut(&crtc).unwrap(); + let mut compositor = compositor.lock().unwrap(); + let (elements, clear_color) = render_elements(&crtc); + compositor + .render_frame(renderer, elements, clear_color, FrameMode::COMPOSITE) + .map_err(DrmOutputManagerError::RenderFrame)?; + compositor.commit_frame().map_err(DrmOutputManagerError::Frame)?; + + Ok(DrmOutput { + crtc, + compositor: self.compositor.clone(), + }) + } + + pub fn reset_format( + &mut self, + renderer: &mut R, + render_elements: RE, + ) -> Result< + (), + DrmOutputManagerError< + ::Error, + <::Buffer as AsDmabuf>::Error, + ::Buffer>>::Error, + R, + >, + > + where + E: RenderElement, + R: Renderer + Bind, + ::TextureId: Texture + 'static, + RE: for<'r> Fn(&'r crtc::Handle) -> (&'r [E], Color32F), + { + // TODO: re-evaluate format and re-render if changed... + Ok(()) + } + + pub fn activate(&mut self, disable_connectors: bool) -> Result<(), DrmError> { + self.device.activate(disable_connectors)?; + + // We request a write guard here to guarantee unique access + let write_guard = self.compositor.write().unwrap(); + for compositor in write_guard.values() { + if let Err(err) = compositor.lock().unwrap().reset_state() { + tracing::warn!("Failed to reset drm surface state: {}", err); + } + } + + Ok(()) + } +} + +pub struct DrmOutput +where + A: Allocator, + F: ExportFramebuffer<::Buffer>, + ::Buffer>>::Framebuffer: std::fmt::Debug + 'static, + G: AsFd + 'static, +{ + crtc: crtc::Handle, + compositor: CompositorList, +} + +impl DrmOutput +where + A: Allocator + std::clone::Clone, + ::Buffer: AsDmabuf, + ::Error: Send + Sync + 'static, + <::Buffer as AsDmabuf>::Error: std::marker::Send + std::marker::Sync + 'static, + F: ExportFramebuffer<::Buffer> + std::clone::Clone, + ::Buffer>>::Framebuffer: std::fmt::Debug + 'static, + ::Buffer>>::Error: + std::marker::Send + std::marker::Sync + 'static, + G: AsFd + std::clone::Clone + 'static, +{ + /// Set the [`DebugFlags`] to use + /// + /// Note: This will reset the primary plane swapchain if + /// the flags differ from the current flags + pub fn set_debug_flags(&self, flags: DebugFlags) { + self.with_compositor(|compositor| compositor.set_debug_flags(flags)); + } + + /// Reset the underlying buffers + pub fn reset_buffers(&self) { + self.with_compositor(|compositor| compositor.reset_buffers()); + } + + pub fn frame_submitted(&self) -> FrameResult, A, F> { + self.with_compositor(|compositor| compositor.frame_submitted()) + } + + pub fn format(&self) -> DrmFourcc { + self.with_compositor(|compositor| compositor.format()) + } + + pub fn render_frame<'a, R, E>( + &mut self, + renderer: &mut R, + elements: &'a [E], + clear_color: impl Into, + frame_mode: FrameMode, + ) -> Result, RenderFrameErrorType> + where + E: RenderElement, + R: Renderer + Bind, + ::TextureId: Texture + 'static, + { + self.with_compositor(|compositor| { + compositor.render_frame(renderer, elements, clear_color, frame_mode) + }) + } + + pub fn queue_frame(&mut self, user_data: U) -> FrameResult<(), A, F> { + self.with_compositor(|compositor| compositor.queue_frame(user_data)) + } + + pub fn commit_frame(&mut self) -> FrameResult<(), A, F> { + self.with_compositor(|compositor| compositor.commit_frame()) + } + + pub fn use_mode( + &mut self, + renderer: &mut R, + mode: Mode, + render_elements: RE, + ) -> Result< + (), + DrmOutputManagerError< + ::Error, + <::Buffer as AsDmabuf>::Error, + ::Buffer>>::Error, + R, + >, + > + where + E: RenderElement, + R: Renderer + Bind, + ::TextureId: Texture + 'static, + RE: for<'r> Fn(&'r crtc::Handle) -> (&'r [E], Color32F), + { + let mut res = self.with_compositor(|compositor| compositor.use_mode(mode)); + if res.is_err() { + let mut write_guard = self.compositor.write().unwrap(); + + for (handle, compositor) in write_guard.iter_mut() { + let mut compositor = compositor.lock().unwrap(); + + let (elements, clear_color) = render_elements(handle); + compositor.reset_buffer_ages(); + compositor + .render_frame(renderer, elements, clear_color, FrameMode::COMPOSITE) + .map_err(DrmOutputManagerError::RenderFrame)?; + compositor.commit_frame().map_err(DrmOutputManagerError::Frame)?; + } + let compositor = write_guard.get_mut(&self.crtc).unwrap(); + let mut compositor = compositor.lock().unwrap(); + res = compositor.use_mode(mode); + } + res.map_err(DrmOutputManagerError::Frame) + } +} + +impl DrmOutput +where + A: Allocator, + F: ExportFramebuffer<::Buffer>, + ::Buffer>>::Framebuffer: std::fmt::Debug + 'static, + G: AsFd + 'static, +{ + pub fn crtc(&self) -> crtc::Handle { + self.crtc + } + + pub fn with_compositor(&self, f: T) -> R + where + T: FnOnce(&mut DrmCompositor) -> R, + { + let read_guard = self.compositor.read().unwrap(); + let mut compositor_guard = read_guard.get(&self.crtc).unwrap().lock().unwrap(); + f(&mut compositor_guard) + } +} + +impl Drop for DrmOutput +where + A: Allocator, + F: ExportFramebuffer<::Buffer>, + ::Buffer>>::Framebuffer: std::fmt::Debug + 'static, + G: AsFd + 'static, +{ + fn drop(&mut self) { + let mut write_guard = self.compositor.write().unwrap(); + write_guard.remove(&self.crtc); + } +} diff --git a/src/backend/renderer/pixman/mod.rs b/src/backend/renderer/pixman/mod.rs index 395678dfb50d..2c6329e1a1e5 100644 --- a/src/backend/renderer/pixman/mod.rs +++ b/src/backend/renderer/pixman/mod.rs @@ -685,6 +685,8 @@ pub struct PixmanRenderer { dmabuf_cache: Vec, } +unsafe impl Send for PixmanRenderer {} + impl PixmanRenderer { /// Creates a new pixman renderer pub fn new() -> Result {