From 3c6fa9c9f42415a4078bcc29413d6b66c3005f2c Mon Sep 17 00:00:00 2001 From: Christian Meissl Date: Sat, 28 Feb 2026 21:02:24 +0100 Subject: [PATCH 1/3] drm/compositor: return an error in case no frame is pending --- anvil/src/udev.rs | 2 +- src/backend/drm/compositor/mod.rs | 22 +++++++++++++--------- src/backend/drm/output.rs | 2 +- 3 files changed, 15 insertions(+), 11 deletions(-) diff --git a/anvil/src/udev.rs b/anvil/src/udev.rs index 21aa3fc4bb2b..8e1db1b2e318 100644 --- a/anvil/src/udev.rs +++ b/anvil/src/udev.rs @@ -1285,7 +1285,7 @@ impl AnvilState { let schedule_render = match submit_result { Ok(user_data) => { - if let Some(mut feedback) = user_data.flatten() { + if let Some(mut feedback) = user_data { feedback.presented(clock, Refresh::fixed(frame_duration), seq as u64, flags); } diff --git a/src/backend/drm/compositor/mod.rs b/src/backend/drm/compositor/mod.rs index 289ce5bfe658..157335f07fdd 100644 --- a/src/backend/drm/compositor/mod.rs +++ b/src/backend/drm/compositor/mod.rs @@ -2603,16 +2603,20 @@ where /// was received after calling [`DrmCompositor::queue_frame`] on this surface. /// Otherwise the underlying swapchain will run out of buffers eventually. #[profiling::function] - pub fn frame_submitted(&mut self) -> FrameResult, A, F> { - if let Some(PendingFrame { mut frame, user_data }) = self.pending_frame.take() { - std::mem::swap(&mut frame, &mut self.current_frame); - if self.queued_frame.is_some() { - self.submit()?; - } - Ok(Some(user_data)) - } else { - Ok(None) + pub fn frame_submitted(&mut self) -> FrameResult { + let Some(PendingFrame { + mut frame, user_data, .. + }) = self.pending_frame.take() + else { + return Err(FrameError::EmptyFrame); + }; + + std::mem::swap(&mut frame, &mut self.current_frame); + if self.queued_frame.is_some() { + self.submit()?; } + + Ok(user_data) } /// Reset the underlying buffers diff --git a/src/backend/drm/output.rs b/src/backend/drm/output.rs index c19163bf1e45..b7698be7d45f 100644 --- a/src/backend/drm/output.rs +++ b/src/backend/drm/output.rs @@ -676,7 +676,7 @@ where /// *Note*: Needs to be called, after the vblank event of the matching [`DrmDevice`] /// was received after calling [`DrmOutput::queue_frame`] on this surface. /// Otherwise the underlying swapchain will run out of buffers eventually. - pub fn frame_submitted(&self) -> FrameResult, A, F> { + pub fn frame_submitted(&self) -> FrameResult { self.with_compositor(|compositor| compositor.frame_submitted()) } From 6865b9dc1ddb6a940fd2c0c5d3daf786df72bf91 Mon Sep 17 00:00:00 2001 From: Christian Meissl Date: Sat, 28 Feb 2026 21:06:18 +0100 Subject: [PATCH 2/3] drm/compositor: allow to access the pending and queued frame --- src/backend/drm/compositor/mod.rs | 21 +++++++++++++++++++++ src/backend/drm/output.rs | 18 +++++++++++++++++- 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/src/backend/drm/compositor/mod.rs b/src/backend/drm/compositor/mod.rs index 157335f07fdd..05f700118b57 100644 --- a/src/backend/drm/compositor/mod.rs +++ b/src/backend/drm/compositor/mod.rs @@ -1041,6 +1041,13 @@ bitflags::bitflags! { } } +/// A reference to a frame +#[derive(Debug)] +pub struct FrameRef<'a, U> { + /// User-data of the frame + pub user_data: &'a U, +} + /// Composite an output using a combination of planes and rendering /// /// see the [`module docs`](crate::backend::drm::compositor) for more information @@ -2597,6 +2604,20 @@ where flip.map_err(FrameError::DrmError) } + /// Access the currently pending frame without submitting it + pub fn pending_frame(&self) -> Option> { + self.pending_frame.as_ref().map(|frame| FrameRef { + user_data: &frame.user_data, + }) + } + + /// Access the currently queued frame + pub fn queued_frame(&self) -> Option> { + self.queued_frame.as_ref().map(|frame| FrameRef { + user_data: &frame.user_data, + }) + } + /// Marks the current frame as submitted. /// /// *Note*: Needs to be called, after the vblank event of the matching [`DrmDevice`](super::DrmDevice) diff --git a/src/backend/drm/output.rs b/src/backend/drm/output.rs index b7698be7d45f..01a781812784 100644 --- a/src/backend/drm/output.rs +++ b/src/backend/drm/output.rs @@ -26,7 +26,7 @@ use crate::{ use super::{ DrmDevice, DrmError, Planes, compositor::{ - DrmCompositor, FrameError, FrameFlags, FrameResult, PrimaryPlaneElement, RenderFrameError, + DrmCompositor, FrameError, FrameFlags, FrameRef, FrameResult, PrimaryPlaneElement, RenderFrameError, RenderFrameErrorType, RenderFrameResult, }, exporter::ExportFramebuffer, @@ -671,6 +671,22 @@ where self.with_compositor(|compositor| compositor.reset_buffers()); } + /// Access the currently pending frame without submitting it + pub fn with_pending_frame(&self, f: T) -> R + where + T: Fn(Option>) -> R, + { + self.with_compositor(|compositor| f(compositor.pending_frame())) + } + + /// Access the currently queued frame + pub fn with_queued_frame(&self, f: T) -> R + where + T: Fn(Option>) -> R, + { + self.with_compositor(|compositor| f(compositor.queued_frame())) + } + /// Marks the current frame as submitted. /// /// *Note*: Needs to be called, after the vblank event of the matching [`DrmDevice`] From f8a8f0fae1a6160451bf9a4cd1b2adeb66a6c11d Mon Sep 17 00:00:00 2001 From: PolyMeilex Date: Sat, 17 Feb 2024 04:19:43 +0100 Subject: [PATCH 3/3] Initial support for DRM async page flip Co-Authored-By: Christian Meissl --- anvil/README.md | 1 + anvil/src/udev.rs | 24 +++- src/backend/drm/compositor/mod.rs | 143 +++++++++++++++++++++--- src/backend/drm/device/mod.rs | 13 +++ src/backend/drm/output.rs | 16 ++- src/backend/drm/surface/atomic.rs | 34 ++++-- src/backend/drm/surface/gbm.rs | 17 ++- src/backend/drm/surface/legacy.rs | 39 ++++--- src/backend/drm/surface/mod.rs | 14 +-- src/backend/renderer/element/mod.rs | 28 ++++- src/backend/renderer/element/surface.rs | 8 +- src/backend/renderer/mod.rs | 9 ++ 12 files changed, 287 insertions(+), 59 deletions(-) diff --git a/anvil/README.md b/anvil/README.md index 07acd376a7b2..9c89e9cce8fa 100644 --- a/anvil/README.md +++ b/anvil/README.md @@ -50,5 +50,6 @@ The currently available backends are: | ANVIL_DISABLE_DIRECT_SCANOUT | any | tty-udev | | ANVIL_GLES_DISABLE_INSTANCING | any | tty-udev | | ANVIL_NO_VULKAN | 1,true,yes,y | x11 | +| ANVIL_FORCE_TEARING | any | tty-udev | | SMITHAY_USE_LEGACY | 1,true,yes,y | tty-udev | | SMITHAY_VK_VERSION | 1.3 | | diff --git a/anvil/src/udev.rs b/anvil/src/udev.rs index 8e1db1b2e318..bd6e9f0e71ca 100644 --- a/anvil/src/udev.rs +++ b/anvil/src/udev.rs @@ -45,7 +45,7 @@ use smithay::{ input::InputEvent, libinput::{LibinputInputBackend, LibinputSessionInterface}, renderer::{ - DebugFlags, ImportDma, ImportMemWl, + DebugFlags, ImportDma, ImportMemWl, PresentationMode, damage::Error as OutputDamageTrackerError, element::{AsRenderElements, RenderElementStates, memory::MemoryRenderBuffer}, gles::{Capability, GlesRenderer}, @@ -651,6 +651,7 @@ struct SurfaceData { dmabuf_feedback: Option, last_presentation_time: Option>, vblank_throttle_timer: Option, + force_tearing: bool, } impl Drop for SurfaceData { @@ -1046,6 +1047,7 @@ impl AnvilState { ) }); + let force_tearing = std::env::var("ANVIL_FORCE_TEARING").is_ok(); let surface = SurfaceData { dh: self.display_handle.clone(), device_id: node, @@ -1061,6 +1063,7 @@ impl AnvilState { dmabuf_feedback, last_presentation_time: None, vblank_throttle_timer: None, + force_tearing, }; device.surfaces.insert(crtc, surface); @@ -1253,7 +1256,13 @@ impl AnvilState { }); if let Some(vblank_remaining_time) = vblank_remaining_time { - if vblank_remaining_time > frame_duration / 2 { + let presentation_mode = surface + .drm_output + .with_pending_frame(|frame| frame.map(|frame| frame.presentation_mode)); + + if presentation_mode != Some(PresentationMode::Async) + && vblank_remaining_time > frame_duration / 2 + { static WARN_ONCE: Once = Once::new(); WARN_ONCE.call_once(|| { warn!("display running faster than expected, throttling vblanks and disabling HwClock") @@ -1638,19 +1647,28 @@ fn render_surface<'a>( let (elements, clear_color) = output_elements(output, space, custom_elements, renderer, show_window_preview); + let presentation_mode = if surface.force_tearing { + PresentationMode::Async + } else { + PresentationMode::VSync + }; + let frame_mode = if surface.disable_direct_scanout { FrameFlags::empty() + } else if presentation_mode == PresentationMode::Async { + FrameFlags::DEFAULT | FrameFlags::ALLOW_PRIMARY_PLANE_SCANOUT_ANY } else { FrameFlags::DEFAULT }; let (rendered, states) = surface .drm_output - .render_frame(renderer, &elements, clear_color, frame_mode) + .render_frame(renderer, &elements, clear_color, frame_mode, presentation_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 { diff --git a/src/backend/drm/compositor/mod.rs b/src/backend/drm/compositor/mod.rs index 05f700118b57..e4fb990d9e7a 100644 --- a/src/backend/drm/compositor/mod.rs +++ b/src/backend/drm/compositor/mod.rs @@ -63,6 +63,7 @@ //! exporter::gbm::GbmFramebufferExporter, //! DrmSurface, //! }, +//! backend::renderer::PresentationMode, //! output::{Output, PhysicalProperties, Subpixel}, //! utils::Size, //! }; @@ -109,7 +110,7 @@ //! //! # let elements: Vec> = Vec::new(); //! let render_frame_result = compositor -//! .render_frame::<_, _>(&mut renderer, &elements, CLEAR_COLOR, FrameFlags::DEFAULT) +//! .render_frame::<_, _>(&mut renderer, &elements, CLEAR_COLOR, FrameFlags::DEFAULT, PresentationMode::VSync) //! .expect("failed to render frame"); //! //! if !render_frame_result.is_empty { @@ -135,7 +136,7 @@ use std::{ use drm::{ Device, DriverCapability, - control::{Device as _, Mode, PlaneType, connector, crtc, framebuffer, plane}, + control::{Device as _, Mode, PageFlipFlags, PlaneType, connector, crtc, framebuffer, plane}, }; use drm_fourcc::{DrmFormat, DrmFourcc, DrmModifier}; use indexmap::{IndexMap, IndexSet}; @@ -159,7 +160,8 @@ use crate::{ }, drm::{DrmError, PlaneDamageClips, plane_has_property}, renderer::{ - Bind, Color32F, DebugFlags, Renderer, RendererSuper, Texture, buffer_y_inverted, + Bind, Color32F, DebugFlags, PresentationMode, Renderer, RendererSuper, Texture, + buffer_y_inverted, damage::{Error as OutputDamageTrackerError, OutputDamageTracker}, element::{ Element, Id, Kind, RenderElement, RenderElementPresentationState, RenderElementState, @@ -568,6 +570,7 @@ impl Clone for PlaneState { #[derive(Debug)] struct FrameState { planes: SmallVec<[(plane::Handle, PlaneState); 10]>, + async_flip_failed: bool, } impl FrameState { @@ -644,7 +647,10 @@ impl FrameState { .map(|info| (info.handle, PlaneState::default())), ); - FrameState { planes: tmp } + FrameState { + planes: tmp, + async_flip_failed: false, + } } } @@ -734,12 +740,11 @@ impl FrameState { surface: &DrmSurface, supports_fencing: bool, allow_partial_update: bool, - event: bool, ) -> Result<(), crate::backend::drm::error::Error> { debug_assert!(!self.planes.iter().any(|(_, state)| state.needs_test)); surface.commit( self.build_planes(surface, supports_fencing, allow_partial_update), - event, + PageFlipFlags::EVENT, ) } @@ -749,12 +754,12 @@ impl FrameState { surface: &DrmSurface, supports_fencing: bool, allow_partial_update: bool, - event: bool, + flip_flags: PageFlipFlags, ) -> Result<(), crate::backend::drm::error::Error> { debug_assert!(!self.planes.iter().any(|(_, state)| state.needs_test)); surface.page_flip( self.build_planes(surface, supports_fencing, allow_partial_update), - event, + flip_flags, ) } @@ -944,6 +949,7 @@ impl From<&PlaneInfo> for PlaneAssignment { struct PendingFrame::Buffer>, U> { frame: CompositorFrameState, user_data: U, + presentation_mode: PresentationMode, } impl std::fmt::Debug for PendingFrame @@ -992,6 +998,7 @@ enum PreparedFrameKind { struct PreparedFrame::Buffer>> { frame: CompositorFrameState, kind: PreparedFrameKind, + presentation_mode: PresentationMode, } impl::Buffer>> PreparedFrame { @@ -1046,6 +1053,9 @@ bitflags::bitflags! { pub struct FrameRef<'a, U> { /// User-data of the frame pub user_data: &'a U, + + /// Presentation mode of this frame + pub presentation_mode: PresentationMode, } /// Composite an output using a combination of planes and rendering @@ -1696,6 +1706,7 @@ where elements: &'a [E], clear_color: impl Into, frame_flags: FrameFlags, + presentation_mode: PresentationMode, ) -> Result, RenderFrameErrorType> where E: RenderElement, @@ -2033,6 +2044,7 @@ where output_geometry, try_assign_primary_plane, frame_flags, + presentation_mode, ) { Ok(direct_scan_out_plane) => { match direct_scan_out_plane.type_ { @@ -2401,6 +2413,7 @@ where PreparedFrameKind::Full }, frame: next_frame_state, + presentation_mode, }; let frame_reference: RenderFrameResult<'a, A::Buffer, F::Framebuffer, E> = RenderFrameResult { is_empty: next_frame.is_empty(), @@ -2510,14 +2523,16 @@ where let flip = prepared_frame .frame - .commit(&self.surface, self.supports_fencing, false, false); + .commit(&self.surface, self.supports_fencing, false) + .map(|_| PresentationMode::VSync); if flip.is_ok() { self.queued_frame = None; self.pending_frame = None; } - self.handle_flip(prepared_frame, None, flip) + self.handle_flip(prepared_frame, None, flip)?; + Ok(()) } /// Re-evaluates the current state of the crtc and forces calls to [`render_frame`](DrmCompositor::render_frame) @@ -2536,7 +2551,7 @@ where } #[profiling::function] - fn submit(&mut self) -> FrameResult<(), A, F> { + fn submit(&mut self) -> FrameResult { let QueuedFrame { mut prepared_frame, user_data, @@ -2546,11 +2561,92 @@ where let flip = if self.surface.commit_pending() { prepared_frame .frame - .commit(&self.surface, self.supports_fencing, allow_partial_update, true) + .commit(&self.surface, self.supports_fencing, allow_partial_update) + .map(|_| PresentationMode::VSync) } else { - prepared_frame + let previous_state = self + .pending_frame + .as_ref() + .map(|f| &f.frame) + .unwrap_or(&self.current_frame); + + let primary_is_compatible = prepared_frame + .frame + .plane_state(self.plane()) + .and_then(|state| { + previous_state + .plane_state(self.plane()) + .map(|previous_state| previous_state.is_compatible(state)) + }) + .unwrap_or(false); + + // If the properties of the plane did not change we can expect the async flip state to + // also stay unchanged. So in case it failed previously we can skip trying again. + if primary_is_compatible { + prepared_frame.frame.async_flip_failed = previous_state.async_flip_failed; + } + + // Currently async page flips are limited to the primary plane, if any other plane + // changes (including the cursor plane) it will fail. + // + // Note: If this changes we should extend `PlaneInfo` to include a flag indicating + // async flip support per plane. This would allows us to check for compatible changes + // per plane that supports async flips here. But that also requires us to track the failed + // combinations. + let only_primary_changed = prepared_frame + .frame + .planes + .iter() + .filter(|&(handle, _)| *handle != self.plane()) + .all(|(_, state)| state.skip); + + let mut flip_flags = PageFlipFlags::EVENT; + + // As already noted async page flips are only allowed when only the primary plane + // changed in a compatible way. We also want to skip it in case we already tried + // and failed. An async page flip can for example also fail for certain modifiers, + // for example on intel compressed formats might not be allowed. + if prepared_frame.presentation_mode == PresentationMode::Async + && only_primary_changed + && primary_is_compatible + && allow_partial_update + && !prepared_frame.frame.async_flip_failed + { + flip_flags |= PageFlipFlags::ASYNC; + } + + let flip = prepared_frame .frame - .page_flip(&self.surface, self.supports_fencing, allow_partial_update, true) + .page_flip( + &self.surface, + self.supports_fencing, + allow_partial_update, + flip_flags, + ) + .map(|_| { + if flip_flags.contains(PageFlipFlags::ASYNC) { + PresentationMode::Async + } else { + PresentationMode::VSync + } + }); + + // If an async page flip fails we retry without async and note + // that it failed to not try again until the plane properties change. + if flip.is_err() && flip_flags.contains(PageFlipFlags::ASYNC) { + prepared_frame.frame.async_flip_failed = true; + prepared_frame + .frame + .page_flip( + &self.surface, + self.supports_fencing, + allow_partial_update, + PageFlipFlags::EVENT, + ) + .map(|_| PresentationMode::VSync) + } else { + flip + } }; self.handle_flip(prepared_frame, Some(user_data), flip) @@ -2560,10 +2656,10 @@ where &mut self, prepared_frame: PreparedFrame, user_data: Option, - flip: Result<(), crate::backend::drm::error::Error>, - ) -> FrameResult<(), A, F> { + flip: Result, + ) -> FrameResult { match flip { - Ok(_) => { + Ok(presentation_mode) => { if prepared_frame.kind == PreparedFrameKind::Full { self.reset_pending = false; } @@ -2571,6 +2667,7 @@ where self.pending_frame = user_data.map(|user_data| PendingFrame { frame: prepared_frame.frame, user_data, + presentation_mode, }); } Err(crate::backend::drm::error::Error::Access(ref access)) @@ -2608,6 +2705,7 @@ where pub fn pending_frame(&self) -> Option> { self.pending_frame.as_ref().map(|frame| FrameRef { user_data: &frame.user_data, + presentation_mode: frame.presentation_mode, }) } @@ -2615,6 +2713,7 @@ where pub fn queued_frame(&self) -> Option> { self.queued_frame.as_ref().map(|frame| FrameRef { user_data: &frame.user_data, + presentation_mode: frame.prepared_frame.presentation_mode, }) } @@ -2842,6 +2941,7 @@ where output_geometry: Rectangle, try_assign_primary_plane: bool, frame_flags: FrameFlags, + presentation_mode: PresentationMode, ) -> Result> where R: Renderer + Bind, @@ -2886,6 +2986,15 @@ where }; } + // FIXME: For now we just skip all overlay planes if the requested + // mode is not sync. With support for IN_FORMATS_ASYNC we might be + // able to lift this restriction again. With IN_FORMATS_ASYNC we should + // be able to check if the element requested async using Element::presentation_mode + // and check the plane IN_FORMATS_ASYNC against the element buffer format. + if presentation_mode != PresentationMode::VSync { + return Err(None); + } + if let Some(plane) = self.try_assign_cursor_plane( renderer, element, diff --git a/src/backend/drm/device/mod.rs b/src/backend/drm/device/mod.rs index f1d7e49fb5dc..5ba353b0283b 100644 --- a/src/backend/drm/device/mod.rs +++ b/src/backend/drm/device/mod.rs @@ -109,6 +109,7 @@ pub struct DrmDevice { pub(super) dev_id: dev_t, pub(crate) internal: Arc, has_universal_planes: bool, + has_async_page_flips: bool, cursor_size: Size, resources: ResourceHandles, plane_claim_storage: PlaneClaimStorage, @@ -142,6 +143,14 @@ impl DrmDeviceInternal { } } + fn has_async_page_flips(&self) -> bool { + let async_cap = match self { + DrmDeviceInternal::Atomic(_) => DriverCapability::AtomicASyncPageFlip, + DrmDeviceInternal::Legacy(_) => DriverCapability::ASyncPageFlip, + }; + self.device_fd().get_driver_capability(async_cap).unwrap_or(0) == 1 + } + fn span(&self) -> &tracing::Span { match self { DrmDeviceInternal::Atomic(internal) => &internal.span, @@ -214,12 +223,14 @@ impl DrmDevice { })?; let internal = Arc::new(DrmDevice::create_internal(fd, active, disable_connectors)?); + let has_async_page_flips = internal.has_async_page_flips(); Ok(( DrmDevice { dev_id, internal: internal.clone(), has_universal_planes, + has_async_page_flips, cursor_size, resources, plane_claim_storage: Default::default(), @@ -373,6 +384,7 @@ impl DrmDevice { mapping, mode, connectors, + self.has_async_page_flips, )?) } else { DrmSurfaceInternal::Legacy(LegacyDrmSurface::new( @@ -381,6 +393,7 @@ impl DrmDevice { crtc, mode, connectors, + self.has_async_page_flips, )?) }; let internal = Arc::new(internal); diff --git a/src/backend/drm/output.rs b/src/backend/drm/output.rs index 01a781812784..89372b5538ac 100644 --- a/src/backend/drm/output.rs +++ b/src/backend/drm/output.rs @@ -18,7 +18,10 @@ use crate::{ dmabuf::{AsDmabuf, Dmabuf}, gbm::GbmDevice, }, - renderer::{Bind, Color32F, DebugFlags, Renderer, RendererSuper, Texture, element::RenderElement}, + renderer::{ + Bind, Color32F, DebugFlags, PresentationMode, Renderer, RendererSuper, Texture, + element::RenderElement, + }, }, output::OutputModeSource, }; @@ -711,6 +714,7 @@ where elements: &'a [E], clear_color: impl Into, frame_mode: FrameFlags, + presentation_mode: PresentationMode, ) -> Result, RenderFrameErrorType> where E: RenderElement, @@ -719,7 +723,7 @@ where R::Error: Send + Sync + 'static, { self.with_compositor(|compositor| { - compositor.render_frame(renderer, elements, clear_color, frame_mode) + compositor.render_frame(renderer, elements, clear_color, frame_mode, presentation_mode) }) } @@ -1033,7 +1037,13 @@ where .map(|(elements, color)| (&**elements, color)) .unwrap_or((&[], &Color32F::BLACK)); let frame_result = compositor - .render_frame(renderer, elements, *clear_color, FrameFlags::empty()) + .render_frame( + renderer, + elements, + *clear_color, + FrameFlags::empty(), + PresentationMode::VSync, + ) .map_err(DrmOutputManagerError::RenderFrame)?; if frame_result.needs_sync() { if let PrimaryPlaneElement::Swapchain(primary_swapchain_element) = frame_result.primary_element { diff --git a/src/backend/drm/surface/atomic.rs b/src/backend/drm/surface/atomic.rs index 318aa9fef7ee..7bc6aa164198 100644 --- a/src/backend/drm/surface/atomic.rs +++ b/src/backend/drm/surface/atomic.rs @@ -3,7 +3,8 @@ use drm::control::atomic::AtomicModeReq; use drm::control::connector::Interface; use drm::control::property::ValueType; use drm::control::{ - AtomicCommitFlags, Mode, PlaneType, connector, crtc, dumbbuffer::DumbBuffer, framebuffer, plane, property, + AtomicCommitFlags, Mode, PageFlipFlags, PlaneType, connector, crtc, dumbbuffer::DumbBuffer, framebuffer, + plane, property, }; #[cfg(debug_assertions)] @@ -173,6 +174,7 @@ pub struct AtomicDrmSurface { state: RwLock, pending: RwLock, pub(super) span: tracing::Span, + supports_async_page_flips: bool, } impl AtomicDrmSurface { @@ -185,6 +187,7 @@ impl AtomicDrmSurface { prop_mapping: Arc>, mode: Mode, connectors: &[connector::Handle], + supports_async_page_flips: bool, ) -> Result { let span = info_span!("drm_atomic", crtc = ?crtc); let _guard = span.enter(); @@ -210,6 +213,7 @@ impl AtomicDrmSurface { }; drop(_guard); + let surface = AtomicDrmSurface { fd, active, @@ -220,6 +224,7 @@ impl AtomicDrmSurface { state: RwLock::new(state), pending: RwLock::new(pending), span, + supports_async_page_flips, }; Ok(surface) @@ -744,13 +749,26 @@ impl AtomicDrmSurface { }) } + fn flip_flags(&self, value: PageFlipFlags) -> AtomicCommitFlags { + let mut v = AtomicCommitFlags::empty(); + if value.contains(PageFlipFlags::EVENT) { + v |= AtomicCommitFlags::PAGE_FLIP_EVENT; + } + if self.supports_async_page_flips && value.contains(PageFlipFlags::ASYNC) { + v |= AtomicCommitFlags::PAGE_FLIP_ASYNC; + } + v + } + #[instrument(level = "trace", parent = &self.span, skip(self, planes))] #[profiling::function] pub fn commit<'a>( &self, planes: impl IntoIterator>, - event: bool, + flip_flags: super::PageFlipFlags, ) -> Result<(), Error> { + let flip_flags = self.flip_flags(flip_flags); + if !self.active.load(Ordering::SeqCst) { return Err(Error::DeviceInactive); } @@ -825,9 +843,9 @@ impl AtomicDrmSurface { let result = self .fd .atomic_commit( - if event { + if flip_flags.contains(AtomicCommitFlags::PAGE_FLIP_EVENT) { // on the atomic api we can modeset and trigger a page_flip event on the same call! - AtomicCommitFlags::PAGE_FLIP_EVENT | AtomicCommitFlags::ALLOW_MODESET + flip_flags | AtomicCommitFlags::ALLOW_MODESET // we also *should* not need to wait for completion, like with `set_crtc`, // because we have tested this exact commit already, so we do not expect any errors later down the line. // @@ -868,8 +886,10 @@ impl AtomicDrmSurface { pub fn page_flip<'a>( &self, planes: impl IntoIterator>, - event: bool, + flip_flags: PageFlipFlags, ) -> Result<(), Error> { + let flip_flags = self.flip_flags(flip_flags); + if !self.active.load(Ordering::SeqCst) { return Err(Error::DeviceInactive); } @@ -896,8 +916,8 @@ impl AtomicDrmSurface { let res = self .fd .atomic_commit( - if event { - AtomicCommitFlags::PAGE_FLIP_EVENT | AtomicCommitFlags::NONBLOCK + if flip_flags.contains(AtomicCommitFlags::PAGE_FLIP_EVENT) { + flip_flags | AtomicCommitFlags::NONBLOCK } else { AtomicCommitFlags::NONBLOCK }, diff --git a/src/backend/drm/surface/gbm.rs b/src/backend/drm/surface/gbm.rs index 56120ba4f0a3..82ca32ff5b39 100644 --- a/src/backend/drm/surface/gbm.rs +++ b/src/backend/drm/surface/gbm.rs @@ -1,7 +1,7 @@ use std::os::unix::io::AsFd; use std::sync::Arc; -use drm::control::{Mode, connector, crtc, plane}; +use drm::control::{Mode, PageFlipFlags, connector, crtc, plane}; use drm::{Device, DriverCapability}; use indexmap::IndexSet; @@ -13,7 +13,7 @@ use crate::backend::allocator::{Allocator, Format, Fourcc, Modifier, Slot, Swapc use crate::backend::drm::error::AccessError; use crate::backend::drm::gbm::{GbmFramebuffer, framebuffer_from_bo}; use crate::backend::drm::{DrmError, DrmSurface, plane_has_property}; -use crate::backend::renderer::sync::SyncPoint; +use crate::backend::renderer::{PresentationMode, sync::SyncPoint}; use crate::utils::{DevPath, Physical, Rectangle, Transform}; use tracing::{debug, info_span, instrument, trace, warn}; @@ -26,6 +26,7 @@ struct QueuedFb { sync: Option, damage: Option>>, user_data: U, + presentation_mode: PresentationMode, } /// Simplified abstraction of a swapchain for gbm-buffers displayed on a [`DrmSurface`]. @@ -292,6 +293,7 @@ where sync: Option, damage: Option>>, user_data: U, + presentation_mode: PresentationMode, ) -> Result<(), Error> { if !self.drm.is_active() { return Err(Error::::DrmError(DrmError::DeviceInactive)); @@ -306,6 +308,7 @@ where sync, damage, user_data, + presentation_mode, }); if self.pending_fb.is_none() { self.submit()?; @@ -342,6 +345,7 @@ where sync, damage, user_data, + presentation_mode, } = self.queued_fb.take().unwrap(); let handle = slot.userdata().get::().unwrap(); let mode = self.drm.pending_mode(); @@ -388,10 +392,15 @@ where }), }; + let mut flip_flags = PageFlipFlags::EVENT; + if presentation_mode == PresentationMode::Async { + flip_flags |= PageFlipFlags::ASYNC; + } + let flip = if self.drm.commit_pending() { - self.drm.commit([plane_state], true) + self.drm.commit([plane_state], flip_flags) } else { - self.drm.page_flip([plane_state], true) + self.drm.page_flip([plane_state], flip_flags) }; if flip.is_ok() { self.pending_fb = Some((slot, user_data)); diff --git a/src/backend/drm/surface/legacy.rs b/src/backend/drm/surface/legacy.rs index 8184acb6b61f..1e7913a68efe 100644 --- a/src/backend/drm/surface/legacy.rs +++ b/src/backend/drm/surface/legacy.rs @@ -88,6 +88,7 @@ pub struct LegacyDrmSurface { pending: RwLock, dpms: Mutex, pub(super) span: tracing::Span, + supports_async_page_flips: bool, } impl LegacyDrmSurface { @@ -97,6 +98,7 @@ impl LegacyDrmSurface { crtc: crtc::Handle, mode: Mode, connectors: &[connector::Handle], + supports_async_page_flips: bool, ) -> Result { let span = info_span!("drm_legacy", crtc = ?crtc); let _guard = span.enter(); @@ -117,6 +119,7 @@ impl LegacyDrmSurface { pending: RwLock::new(pending), dpms: Mutex::new(true), span, + supports_async_page_flips, }; Ok(surface) @@ -226,9 +229,18 @@ impl LegacyDrmSurface { *self.pending.read().unwrap() != *self.state.read().unwrap() } + fn flip_flags(&self, mut flip_flags: PageFlipFlags) -> PageFlipFlags { + if !self.supports_async_page_flips { + flip_flags.remove(PageFlipFlags::ASYNC); + } + flip_flags + } + #[instrument(level = "trace", parent = &self.span, skip(self))] #[profiling::function] - pub fn commit(&self, framebuffer: framebuffer::Handle, event: bool) -> Result<(), Error> { + pub fn commit(&self, framebuffer: framebuffer::Handle, flip_flags: PageFlipFlags) -> Result<(), Error> { + let flip_flags = self.flip_flags(flip_flags); + if !self.active.load(Ordering::SeqCst) { return Err(Error::DeviceInactive); } @@ -311,13 +323,13 @@ impl LegacyDrmSurface { *current = pending.clone(); - if event { + if flip_flags.contains(PageFlipFlags::EVENT) { // set crtc does not trigger page_flip events, so we immediately queue a flip // with the same framebuffer. // this will result in wasting a frame, because this flip will need to wait // for `set_crtc`, but is necessary to drive the event loop and thus provide // a more consistent api. - ControlDevice::page_flip(&*self.fd, self.crtc, framebuffer, PageFlipFlags::EVENT, None).map_err( + ControlDevice::page_flip(&*self.fd, self.crtc, framebuffer, flip_flags, None).map_err( |source| { Error::Access(AccessError { errmsg: "Failed to queue page flip", @@ -333,7 +345,13 @@ impl LegacyDrmSurface { #[instrument(level = "trace", parent = &self.span, skip(self))] #[profiling::function] - pub fn page_flip(&self, framebuffer: framebuffer::Handle, event: bool) -> Result<(), Error> { + pub fn page_flip( + &self, + framebuffer: framebuffer::Handle, + flip_flags: PageFlipFlags, + ) -> Result<(), Error> { + let flip_flags = self.flip_flags(flip_flags); + trace!("Queueing Page flip"); if !self.active.load(Ordering::SeqCst) { @@ -347,18 +365,7 @@ impl LegacyDrmSurface { *dpms = true; } - ControlDevice::page_flip( - &*self.fd, - self.crtc, - framebuffer, - if event { - PageFlipFlags::EVENT - } else { - PageFlipFlags::empty() - }, - None, - ) - .map_err(|source| { + ControlDevice::page_flip(&*self.fd, self.crtc, framebuffer, flip_flags, None).map_err(|source| { Error::Access(AccessError { errmsg: "Failed to page flip", dev: self.fd.dev_path(), diff --git a/src/backend/drm/surface/mod.rs b/src/backend/drm/surface/mod.rs index 952ef840255a..8fe36e723fe3 100644 --- a/src/backend/drm/surface/mod.rs +++ b/src/backend/drm/surface/mod.rs @@ -4,7 +4,7 @@ use std::sync::Arc; use std::sync::atomic::Ordering; use drm::Device as BasicDevice; -use drm::control::{Device as ControlDevice, Mode, connector, crtc, framebuffer, plane}; +use drm::control::{Device as ControlDevice, Mode, PageFlipFlags, connector, crtc, framebuffer, plane}; use libc::dev_t; @@ -406,13 +406,13 @@ impl DrmSurface { pub fn commit<'a>( &self, planes: impl IntoIterator>, - event: bool, + flip_flags: PageFlipFlags, ) -> Result<(), Error> { match &*self.internal { - DrmSurfaceInternal::Atomic(surf) => surf.commit(planes, event), + DrmSurfaceInternal::Atomic(surf) => surf.commit(planes, flip_flags), DrmSurfaceInternal::Legacy(surf) => { let fb = ensure_legacy_planes(self, planes)?; - surf.commit(fb, event) + surf.commit(fb, flip_flags) } } } @@ -428,13 +428,13 @@ impl DrmSurface { pub fn page_flip<'a>( &self, planes: impl IntoIterator>, - event: bool, + flip_flags: PageFlipFlags, ) -> Result<(), Error> { match &*self.internal { - DrmSurfaceInternal::Atomic(surf) => surf.page_flip(planes, event), + DrmSurfaceInternal::Atomic(surf) => surf.page_flip(planes, flip_flags), DrmSurfaceInternal::Legacy(surf) => { let fb = ensure_legacy_planes(self, planes)?; - surf.page_flip(fb, event) + surf.page_flip(fb, flip_flags) } } } diff --git a/src/backend/renderer/element/mod.rs b/src/backend/renderer/element/mod.rs index b68779367abd..330c2fc64958 100644 --- a/src/backend/renderer/element/mod.rs +++ b/src/backend/renderer/element/mod.rs @@ -35,7 +35,7 @@ use crate::{ #[cfg(feature = "wayland_frontend")] use super::utils::Buffer; use super::{ - Renderer, + PresentationMode, Renderer, utils::{CommitCounter, DamageSet, OpaqueRegions}, }; @@ -566,6 +566,11 @@ pub trait Element { fn is_framebuffer_effect(&self) -> bool { false } + + /// Hint for DRM backend on how the element should be presented + fn presentation_mode(&self) -> PresentationMode { + PresentationMode::VSync + } } /// A single render element @@ -670,6 +675,10 @@ where fn is_framebuffer_effect(&self) -> bool { (*self).is_framebuffer_effect() } + + fn presentation_mode(&self) -> PresentationMode { + (*self).presentation_mode() + } } impl RenderElement for &E @@ -1111,6 +1120,19 @@ macro_rules! render_elements_internal { Self::_GenericCatcher(_) => unreachable!(), } } + + fn presentation_mode(&self) -> $crate::backend::renderer::PresentationMode { + match self { + $( + #[allow(unused_doc_comments)] + $( + #[$meta] + )* + Self::$body(x) => $crate::render_elements_internal!(@call presentation_mode; x) + ),*, + Self::_GenericCatcher(_) => unreachable!(), + } + } }; (@draw <$renderer:ty>; $($(#[$meta:meta])* $body:ident=$field:ty $(as <$other_renderer:ty>)?),* $(,)?) => { fn draw( @@ -1800,6 +1822,10 @@ where fn is_framebuffer_effect(&self) -> bool { self.0.is_framebuffer_effect() } + + fn presentation_mode(&self) -> PresentationMode { + self.0.presentation_mode() + } } impl RenderElement for Wrap diff --git a/src/backend/renderer/element/surface.rs b/src/backend/renderer/element/surface.rs index 4e70db8604ef..618c41948976 100644 --- a/src/backend/renderer/element/surface.rs +++ b/src/backend/renderer/element/surface.rs @@ -69,7 +69,7 @@ use wayland_server::protocol::wl_surface; use crate::{ backend::renderer::{ - Color32F, Frame, ImportAll, Renderer, Texture, + Color32F, Frame, ImportAll, PresentationMode, Renderer, Texture, utils::{ Buffer, DamageSet, DamageSnapshot, OpaqueRegions, RendererSurfaceState, RendererSurfaceStateUserData, SurfaceView, @@ -430,6 +430,12 @@ impl Element for WaylandSurfaceRenderElement { fn kind(&self) -> Kind { self.kind } + + fn presentation_mode(&self) -> PresentationMode { + // On Wayland assume VSync as default preference + PresentationMode::VSync + // TODO: Return Async in case wp_tearing is attached to the surface + } } impl RenderElement for WaylandSurfaceRenderElement diff --git a/src/backend/renderer/mod.rs b/src/backend/renderer/mod.rs index 5d5bf5c74b7d..7501a4c36bb3 100644 --- a/src/backend/renderer/mod.rs +++ b/src/backend/renderer/mod.rs @@ -66,6 +66,15 @@ use sync::SyncPoint; #[cfg(any(feature = "renderer_test", test, doctest))] pub mod test; +/// Hint for DRM backend on how the surface should be presented +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum PresentationMode { + /// Vertical synchronization + VSync, + /// Tearing presentationres + Async, +} + /// Identifies a renderer context for a specific texture type. /// /// Renderers with the same `ContextId` are assumed to be texture-compatible,