Skip to content

Commit

Permalink
Initial support for DRM async page flip
Browse files Browse the repository at this point in the history
  • Loading branch information
PolyMeilex committed May 31, 2024
1 parent 12bcefc commit ee80df4
Show file tree
Hide file tree
Showing 10 changed files with 227 additions and 62 deletions.
27 changes: 20 additions & 7 deletions anvil/src/udev.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ use smithay::{
gles::{GlesRenderer, GlesTexture},
multigpu::{gbm::GbmGlesBackend, GpuManager, MultiRenderer},
sync::SyncPoint,
Bind, DebugFlags, ExportMem, ImportDma, ImportMemWl, Offscreen, Renderer,
Bind, DebugFlags, ExportMem, ImportDma, ImportMemWl, Offscreen, PresentationMode, Renderer,
},
session::{
libseat::{self, LibSeatSession},
Expand Down Expand Up @@ -560,6 +560,7 @@ struct SurfaceCompositorRenderResult<'a> {
states: RenderElementStates,
sync: Option<SyncPoint>,
damage: Option<&'a Vec<Rectangle<i32, Physical>>>,
presentation_mode: PresentationMode,
}

impl SurfaceComposition {
Expand Down Expand Up @@ -610,14 +611,15 @@ impl SurfaceComposition {
sync: Option<SyncPoint>,
damage: Option<Vec<Rectangle<i32, Physical>>>,
user_data: Option<OutputPresentationFeedback>,
presentation_mode: PresentationMode,
) -> Result<(), SwapBuffersError> {
match self {
SurfaceComposition::Surface { surface, .. } => surface
.queue_buffer(sync, damage, user_data)
.queue_buffer(sync, damage, user_data, presentation_mode)
.map_err(Into::<SwapBuffersError>::into),
SurfaceComposition::Compositor(c) => c
.queue_frame(user_data, presentation_mode)
.map_err(Into::<SwapBuffersError>::into),
SurfaceComposition::Compositor(c) => {
c.queue_frame(user_data).map_err(Into::<SwapBuffersError>::into)
}
}
}

Expand Down Expand Up @@ -655,6 +657,7 @@ impl SurfaceComposition {
damage: res.damage,
states: res.states,
sync: rendered.then_some(res.sync),
presentation_mode: PresentationMode::VSync,
}
})
.map_err(|err| match err {
Expand All @@ -671,11 +674,13 @@ impl SurfaceComposition {
if let PrimaryPlaneElement::Swapchain(element) = render_frame_result.primary_element {
element.sync.wait();
}
let presentation_mode = render_frame_result.presentation_mode();
SurfaceCompositorRenderResult {
rendered: !render_frame_result.is_empty,
damage: None,
states: render_frame_result.states,
sync: None,
presentation_mode,
}
})
.map_err(|err| match err {
Expand Down Expand Up @@ -1616,6 +1621,7 @@ fn render_surface<'a>(
states,
sync,
damage,
presentation_mode,
} = surface
.compositor
.render_frame::<_, _, GlesTexture>(renderer, &elements, clear_color)?;
Expand All @@ -1639,7 +1645,12 @@ fn render_surface<'a>(
let damage = damage.cloned();
surface
.compositor
.queue_frame(sync, damage, Some(output_presentation_feedback))
.queue_frame(
sync,
damage,
Some(output_presentation_feedback),
presentation_mode,
)
.map_err(Into::<SwapBuffersError>::into)?;
}

Expand All @@ -1653,7 +1664,9 @@ fn initial_render(
surface
.compositor
.render_frame::<_, CustomRenderElements<_>, GlesTexture>(renderer, &[], CLEAR_COLOR)?;
surface.compositor.queue_frame(None, None, None)?;
surface
.compositor
.queue_frame(None, None, None, PresentationMode::VSync)?;
surface.compositor.reset_buffers();

Ok(())
Expand Down
102 changes: 83 additions & 19 deletions src/backend/drm/compositor/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,10 @@
//! # use std::{collections::HashSet, mem::MaybeUninit};
//! #
//! use smithay::{
//! backend::drm::{compositor::DrmCompositor, DrmSurface},
//! backend::{
//! drm::{compositor::DrmCompositor, DrmSurface},
//! renderer::PresentationMode,
//! },
//! output::{Output, PhysicalProperties, Subpixel},
//! utils::Size,
//! };
Expand Down Expand Up @@ -108,7 +111,7 @@
//! .expect("failed to render frame");
//!
//! if !render_frame_result.is_empty {
//! compositor.queue_frame(()).expect("failed to queue frame");
//! compositor.queue_frame((), PresentationMode::VSync).expect("failed to queue frame");
//!
//! // ...wait for VBlank event
//!
Expand All @@ -130,7 +133,7 @@ use std::{
};

use drm::{
control::{connector, crtc, framebuffer, plane, Mode, PlaneType},
control::{connector, crtc, framebuffer, plane, Mode, PageFlipFlags, PlaneType},
Device, DriverCapability,
};
use drm_fourcc::{DrmFormat, DrmFourcc, DrmModifier};
Expand Down Expand Up @@ -162,7 +165,7 @@ use crate::{
},
sync::SyncPoint,
utils::{CommitCounter, DamageBag, DamageSet, DamageSnapshot, OpaqueRegions},
Bind, Blit, DebugFlags, Frame as RendererFrame, Renderer, Texture,
Bind, Blit, DebugFlags, Frame as RendererFrame, PresentationMode, Renderer, Texture,
},
SwapBuffersError,
},
Expand Down Expand Up @@ -732,12 +735,12 @@ impl<B: Framebuffer> FrameState<B> {
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.commit(
self.build_planes(surface, supports_fencing, allow_partial_update),
event,
flip_flags,
)
}

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

Expand Down Expand Up @@ -932,6 +935,9 @@ pub struct PrimarySwapchainElement<B: Buffer, F: Framebuffer> {
pub transform: Transform,
/// The damage on the primary plane
pub damage: DamageSnapshot<i32, BufferCoords>,
/// Presentation preference for this swapchain element, created by combining mode of all elements
/// rendered on this buffer
pub presentation_mode: Option<PresentationMode>,
}

impl<B: Buffer, F: Framebuffer> PrimarySwapchainElement<B, F> {
Expand Down Expand Up @@ -989,7 +995,22 @@ pub struct RenderFrameResult<'a, B: Buffer, F: Framebuffer, E> {
supports_fencing: bool,
}

impl<'a, B: Buffer, F: Framebuffer, E> RenderFrameResult<'a, B, F, E> {
fn combine_modes(a: Option<PresentationMode>, b: Option<PresentationMode>) -> Option<PresentationMode> {
match (a, b) {
// If either one wants VSync we go with VSync
(Some(PresentationMode::VSync), _) | (_, Some(PresentationMode::VSync)) => {
Some(PresentationMode::VSync)
}
// If neither wants VSync, and one wants Async we go with Async
(Some(PresentationMode::Async), _) | (_, Some(PresentationMode::Async)) => {
Some(PresentationMode::Async)
}
// Elements have no preference
(None, None) => None,
}
}

impl<'a, B: Buffer, F: Framebuffer, E: Element> RenderFrameResult<'a, B, F, E> {
/// Returns if synchronization with kms submission can't be guaranteed through the available apis.
pub fn needs_sync(&self) -> bool {
if let PrimaryPlaneElement::Swapchain(ref element) = self.primary_element {
Expand All @@ -998,6 +1019,24 @@ impl<'a, B: Buffer, F: Framebuffer, E> RenderFrameResult<'a, B, F, E> {
false
}
}

/// Hint for DRM backend on how the surface should be presented
pub fn presentation_mode(&self) -> PresentationMode {
let mut res = None;

res = match &self.primary_element {
PrimaryPlaneElement::Swapchain(e) => combine_modes(res, e.presentation_mode),
PrimaryPlaneElement::Element(e) => combine_modes(res, e.presentation_mode()),
};

for e in self.overlay_elements.iter() {
res = combine_modes(res, e.presentation_mode());
}

// Let's assume that cursor element does not care about tearing

res.unwrap_or(PresentationMode::VSync)
}
}

struct SwapchainElement<'a, 'b, B: Buffer> {
Expand Down Expand Up @@ -1472,6 +1511,7 @@ where
struct QueuedFrame<A: Allocator, F: ExportFramebuffer<<A as Allocator>::Buffer>, U> {
prepared_frame: PreparedFrame<A, F>,
user_data: U,
presentation_mode: PresentationMode,
}

impl<A, F, U> std::fmt::Debug for QueuedFrame<A, F, U>
Expand Down Expand Up @@ -2357,6 +2397,7 @@ where
.map(|config| matches!(&config.buffer, ScanoutBuffer::Swapchain(_)))
.unwrap_or(false);

let mut swapchain_presentation_mode = None;
if render {
trace!(
"rendering {} elements on the primary {:?}",
Expand Down Expand Up @@ -2428,6 +2469,11 @@ where
)
.collect::<Vec<_>>();

for e in elements.iter() {
swapchain_presentation_mode =
combine_modes(swapchain_presentation_mode, e.presentation_mode());
}

let render_res =
self.damage_tracker
.render_output_with(renderer, dmabuf, age, &elements, clear_color);
Expand Down Expand Up @@ -2541,6 +2587,7 @@ where
transform: output_transform,
damage: self.primary_plane_damage_bag.snapshot(),
sync,
presentation_mode: swapchain_presentation_mode,
})
} else {
PrimaryPlaneElement::Element(primary_plane_scanout_element.unwrap())
Expand All @@ -2564,7 +2611,7 @@ where
supports_fencing: self.supports_fencing,
};

// We only store the next frame if it acutaly contains any changes or if a commit is pending
// We only store the next frame if it actually contains any changes or if a commit is pending
// Storing the (empty) frame could keep a reference to wayland buffers which
// could otherwise be potentially released on `frame_submitted`
if !next_frame.is_empty() {
Expand All @@ -2591,7 +2638,11 @@ where
///
/// `user_data` can be used to attach some data to a specific buffer and later retrieved with [`DrmCompositor::frame_submitted`]
#[profiling::function]
pub fn queue_frame(&mut self, user_data: U) -> FrameResult<(), A, F> {
pub fn queue_frame(
&mut self,
user_data: U,
presentation_mode: PresentationMode,
) -> FrameResult<(), A, F> {
if !self.surface.is_active() {
return Err(FrameErrorType::<A, F>::DrmError(DrmError::DeviceInactive));
}
Expand All @@ -2617,6 +2668,7 @@ where
self.queued_frame = Some(QueuedFrame {
prepared_frame,
user_data,
presentation_mode,
});
if self.pending_frame.is_none() {
self.submit()?;
Expand Down Expand Up @@ -2644,17 +2696,29 @@ where
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, true)
prepared_frame.frame.commit(
&self.surface,
self.supports_fencing,
allow_partial_update,
flip_flags,
)
} else {
prepared_frame
.frame
.page_flip(&self.surface, self.supports_fencing, allow_partial_update, true)
prepared_frame.frame.page_flip(
&self.surface,
self.supports_fencing,
allow_partial_update,
flip_flags,
)
};

match flip {
Expand Down Expand Up @@ -3463,7 +3527,7 @@ where
let previous_fb_cache = self
.previous_element_states
.get_mut(element_id)
// Note: We can mem::take the old fb_cache here here as we guarante that
// Note: We can mem::take the old fb_cache here here as we guarantee that
// the element state will always overwrite the current state at the end of render_frame
.map(|state| std::mem::take(&mut state.fb_cache))
.unwrap_or_default();
Expand Down Expand Up @@ -3626,7 +3690,7 @@ where
});

if !(primary_plane_changed || overlay_plane_changed) {
// we now know that nothing changed and we can assume any previouly failed
// we now know that nothing changed and we can assume any previously failed
// test will again fail
let instance_state = element_state
.instances
Expand Down
13 changes: 13 additions & 0 deletions src/backend/drm/device/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ pub struct DrmDevice {
pub(super) dev_id: dev_t,
pub(crate) internal: Arc<DrmDeviceInternal>,
has_universal_planes: bool,
has_async_page_flips: bool,
cursor_size: Size<u32, Buffer>,
resources: ResourceHandles,
plane_claim_storage: PlaneClaimStorage,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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(),
Expand Down Expand Up @@ -359,6 +370,7 @@ impl DrmDevice {
mapping,
mode,
connectors,
self.has_async_page_flips,
)?)
} else {
DrmSurfaceInternal::Legacy(LegacyDrmSurface::new(
Expand All @@ -367,6 +379,7 @@ impl DrmDevice {
crtc,
mode,
connectors,
self.has_async_page_flips,
)?)
};
let internal = Arc::new(internal);
Expand Down
Loading

0 comments on commit ee80df4

Please sign in to comment.