Skip to content

Commit

Permalink
wip: make async page flips work with atomic backend
Browse files Browse the repository at this point in the history
  • Loading branch information
cmeissl authored and PolyMeilex committed May 31, 2024
1 parent ee80df4 commit e62c854
Show file tree
Hide file tree
Showing 3 changed files with 141 additions and 40 deletions.
15 changes: 13 additions & 2 deletions anvil/src/udev.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -774,11 +775,13 @@ fn get_surface_dmabuf_feedback(
.single_renderer(&render_node)
.ok()?
.dmabuf_formats()
.filter(|f| f.modifier == Modifier::Linear)
.collect::<HashSet<_>>();

let all_render_formats = primary_formats
.iter()
.chain(render_formats.iter())
.filter(|f| f.modifier == Modifier::Linear)
.copied()
.collect::<HashSet<_>>();

Expand Down Expand Up @@ -903,7 +906,14 @@ impl AnvilState<UdevData> {
.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,);
Expand Down Expand Up @@ -1450,6 +1460,7 @@ impl AnvilState<UdevData> {
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,
},
Expand Down
164 changes: 127 additions & 37 deletions src/backend/drm/compositor/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -525,6 +525,7 @@ impl<B> Clone for PlaneState<B> {
#[derive(Debug)]
struct FrameState<B: AsRef<framebuffer::Handle>> {
planes: SmallVec<[(plane::Handle, PlaneState<B>); 10]>,
async_flip_failed: bool,
}

impl<B: AsRef<framebuffer::Handle>> FrameState<B> {
Expand Down Expand Up @@ -645,11 +646,23 @@ impl<B: Framebuffer> FrameState<B> {
.map(|info| (info.handle, PlaneState::default())),
);

FrameState { planes: tmp }
FrameState {
planes: tmp,
async_flip_failed: false,
}
}
}

impl<B: Framebuffer> FrameState<B> {
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]
#[inline]
fn set_state(&mut self, plane: plane::Handle, state: PlaneState<B>) {
Expand All @@ -676,7 +689,10 @@ impl<B: Framebuffer> FrameState<B> {
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
Expand All @@ -700,12 +716,7 @@ impl<B: Framebuffer> FrameState<B> {
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");
Expand All @@ -716,7 +727,12 @@ impl<B: Framebuffer> FrameState<B> {
}

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,
);

Expand All @@ -735,12 +751,16 @@ impl<B: Framebuffer> FrameState<B> {
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,
)
}

Expand All @@ -754,7 +774,7 @@ impl<B: Framebuffer> FrameState<B> {
) -> 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,
)
}
Expand All @@ -765,6 +785,7 @@ impl<B: Framebuffer> FrameState<B> {
surface: &'a DrmSurface,
supports_fencing: bool,
allow_partial_update: bool,
flip_flags: PageFlipFlags,
) -> impl IntoIterator<Item = super::PlaneState<'a>> {
for (_, state) in self.planes.iter_mut().filter(|(_, state)| !state.skip) {
if let Some(config) = state.config.as_mut() {
Expand Down Expand Up @@ -794,17 +815,27 @@ impl<B: Framebuffer> FrameState<B> {
})
.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,
}
}),
})
}
Expand Down Expand Up @@ -2638,6 +2669,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,
Expand Down Expand Up @@ -2692,33 +2724,91 @@ where
}

#[profiling::function]
#[instrument(level = "info", parent = &self.span, skip_all)]
fn submit(&mut self) -> FrameResult<(), A, F> {
let QueuedFrame {
mut prepared_frame,
user_data,
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 {
Expand Down
2 changes: 1 addition & 1 deletion src/backend/drm/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ impl From<Error> 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,
Expand Down

0 comments on commit e62c854

Please sign in to comment.