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/state.rs b/anvil/src/state.rs index 31efd8ee5f13..b9b41c63f3f6 100644 --- a/anvil/src/state.rs +++ b/anvil/src/state.rs @@ -997,6 +997,7 @@ impl AnvilState { render_element_states, &dmabuf_feedback.render_feedback, &dmabuf_feedback.scanout_feedback, + &dmabuf_feedback.async_feedback, ) }); } @@ -1041,6 +1042,7 @@ impl AnvilState { render_element_states, &dmabuf_feedback.render_feedback, &dmabuf_feedback.scanout_feedback, + &dmabuf_feedback.async_feedback, ) }); } @@ -1178,6 +1180,7 @@ pub fn update_primary_scanout_output( pub struct SurfaceDmabufFeedback { pub render_feedback: DmabufFeedback, pub scanout_feedback: DmabufFeedback, + pub async_feedback: DmabufFeedback, } #[profiling::function] diff --git a/anvil/src/udev.rs b/anvil/src/udev.rs index 3ca4912df50c..64097ddde5e7 100644 --- a/anvil/src/udev.rs +++ b/anvil/src/udev.rs @@ -48,7 +48,7 @@ use smithay::{ element::{memory::MemoryRenderBuffer, AsRenderElements, RenderElementStates}, gles::{Capability, GlesRenderer}, multigpu::{gbm::GbmGlesBackend, GpuManager, MultiRenderer}, - DebugFlags, ImportDma, ImportMemWl, + DebugFlags, ImportDma, ImportMemWl, PresentationMode, }, session::{ libseat::{self, LibSeatSession}, @@ -651,6 +651,7 @@ struct SurfaceData { dmabuf_feedback: Option, last_presentation_time: Option>, vblank_throttle_timer: Option, + force_tearing: bool, } impl Drop for SurfaceData { @@ -726,7 +727,28 @@ fn get_surface_dmabuf_feedback( .formats .iter() .copied() - .chain(planes.overlay.into_iter().flat_map(|p| p.formats)) + .chain(planes.overlay.iter().flat_map(|p| p.formats.iter().copied())) + .collect::() + .intersection(&all_render_formats) + .copied() + .collect::(); + + // We limit the scan-out tranche to formats we can also render from + // so that there is always a fallback render path available in case + // the supplied buffer can not be scanned out directly + let async_planes_formats = surface + .plane_info() + .formats_async + .as_ref() + .unwrap_or(&surface.plane_info().formats) + .iter() + .copied() + .chain( + planes + .overlay + .into_iter() + .flat_map(|p| p.formats_async.unwrap_or(FormatSet::default())), + ) .collect::() .intersection(&all_render_formats) .copied() @@ -744,11 +766,22 @@ fn get_surface_dmabuf_feedback( }; let scanout_feedback = builder + .clone() .add_preference_tranche( surface.device_fd().dev_id().unwrap(), Some(zwp_linux_dmabuf_feedback_v1::TrancheFlags::Scanout), planes_formats, ) + .add_preference_tranche(scanout_node.dev_id(), None, render_formats.clone()) + .build() + .unwrap(); + + let async_feedback = builder + .add_preference_tranche( + surface.device_fd().dev_id().unwrap(), + Some(zwp_linux_dmabuf_feedback_v1::TrancheFlags::Scanout), + async_planes_formats, + ) .add_preference_tranche(scanout_node.dev_id(), None, render_formats) .build() .unwrap(); @@ -756,6 +789,7 @@ fn get_surface_dmabuf_feedback( Some(SurfaceDmabufFeedback { render_feedback, scanout_feedback, + async_feedback, }) } @@ -1046,6 +1080,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 +1096,7 @@ impl AnvilState { dmabuf_feedback, last_presentation_time: None, vblank_throttle_timer: None, + force_tearing, }; device.surfaces.insert(crtc, surface); @@ -1252,8 +1288,14 @@ impl AnvilState { frame_duration.saturating_sub(Time::elapsed(&last_presentation_time, clock)) }); + let presentation_mode = surface + .drm_output + .with_pending_frame(|frame| frame.map(|frame| frame.presentation_mode)); + if let Some(vblank_remaining_time) = vblank_remaining_time { - if vblank_remaining_time > frame_duration / 2 { + 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") @@ -1285,7 +1327,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); } @@ -1640,19 +1682,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/frame_result.rs b/src/backend/drm/compositor/frame_result.rs index 5c3e2262d22c..aeaa38da2c21 100644 --- a/src/backend/drm/compositor/frame_result.rs +++ b/src/backend/drm/compositor/frame_result.rs @@ -55,7 +55,7 @@ pub struct RenderFrameResult<'a, B: Buffer, F: Framebuffer, E> { pub(super) supports_fencing: bool, } -impl RenderFrameResult<'_, B, F, E> { +impl RenderFrameResult<'_, 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 { diff --git a/src/backend/drm/compositor/mod.rs b/src/backend/drm/compositor/mod.rs index 06d4ad3160a6..d5199699cec0 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 { @@ -134,7 +135,7 @@ use std::{ }; use drm::{ - control::{connector, crtc, framebuffer, plane, Device as _, Mode, PlaneType}, + control::{connector, crtc, framebuffer, plane, Device as _, Mode, PageFlipFlags, PlaneType}, Device, DriverCapability, }; use drm_fourcc::{DrmFormat, DrmFourcc, DrmModifier}; @@ -166,7 +167,7 @@ use crate::{ }, sync::SyncPoint, utils::{CommitCounter, DamageBag}, - Bind, Color32F, DebugFlags, Renderer, RendererSuper, Texture, + Bind, Color32F, DebugFlags, PresentationMode, Renderer, RendererSuper, Texture, }, SwapBuffersError, }, @@ -196,6 +197,13 @@ impl RenderElementState { } } + pub(crate) fn zero_copy_async(visible_area: usize) -> Self { + RenderElementState { + visible_area, + presentation_state: RenderElementPresentationState::Async, + } + } + pub(crate) fn rendering_with_reason(reason: RenderingReason) -> Self { RenderElementState { visible_area: 0, @@ -567,6 +575,7 @@ impl Clone for PlaneState { #[derive(Debug)] struct FrameState { planes: SmallVec<[(plane::Handle, PlaneState); 10]>, + async_flip_failed: bool, } impl FrameState { @@ -643,11 +652,23 @@ impl FrameState { .map(|info| (info.handle, PlaneState::default())), ); - FrameState { planes: tmp } + FrameState { + planes: tmp, + async_flip_failed: false, + } } } impl FrameState { + fn is_fully_compatible(&self, previous_frame: &Self) -> bool { + self.planes.iter().all(|(handle, state)| { + previous_frame + .plane_state(*handle) + .map(|other| state.is_compatible(other)) + .unwrap_or(false) + }) + } + #[profiling::function] #[inline] fn set_state(&mut self, plane: plane::Handle, state: PlaneState) { @@ -666,6 +687,7 @@ impl FrameState { plane: plane::Handle, state: PlaneState, allow_modeset: bool, + presentation_mode: PresentationMode, ) -> Result<(), DrmError> { let current_config = match self.plane_state_mut(plane) { Some(config) => config, @@ -674,7 +696,11 @@ impl FrameState { let backup = current_config.clone(); *current_config = state; - let res = surface.test_state(self.build_planes(surface, supports_fencing, true), allow_modeset); + let res = surface.test_state( + self.build_planes(surface, supports_fencing, true), + allow_modeset, + presentation_mode == PresentationMode::Async, + ); if res.is_err() { // test failed, restore previous state @@ -696,14 +722,10 @@ impl FrameState { supports_fencing: bool, allow_modeset: bool, allow_partial_update: bool, + presentation_mode: PresentationMode, ) -> Result<(), DrmError> { let needs_test = self.planes.iter().any(|(_, state)| state.needs_test); - let is_fully_compatible = self.planes.iter().all(|(handle, state)| { - previous_frame - .plane_state(*handle) - .map(|other| state.is_compatible(other)) - .unwrap_or(false) - }); + let is_fully_compatible = self.is_fully_compatible(previous_frame); if allow_partial_update && (!needs_test || is_fully_compatible) { trace!("skipping fully compatible state test"); @@ -716,6 +738,7 @@ impl FrameState { let res = surface.test_state( self.build_planes(surface, supports_fencing, allow_partial_update), allow_modeset, + presentation_mode == PresentationMode::Async, ); if res.is_ok() { @@ -733,12 +756,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, ) } @@ -748,12 +770,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, ) } @@ -943,6 +965,7 @@ impl From<&PlaneInfo> for PlaneAssignment { struct PendingFrame::Buffer>, U> { frame: CompositorFrameState, user_data: U, + presentation_mode: PresentationMode, } impl std::fmt::Debug for PendingFrame @@ -991,6 +1014,7 @@ enum PreparedFrameKind { struct PreparedFrame::Buffer>> { frame: CompositorFrameState, kind: PreparedFrameKind, + presentation_mode: PresentationMode, } impl::Buffer>> PreparedFrame { @@ -1040,6 +1064,16 @@ bitflags::bitflags! { } } +/// A reference to a frame +#[derive(Debug)] +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 /// /// see the [`module docs`](crate::backend::drm::compositor) for more information @@ -1571,7 +1605,14 @@ where }), }; - match current_frame_state.test_state(drm, supports_fencing, drm.plane(), plane_state, true) { + match current_frame_state.test_state( + drm, + supports_fencing, + drm.plane(), + plane_state, + true, + PresentationMode::VSync, + ) { Ok(_) => Ok((swapchain, use_opaque)), Err(err) => { warn!( @@ -1688,6 +1729,7 @@ where elements: &'a [E], clear_color: impl Into, frame_flags: FrameFlags, + presentation_mode: PresentationMode, ) -> Result, RenderFrameErrorType> where E: RenderElement, @@ -2025,6 +2067,7 @@ where output_geometry, try_assign_primary_plane, frame_flags, + presentation_mode, ) { Ok(direct_scan_out_plane) => { match direct_scan_out_plane.type_ { @@ -2036,8 +2079,17 @@ where } if let Some(state) = render_element_states.states.get_mut(element_id) { - state.presentation_state = RenderElementPresentationState::ZeroCopy; + state.presentation_state = if presentation_mode == PresentationMode::Async { + RenderElementPresentationState::Async + } else { + RenderElementPresentationState::ZeroCopy + }; state.visible_area += element_visible_area; + } else if presentation_mode == PresentationMode::Async { + render_element_states.states.insert( + element_id.clone(), + RenderElementState::zero_copy_async(*element_visible_area), + ); } else { render_element_states.states.insert( element_id.clone(), @@ -2086,6 +2138,7 @@ where self.supports_fencing, false, allow_partial_update, + presentation_mode, ) .is_err() { @@ -2386,6 +2439,48 @@ where } } + // In case we want to do an async page flip make sure + // that we at least now that it won't fail. + let presentation_mode = if presentation_mode == PresentationMode::Async + && next_frame_state.is_fully_compatible(previous_state) + { + let async_page_flip_might_supported = + next_frame_state.planes.iter().all(|(plane_handle, state)| { + let Some(buffer) = state.buffer() else { + return true; + }; + + let Some(plane_info) = self + .planes + .primary + .iter() + .find(|plane| plane.handle == *plane_handle) + .or_else(|| { + self.planes + .overlay + .iter() + .find(|plane| plane.handle == *plane_handle) + }) + else { + return false; + }; + + let Some(async_formats) = plane_info.formats_async.as_ref() else { + return true; + }; + + async_formats.contains(&buffer.format()) + }); + + if async_page_flip_might_supported { + presentation_mode + } else { + PresentationMode::VSync + } + } else { + PresentationMode::VSync + }; + let next_frame = PreparedFrame { kind: if allow_partial_update { PreparedFrameKind::Partial @@ -2393,6 +2488,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(), @@ -2502,14 +2598,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) @@ -2528,7 +2626,7 @@ where } #[profiling::function] - fn submit(&mut self) -> FrameResult<(), A, F> { + fn submit(&mut self) -> FrameResult { let QueuedFrame { mut prepared_frame, user_data, @@ -2538,11 +2636,69 @@ 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 is_fully_compatible = prepared_frame.frame.is_fully_compatible(previous_state); + + // 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 is_fully_compatible { + prepared_frame.frame.async_flip_failed = previous_state.async_flip_failed; + } + + 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 + && is_fully_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) @@ -2552,10 +2708,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; } @@ -2563,6 +2719,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)) @@ -2596,22 +2753,42 @@ 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, + presentation_mode: frame.presentation_mode, + }) + } + + /// Access the currently queued frame + 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, + }) + } + /// Marks the current frame as submitted. /// /// *Note*: Needs to be called, after the vblank event of the matching [`DrmDevice`](super::DrmDevice) /// 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 @@ -2816,6 +2993,7 @@ where output_geometry: Rectangle, try_assign_primary_plane: bool, frame_flags: FrameFlags, + presentation_mode: PresentationMode, ) -> Result> where R: Renderer + Bind, @@ -2844,6 +3022,7 @@ where output_transform, output_geometry, frame_flags, + presentation_mode, ) { Ok(plane) => { trace!( @@ -2867,6 +3046,7 @@ where output_transform, output_geometry, frame_flags, + presentation_mode, ) { trace!("assigned element {:?} to cursor {:?}", element.id(), plane.handle); return Ok(plane); @@ -2885,6 +3065,7 @@ where output_transform, output_geometry, frame_flags, + presentation_mode, ) { Ok(plane) => { trace!( @@ -2915,6 +3096,7 @@ where output_transform: Transform, output_geometry: Rectangle, frame_flags: FrameFlags, + presentation_mode: PresentationMode, ) -> Result> where R: Renderer, @@ -2982,7 +3164,14 @@ where } if element_config.failed_planes.primary { - return Err(Some(RenderingReason::ScanoutFailed)); + // Note: This might not be completely correct, but it is easier than remembering + // why it failed. But it should not be wrong either, so... + let rendering_reason = if presentation_mode == PresentationMode::Async { + RenderingReason::AsyncScanoutFailed + } else { + RenderingReason::ScanoutFailed + }; + return Err(Some(rendering_reason)); } let res = self.try_assign_plane( @@ -2991,9 +3180,10 @@ where self.surface.plane_info(), scale, frame_state, + presentation_mode, ); - if let Err(Some(RenderingReason::ScanoutFailed)) = res { + if res.is_err() { element_config.failed_planes.primary = true; } @@ -3014,6 +3204,7 @@ where output_transform: Transform, output_geometry: Rectangle, frame_flags: FrameFlags, + presentation_mode: PresentationMode, ) -> Option where R: Renderer, @@ -3037,6 +3228,14 @@ where return None; } + if presentation_mode == PresentationMode::Async { + trace!( + "skipping element {:?} on cursor plane(s), async presentation not supported", + element.id(), + ); + return None; + } + let element_size = output_transform.transform_size(element_geometry.size); // if the element is greater than the cursor size we can not @@ -3420,6 +3619,7 @@ where plane_info.handle, plane_state, false, + PresentationMode::VSync, ) .is_ok() }; @@ -3707,6 +3907,7 @@ where output_transform: Transform, output_geometry: Rectangle, frame_flags: FrameFlags, + presentation_mode: PresentationMode, ) -> Result> where R: Renderer, @@ -3856,7 +4057,14 @@ where return Err(None); } - self.try_assign_plane(element, element_config, plane, scale, frame_state) + self.try_assign_plane( + element, + element_config, + plane, + scale, + frame_state, + presentation_mode, + ) }; // First try to assign the element to a compatible plane, this can save us @@ -3882,7 +4090,12 @@ where trace!( "skipping direct scan-out on {:?} with zpos {:?}, element {:?} geometry {:?}, test already known to fail", plane.handle, plane.zpos, element_id, element_config.geometry, ); - rendering_reason = rendering_reason.or(Some(RenderingReason::ScanoutFailed)); + rendering_reason = + rendering_reason.or(Some(if presentation_mode == PresentationMode::Async { + RenderingReason::AsyncScanoutFailed + } else { + RenderingReason::ScanoutFailed + })); continue; } @@ -3890,7 +4103,10 @@ where Ok(plane) => return Ok(plane), Err(err) => { // if the test failed save that in the tested element state - if let Some(RenderingReason::ScanoutFailed) = err { + if matches!( + err, + Some(RenderingReason::ScanoutFailed) | Some(RenderingReason::AsyncScanoutFailed) + ) { element_config.failed_planes.overlay_bitmask |= 1 << index; } @@ -3916,6 +4132,7 @@ where plane: &PlaneInfo, scale: Scale, frame_state: &mut CompositorFrameState, + presentation_mode: PresentationMode, ) -> Result> where R: Renderer, @@ -3934,7 +4151,31 @@ where // Try to assign the element to a plane trace!("testing direct scan-out for element {:?} on {:?} with zpos {:?}: fb: {:?}, element_geometry: {:?}", element_id, plane.handle, plane.zpos, &element_config.buffer.fb, element_config.geometry); - if !plane.formats.contains(&element_config.properties.format) { + if presentation_mode == PresentationMode::Async { + // For async page flips we want to make sure to avoid unnecessary testing. + // If we know the supported formats we will make sure to only test if the + // buffer format is actually supported. + // + // If we don't know because the driver does not announce async formats we + // will take the slower path and test what actually works. + // At the time of writing only the intel driver supports the async formats. + let async_format_might_supported = plane + .formats_async + .as_ref() + .map(|formats| formats.contains(&element_config.properties.format)) + .unwrap_or(true); + + if !async_format_might_supported { + trace!( + "skipping direct scan-out on {:?} with zpos {:?} for element {:?}, async format {:?} not supported", + plane.handle, + plane.zpos, + element_id, + element_config.properties.format, + ); + return Err(Some(RenderingReason::AsyncFormatUnsupported)); + } + } else if !plane.formats.contains(&element_config.properties.format) { trace!( "skipping direct scan-out on {:?} with zpos {:?} for element {:?}, format {:?} not supported", plane.handle, @@ -4027,7 +4268,14 @@ where config: Some(config), }; - let res = if is_compatible { + // In case we try to change the presentation mode we have to re-test + let presentation_mode_unchanged = self + .pending_frame + .as_ref() + .map(|frame| frame.presentation_mode == presentation_mode) + .unwrap_or(false); + + let res = if is_compatible && presentation_mode_unchanged { trace!( "skipping atomic test for compatible element {:?} on {:?} with zpos {:?}", element_id, @@ -4037,6 +4285,26 @@ where frame_state.set_state(plane.handle, plane_state); true } else { + // Async page flips are a bit special as they are only allowed to + // change the fb_id, damage rects and fence. An async page flip will + // fail if we change anything else, including enabling the plane or + // changing the position. + // + // So how to we get the element on the plane if the plane support the format + // but we are not allowed to enable the plane? + // We will lie here and do a non async test to check if we can even put the + // element on the plane. This will make the async page flip fail, but might + // put the element on it and allow the next async flip to succeed. + // The next test will detect that the presentation mode has changed and the + // plane is compatible, in which case we will do an async test commit. + // If this fails we are out of luck and the element won't be put on a plane. + // The logic around failed planes should prevent a test cycle. + let presentation_mode_test = if is_compatible { + presentation_mode + } else { + PresentationMode::VSync + }; + frame_state .test_state( &self.surface, @@ -4044,6 +4312,7 @@ where plane.handle, plane_state, false, + presentation_mode_test, ) .is_ok() }; @@ -4065,7 +4334,11 @@ where element_id ); - Err(Some(RenderingReason::ScanoutFailed)) + if presentation_mode == PresentationMode::Async { + Err(Some(RenderingReason::AsyncScanoutFailed)) + } else { + Err(Some(RenderingReason::ScanoutFailed)) + } } } diff --git a/src/backend/drm/device/mod.rs b/src/backend/drm/device/mod.rs index 44fca2f63826..3a821670b667 100644 --- a/src/backend/drm/device/mod.rs +++ b/src/backend/drm/device/mod.rs @@ -113,6 +113,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, @@ -146,6 +147,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, @@ -218,12 +227,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(), @@ -377,6 +388,7 @@ impl DrmDevice { mapping, mode, connectors, + self.has_async_page_flips, )?) } else { DrmSurfaceInternal::Legacy(LegacyDrmSurface::new( @@ -385,6 +397,7 @@ impl DrmDevice { crtc, mode, connectors, + self.has_async_page_flips, )?) }; let internal = Arc::new(internal); diff --git a/src/backend/drm/mod.rs b/src/backend/drm/mod.rs index 2ccec8dcec3e..ce5da57f3314 100644 --- a/src/backend/drm/mod.rs +++ b/src/backend/drm/mod.rs @@ -145,6 +145,8 @@ pub struct PlaneInfo { pub zpos: Option, /// Formats supported by this plane pub formats: FormatSet, + /// Async formats supported by this plane + pub formats_async: Option, /// Recommended plane size in order of preference pub size_hints: Option>>, } @@ -187,12 +189,14 @@ fn planes( let zpos = plane_zpos(dev, plane).ok().flatten(); let type_ = plane_type(dev, plane)?; let formats = plane_formats(dev, plane)?; + let formats_async = plane_formats_async(dev, plane)?; let size_hints = plane_size_hints(dev, plane)?; let plane_info = PlaneInfo { handle: plane, type_, zpos, formats, + formats_async, size_hints, }; match type_ { @@ -409,6 +413,118 @@ fn plane_formats(dev: &(impl ControlDevice + DevPath), plane: plane::Handle) -> Ok(FormatSet::from_formats(formats)) } +fn plane_formats_async( + dev: &(impl ControlDevice + DevPath), + plane: plane::Handle, +) -> Result, DrmError> { + let Ok(1) = dev.get_driver_capability(DriverCapability::AddFB2Modifiers) else { + return Ok(None); + }; + + let set = dev.get_properties(plane).map_err(|source| { + DrmError::Access(AccessError { + errmsg: "Failed to query properties", + dev: dev.dev_path(), + source, + }) + })?; + let (handles, _) = set.as_props_and_values(); + + // for every handle ... + let prop = handles + .iter() + .find(|handle| { + // get information of that property + if let Ok(info) = dev.get_property(**handle) { + // to find out, if we got the handle of the "IN_FORMATS_ASYNC" property ... + if info + .name() + .to_str() + .map(|x| x == "IN_FORMATS_ASYNC") + .unwrap_or(false) + { + // so we can use that to get formats + return true; + } + } + false + }) + .copied(); + + let Some(prop) = prop else { + return Ok(None); + }; + + let prop_info = dev.get_property(prop).map_err(|source| { + DrmError::Access(AccessError { + errmsg: "Failed to query property", + dev: dev.dev_path(), + source, + }) + })?; + let (handles, raw_values) = set.as_props_and_values(); + let raw_value = raw_values[handles + .iter() + .enumerate() + .find_map(|(i, handle)| if *handle == prop { Some(i) } else { None }) + .unwrap()]; + + let drm::control::property::Value::Blob(blob) = prop_info.value_type().convert_value(raw_value) else { + return Ok(None); + }; + + let data = dev.get_property_blob(blob).map_err(|source| { + DrmError::Access(AccessError { + errmsg: "Failed to query property blob data", + dev: dev.dev_path(), + source, + }) + })?; + + let mut formats = IndexSet::new(); + + // be careful here, we have no idea about the alignment inside the blob, so always copy using `read_unaligned`, + // although slice::from_raw_parts would be so much nicer to iterate and to read. + unsafe { + let fmt_mod_blob_ptr = data.as_ptr() as *const drm_ffi::drm_format_modifier_blob; + let fmt_mod_blob = &*fmt_mod_blob_ptr; + + let formats_ptr: *const u32 = fmt_mod_blob_ptr + .cast::() + .offset(fmt_mod_blob.formats_offset as isize) as *const _; + let modifiers_ptr: *const drm_ffi::drm_format_modifier = fmt_mod_blob_ptr + .cast::() + .offset(fmt_mod_blob.modifiers_offset as isize) + as *const _; + #[allow(clippy::unnecessary_cast)] + let formats_ptr = formats_ptr as *const u32; + #[allow(clippy::unnecessary_cast)] + let modifiers_ptr = modifiers_ptr as *const drm_ffi::drm_format_modifier; + + for i in 0..fmt_mod_blob.count_modifiers { + let mod_info = modifiers_ptr.offset(i as isize).read_unaligned(); + for j in 0..64 { + if mod_info.formats & (1u64 << j) != 0 { + let code = DrmFourcc::try_from( + formats_ptr + .offset((j + mod_info.offset) as isize) + .read_unaligned(), + ) + .ok(); + let modifier = DrmModifier::from(mod_info.modifier); + if let Some(code) = code { + formats.insert(DrmFormat { code, modifier }); + } + } + } + } + } + + trace!("Supported async formats for plane ({:?}): {:?}", plane, formats); + + Ok(Some(FormatSet::from_formats(formats))) +} + #[cfg(feature = "backend_gbm")] fn plane_has_property( dev: &(impl drm::control::Device + DevPath), diff --git a/src/backend/drm/output.rs b/src/backend/drm/output.rs index 673067dc8c6b..64fce18ec469 100644 --- a/src/backend/drm/output.rs +++ b/src/backend/drm/output.rs @@ -18,7 +18,11 @@ use crate::{ gbm::GbmDevice, Allocator, }, - renderer::{element::RenderElement, Bind, Color32F, DebugFlags, Renderer, RendererSuper, Texture}, + drm::compositor::FrameRef, + renderer::{ + element::RenderElement, Bind, Color32F, DebugFlags, PresentationMode, Renderer, RendererSuper, + Texture, + }, }, output::OutputModeSource, }; @@ -99,7 +103,7 @@ where } } -impl<'a, A, F, U, G> fmt::Debug for LockedDrmOutputManager<'a, A, F, U, G> +impl fmt::Debug for LockedDrmOutputManager<'_, A, F, U, G> where A: Allocator + fmt::Debug, ::Buffer: fmt::Debug, @@ -150,7 +154,7 @@ where } } -impl<'a, A, F, U, G> LockedDrmOutputManager<'a, A, F, U, G> +impl LockedDrmOutputManager<'_, A, F, U, G> where A: Allocator, F: ExportFramebuffer<::Buffer>, @@ -277,7 +281,7 @@ where } } -impl<'a, A, F, U, G> LockedDrmOutputManager<'a, A, F, U, G> +impl LockedDrmOutputManager<'_, A, F, U, G> where A: Allocator + std::clone::Clone + std::fmt::Debug, ::Buffer: AsDmabuf, @@ -671,12 +675,28 @@ 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`] /// 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()) } @@ -695,6 +715,7 @@ where elements: &'a [E], clear_color: impl Into, frame_mode: FrameFlags, + presentation_mode: PresentationMode, ) -> Result, RenderFrameErrorType> where E: RenderElement, @@ -703,7 +724,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) }) } @@ -813,8 +834,8 @@ where } } -fn use_mode_internal<'a, A, F, U, G, R, E>( - compositor_list: &mut RwLockWriteGuard<'a, HashMap>>>, +fn use_mode_internal( + compositor_list: &mut RwLockWriteGuard<'_, HashMap>>>, crtc: &crtc::Handle, mode: Mode, allocator: &A, @@ -1017,7 +1038,13 @@ where .map(|(ref elements, ref 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 3390ff1f8425..180f00a31f02 100644 --- a/src/backend/drm/surface/atomic.rs +++ b/src/backend/drm/surface/atomic.rs @@ -3,7 +3,8 @@ use drm::control::connector::Interface; use drm::control::property::ValueType; use drm::control::Device as ControlDevice; use drm::control::{ - connector, crtc, dumbbuffer::DumbBuffer, framebuffer, plane, property, AtomicCommitFlags, Mode, PlaneType, + connector, crtc, dumbbuffer::DumbBuffer, framebuffer, plane, property, AtomicCommitFlags, Mode, + PageFlipFlags, PlaneType, }; #[cfg(debug_assertions)] @@ -172,6 +173,7 @@ pub struct AtomicDrmSurface { state: RwLock, pending: RwLock, pub(super) span: tracing::Span, + supports_async_page_flips: bool, } impl AtomicDrmSurface { @@ -184,6 +186,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(); @@ -209,6 +212,7 @@ impl AtomicDrmSurface { }; drop(_guard); + let surface = AtomicDrmSurface { fd, active, @@ -219,6 +223,7 @@ impl AtomicDrmSurface { state: RwLock::new(state), pending: RwLock::new(pending), span, + supports_async_page_flips, }; Ok(surface) @@ -694,6 +699,7 @@ impl AtomicDrmSurface { &self, planes: impl IntoIterator>, allow_modeset: bool, + async_commit: bool, ) -> Result<(), Error> { if !self.active.load(Ordering::SeqCst) { return Err(Error::DeviceInactive); @@ -702,13 +708,14 @@ impl AtomicDrmSurface { let current = self.state.read().unwrap(); let pending = self.pending.read().unwrap(); - self.test_state_internal(planes, allow_modeset, ¤t, &pending) + self.test_state_internal(planes, allow_modeset, async_commit, ¤t, &pending) } fn test_state_internal<'a>( &self, planes: impl IntoIterator>, allow_modeset: bool, + async_commit: bool, current: &'_ State, pending: &'_ State, ) -> Result<(), Error> { @@ -729,11 +736,15 @@ impl AtomicDrmSurface { &*planes, )?; - let flags = if allow_modeset { - AtomicCommitFlags::ALLOW_MODESET | AtomicCommitFlags::TEST_ONLY - } else { - AtomicCommitFlags::TEST_ONLY - }; + let mut flags = AtomicCommitFlags::TEST_ONLY; + if allow_modeset { + flags |= AtomicCommitFlags::ALLOW_MODESET; + } + + if async_commit { + flags |= AtomicCommitFlags::PAGE_FLIP_ASYNC; + } + self.fd.atomic_commit(flags, req.build()?).map_err(|source| { Error::Access(AccessError { errmsg: "Error testing state", @@ -743,13 +754,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); } @@ -824,9 +848,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. // @@ -867,8 +891,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); } @@ -895,8 +921,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 }, @@ -1088,37 +1114,6 @@ impl From for DrmRotation { } } -#[cfg(test)] -mod test { - use crate::{ - backend::drm::surface::atomic::to_fixed, - utils::{Physical, Rectangle}, - }; - - use super::AtomicDrmSurface; - - fn is_send() {} - - #[test] - fn surface_is_send() { - is_send::(); - } - - #[test] - fn test_fixed_point() { - let geometry: Rectangle = Rectangle::from_size((1920.0, 1080.0).into()); - let fixed = to_fixed(geometry.size.w) as u64; - assert_eq!(125829120, fixed); - } - - #[test] - fn test_fractional_fixed_point() { - let geometry: Rectangle = Rectangle::from_size((1920.1, 1080.0).into()); - let fixed = to_fixed(geometry.size.w) as u64; - assert_eq!(125835674, fixed); - } -} - #[cfg(debug_assertions)] struct AtomicRequest<'a> { mapping: &'a PropMapping, @@ -1660,3 +1655,34 @@ impl<'a> AtomicRequest<'a> { Ok(req) } } + +#[cfg(test)] +mod test { + use crate::{ + backend::drm::surface::atomic::to_fixed, + utils::{Physical, Rectangle}, + }; + + use super::AtomicDrmSurface; + + fn is_send() {} + + #[test] + fn surface_is_send() { + is_send::(); + } + + #[test] + fn test_fixed_point() { + let geometry: Rectangle = Rectangle::from_size((1920.0, 1080.0).into()); + let fixed = to_fixed(geometry.size.w) as u64; + assert_eq!(125829120, fixed); + } + + #[test] + fn test_fractional_fixed_point() { + let geometry: Rectangle = Rectangle::from_size((1920.1, 1080.0).into()); + let fixed = to_fixed(geometry.size.w) as u64; + assert_eq!(125835674, fixed); + } +} diff --git a/src/backend/drm/surface/gbm.rs b/src/backend/drm/surface/gbm.rs index e727b05b6248..9f49e24733ee 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::{connector, crtc, plane, Mode}; +use drm::control::{connector, crtc, plane, Mode, PageFlipFlags}; use drm::{Device, DriverCapability}; use indexmap::IndexSet; @@ -12,7 +12,7 @@ use crate::backend::allocator::{Allocator, Format, Fourcc, Modifier, Slot, Swapc use crate::backend::drm::error::AccessError; use crate::backend::drm::gbm::{framebuffer_from_bo, GbmFramebuffer}; use crate::backend::drm::{plane_has_property, DrmError, DrmSurface}; -use crate::backend::renderer::sync::SyncPoint; +use crate::backend::renderer::{sync::SyncPoint, PresentationMode}; use crate::backend::SwapBuffersError; use crate::utils::{DevPath, Physical, Rectangle, Transform}; @@ -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`]. @@ -229,7 +230,7 @@ where }), }; - match drm.test_state([plane_state], true) { + match drm.test_state([plane_state], true, false) { Ok(_) => { debug!("Chosen format: {:?}", format); Ok((buffer, swapchain, use_opaque)) @@ -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 581c6184c690..6bb6c4b2a5ad 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 0590324de67d..91834b5fdceb 100644 --- a/src/backend/drm/surface/mod.rs +++ b/src/backend/drm/surface/mod.rs @@ -3,7 +3,7 @@ use std::os::unix::io::{AsFd, BorrowedFd}; use std::sync::atomic::Ordering; use std::sync::Arc; -use drm::control::{connector, crtc, framebuffer, plane, Device as ControlDevice, Mode}; +use drm::control::{connector, crtc, framebuffer, plane, Device as ControlDevice, Mode, PageFlipFlags}; use drm::Device as BasicDevice; use libc::dev_t; @@ -374,9 +374,10 @@ impl DrmSurface { &self, planes: impl IntoIterator>, allow_modeset: bool, + async_commit: bool, ) -> Result<(), Error> { match &*self.internal { - DrmSurfaceInternal::Atomic(surf) => surf.test_state(planes, allow_modeset), + DrmSurfaceInternal::Atomic(surf) => surf.test_state(planes, allow_modeset, async_commit), DrmSurfaceInternal::Legacy(surf) => { let fb = ensure_legacy_planes(self, planes)?; @@ -406,13 +407,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 +429,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 906acc99a972..303de1e20634 100644 --- a/src/backend/renderer/element/mod.rs +++ b/src/backend/renderer/element/mod.rs @@ -36,7 +36,7 @@ use crate::{ use super::utils::Buffer; use super::{ utils::{CommitCounter, DamageSet, OpaqueRegions}, - Renderer, + PresentationMode, Renderer, }; pub mod memory; @@ -214,8 +214,12 @@ pub enum UnderlyingStorage<'a> { pub enum RenderingReason { /// The element buffer format is unsuited for direct scan-out FormatUnsupported, + /// The element buffer format is unsuited for async direct scan-out + AsyncFormatUnsupported, /// Element was selected for direct scan-out but failed ScanoutFailed, + /// Element was selected for async direct scan-out but failed + AsyncScanoutFailed, } /// Defines the presentation state of an element after rendering @@ -231,6 +235,8 @@ pub enum RenderElementPresentationState { }, /// The element was selected for zero-copy scan-out ZeroCopy, + /// The element was selected for zero-copy async scan-out + Async, /// The element was skipped as it is current not visible Skipped, } @@ -495,6 +501,10 @@ pub trait Element { fn kind(&self) -> Kind { Kind::default() } + /// Hint for DRM backend on how the element should be presented + fn presentation_mode(&self) -> PresentationMode { + PresentationMode::VSync + } } /// A single render element @@ -577,6 +587,10 @@ where fn kind(&self) -> Kind { (*self).kind() } + + fn presentation_mode(&self) -> PresentationMode { + (*self).presentation_mode() + } } impl RenderElement for &E @@ -882,6 +896,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( @@ -1512,6 +1539,10 @@ where fn kind(&self) -> Kind { self.0.kind() } + + 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 b0dff1f6e1ad..e9a820764197 100644 --- a/src/backend/renderer/element/surface.rs +++ b/src/backend/renderer/element/surface.rs @@ -73,7 +73,7 @@ use crate::{ Buffer, DamageSet, DamageSnapshot, OpaqueRegions, RendererSurfaceState, RendererSurfaceStateUserData, SurfaceView, }, - Color32F, Frame, ImportAll, Renderer, Texture, + Color32F, Frame, ImportAll, PresentationMode, Renderer, Texture, }, utils::{Buffer as BufferCoords, Logical, Physical, Point, Rectangle, Scale, Size, Transform}, wayland::{ @@ -426,6 +426,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/element/utils/wayland.rs b/src/backend/renderer/element/utils/wayland.rs index 8c040b524332..bd45de5695f4 100644 --- a/src/backend/renderer/element/utils/wayland.rs +++ b/src/backend/renderer/element/utils/wayland.rs @@ -13,6 +13,7 @@ pub fn select_dmabuf_feedback<'a>( render_element_states: &RenderElementStates, default_feedback: &'a DmabufFeedback, scanout_feedback: &'a DmabufFeedback, + async_feedback: &'a DmabufFeedback, ) -> &'a DmabufFeedback { let id = element.into(); @@ -25,9 +26,13 @@ pub fn select_dmabuf_feedback<'a>( Some(RenderingReason::FormatUnsupported) | Some(RenderingReason::ScanoutFailed) => { scanout_feedback } + Some(RenderingReason::AsyncFormatUnsupported) | Some(RenderingReason::AsyncScanoutFailed) => { + async_feedback + } None => default_feedback, }, RenderElementPresentationState::ZeroCopy => scanout_feedback, + RenderElementPresentationState::Async => async_feedback, RenderElementPresentationState::Skipped => default_feedback, } } diff --git a/src/backend/renderer/gles/profiler.rs b/src/backend/renderer/gles/profiler.rs index 01a0b9c20815..898658a742e6 100644 --- a/src/backend/renderer/gles/profiler.rs +++ b/src/backend/renderer/gles/profiler.rs @@ -72,7 +72,7 @@ pub(crate) struct ScopedGpuSpan<'a, 'b> { gl: &'b ffi::Gles2, } -impl<'a, 'b> Drop for ScopedGpuSpan<'a, 'b> { +impl Drop for ScopedGpuSpan<'_, '_> { fn drop(&mut self) { let span = self.span.take().unwrap(); self.profiler.exit(self.gl, span); diff --git a/src/backend/renderer/mod.rs b/src/backend/renderer/mod.rs index 1955224c5ab4..a3abb9f71ec3 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, diff --git a/src/desktop/wayland/utils.rs b/src/desktop/wayland/utils.rs index 2485959ae7f2..329134f9e943 100644 --- a/src/desktop/wayland/utils.rs +++ b/src/desktop/wayland/utils.rs @@ -470,7 +470,12 @@ pub fn surface_presentation_feedback_flags_from_states( ) -> wp_presentation_feedback::Kind { let zero_copy = states .element_render_state(surface) - .map(|state| state.presentation_state == RenderElementPresentationState::ZeroCopy) + .map(|state| { + matches!( + state.presentation_state, + RenderElementPresentationState::ZeroCopy | RenderElementPresentationState::Async + ) + }) .unwrap_or(false); if zero_copy {