diff --git a/anvil/src/udev.rs b/anvil/src/udev.rs index c4d454bdfbec..c9e5d1069dde 100644 --- a/anvil/src/udev.rs +++ b/anvil/src/udev.rs @@ -67,8 +67,9 @@ use smithay::{ control::{connector, crtc, Device, ModeTypeFlags}, Device as _, }, + gbm::Modifier, input::{DeviceCapability, Libinput}, - rustix::fs::OFlags, + rustix::{self, fs::OFlags}, wayland_protocols::wp::{ linux_dmabuf::zv1::server::zwp_linux_dmabuf_feedback_v1, presentation_time::server::wp_presentation_feedback, @@ -774,11 +775,13 @@ fn get_surface_dmabuf_feedback( .single_renderer(&render_node) .ok()? .dmabuf_formats() + .filter(|f| f.modifier == Modifier::Linear) .collect::>(); let all_render_formats = primary_formats .iter() .chain(render_formats.iter()) + .filter(|f| f.modifier == Modifier::Linear) .copied() .collect::>(); @@ -903,7 +906,14 @@ impl AnvilState { .gpus .single_renderer(&device.render_node) .unwrap(); - let render_formats = renderer.as_mut().egl_context().dmabuf_render_formats().clone(); + let render_formats = renderer + .as_mut() + .egl_context() + .dmabuf_render_formats() + .iter() + .filter(|f| f.modifier == Modifier::Linear) + .copied() + .collect(); let output_name = format!("{}-{}", connector.interface().as_str(), connector.interface_id()); info!(?crtc, "Trying to setup connector {}", output_name,); @@ -1450,6 +1460,7 @@ impl AnvilState { Some(DrmError::DeviceInactive) => true, Some(DrmError::Access(DrmAccessError { source, .. })) => { source.kind() == io::ErrorKind::PermissionDenied + || source.raw_os_error() == Some(rustix::io::Errno::BUSY.raw_os_error()) } _ => false, }, diff --git a/src/backend/drm/compositor/mod.rs b/src/backend/drm/compositor/mod.rs index 53d4171e85d3..d7bf77cdf93f 100644 --- a/src/backend/drm/compositor/mod.rs +++ b/src/backend/drm/compositor/mod.rs @@ -508,6 +508,7 @@ impl Clone for PlaneState { #[derive(Debug)] struct FrameState> { planes: HashMap>, + async_flip_failed: bool, } impl> FrameState { @@ -607,11 +608,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, other: &Self) -> bool { + self.planes.iter().all(|(handle, state)| { + other + .plane_state(*handle) + .map(|other| state.is_compatible(other)) + .unwrap_or(false) + }) + } + #[profiling::function] fn set_state(&mut self, plane: plane::Handle, state: PlaneState) { let current_config = match self.planes.get_mut(&plane) { @@ -637,7 +650,10 @@ 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, PageFlipFlags::empty()), + allow_modeset, + ); if res.is_err() { // test failed, restore previous state @@ -661,12 +677,7 @@ impl FrameState { allow_partial_update: bool, ) -> 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"); @@ -677,7 +688,12 @@ impl FrameState { } let res = surface.test_state( - self.build_planes(surface, supports_fencing, allow_partial_update), + self.build_planes( + surface, + supports_fencing, + allow_partial_update, + PageFlipFlags::empty(), + ), allow_modeset, ); @@ -696,12 +712,16 @@ impl FrameState { surface: &DrmSurface, supports_fencing: bool, allow_partial_update: bool, - flip_flags: PageFlipFlags, ) -> 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), - flip_flags, + self.build_planes( + surface, + supports_fencing, + allow_partial_update, + PageFlipFlags::EVENT, + ), + PageFlipFlags::EVENT, ) } @@ -715,7 +735,7 @@ impl FrameState { ) -> 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), + self.build_planes(surface, supports_fencing, allow_partial_update, flip_flags), flip_flags, ) } @@ -726,6 +746,7 @@ impl FrameState { surface: &'a DrmSurface, supports_fencing: bool, allow_partial_update: bool, + flip_flags: PageFlipFlags, ) -> impl IntoIterator> { for (_, state) in self.planes.iter_mut().filter(|(_, state)| !state.skip) { if let Some(config) = state.config.as_mut() { @@ -755,17 +776,27 @@ impl FrameState { }) .map(move |(handle, state)| super::surface::PlaneState { handle: *handle, - config: state.config.as_mut().map(|config| super::PlaneConfig { - src: config.properties.src, - dst: config.properties.dst, - alpha: config.properties.alpha, - transform: config.properties.transform, - damage_clips: config.damage_clips.as_ref().map(|d| d.blob()), - fb: *config.buffer.as_ref(), - fence: config - .sync - .as_ref() - .and_then(|(_, fence)| fence.as_ref().map(|fence| fence.as_fd())), + config: state.config.as_mut().map(|config| { + let (damage_clips, fence) = if flip_flags.contains(PageFlipFlags::ASYNC) { + (None, None) + } else { + ( + config.damage_clips.as_ref().map(|d| d.blob()), + config + .sync + .as_ref() + .and_then(|(_, fence)| fence.as_ref().map(|fence| fence.as_fd())), + ) + }; + super::PlaneConfig { + src: config.properties.src, + dst: config.properties.dst, + alpha: config.properties.alpha, + transform: config.properties.transform, + damage_clips, + fb: *config.buffer.as_ref(), + fence, + } }), }) } @@ -2549,6 +2580,7 @@ where /// /// `user_data` can be used to attach some data to a specific buffer and later retrieved with [`DrmCompositor::frame_submitted`] #[profiling::function] + #[instrument(level = "trace", parent = &self.span, skip_all)] pub fn queue_frame( &mut self, user_data: U, @@ -2603,6 +2635,7 @@ where } #[profiling::function] + #[instrument(level = "info", parent = &self.span, skip_all)] fn submit(&mut self) -> FrameResult<(), A, F> { let QueuedFrame { mut prepared_frame, @@ -2610,26 +2643,83 @@ where presentation_mode, } = self.queued_frame.take().unwrap(); - let mut flip_flags = PageFlipFlags::EVENT; - if presentation_mode == PresentationMode::Async { - flip_flags |= PageFlipFlags::ASYNC; - } - let allow_partial_update = prepared_frame.kind == PreparedFrameKind::Partial; let flip = if self.surface.commit_pending() { - prepared_frame.frame.commit( - &self.surface, - self.supports_fencing, - allow_partial_update, - flip_flags, - ) + prepared_frame + .frame + .commit(&self.surface, self.supports_fencing, allow_partial_update) } else { - prepared_frame.frame.page_flip( + 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.planes.primary.handle) + .and_then(|state| { + previous_state + .plane_state(self.planes.primary.handle) + .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.planes.primary.handle) + .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 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, flip_flags, - ) + ); + + // 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, + ) + } else { + flip + } }; match flip { diff --git a/src/backend/drm/error.rs b/src/backend/drm/error.rs index f0ae60af72db..73f2339398d9 100644 --- a/src/backend/drm/error.rs +++ b/src/backend/drm/error.rs @@ -88,7 +88,7 @@ impl From for SwapBuffersError { }) if matches!( source.kind(), ErrorKind::PermissionDenied | ErrorKind::WouldBlock | ErrorKind::Interrupted - ) => + ) || source.raw_os_error() == Some(libc::EBUSY) => { SwapBuffersError::TemporaryFailure(Box::new(Error::Access(AccessError { errmsg,