From 18a6d249ee49cc60f7f59f91f4801ea5812dffc8 Mon Sep 17 00:00:00 2001 From: Christian Meissl Date: Fri, 21 Jul 2023 22:13:12 +0200 Subject: [PATCH] drm: reduce amount of atomic tests --- src/backend/drm/compositor/mod.rs | 1266 +++++++++++++++++++++-------- 1 file changed, 916 insertions(+), 350 deletions(-) diff --git a/src/backend/drm/compositor/mod.rs b/src/backend/drm/compositor/mod.rs index 1288f9219e6a..c372e6c76928 100644 --- a/src/backend/drm/compositor/mod.rs +++ b/src/backend/drm/compositor/mod.rs @@ -140,7 +140,7 @@ use crate::{ backend::{ allocator::{ dmabuf::{AsDmabuf, Dmabuf}, - format::get_opaque, + format::{get_opaque, has_alpha}, gbm::{GbmAllocator, GbmDevice}, Allocator, Buffer, Slot, Swapchain, }, @@ -272,26 +272,61 @@ impl Framebuffer for DrmScanoutBuffer { } #[derive(Debug, Clone, Hash, PartialEq, Eq)] -enum ElementFramebufferCacheKey { +enum ElementFramebufferCacheBuffer { Wayland(wayland_server::Weak), } +impl From<&UnderlyingStorage> for ElementFramebufferCacheBuffer { + fn from(storage: &UnderlyingStorage) -> Self { + match storage { + UnderlyingStorage::Wayland(buffer) => Self::Wayland(buffer.downgrade()), + } + } +} + +#[derive(Debug, Clone, Hash, PartialEq, Eq)] +struct ElementFramebufferCacheKey { + allow_opaque_fallback: bool, + buffer: ElementFramebufferCacheBuffer, +} + impl ElementFramebufferCacheKey { - fn is_alive(&self) -> bool { - match self { - ElementFramebufferCacheKey::Wayland(buffer) => buffer.upgrade().is_ok(), + fn from_underlying_storage(storage: &UnderlyingStorage, allow_opaque_fallback: bool) -> Self { + Self { + allow_opaque_fallback, + buffer: ElementFramebufferCacheBuffer::from(storage), } } } -impl From<&UnderlyingStorage> for ElementFramebufferCacheKey { - fn from(storage: &UnderlyingStorage) -> Self { - match storage { - UnderlyingStorage::Wayland(buffer) => Self::Wayland(buffer.downgrade()), +impl ElementFramebufferCacheKey { + fn is_alive(&self) -> bool { + match self.buffer { + ElementFramebufferCacheBuffer::Wayland(ref buffer) => buffer.upgrade().is_ok(), } } } +#[derive(Debug, Default, Clone, Copy, PartialEq)] +struct PlanesSnapshot { + primary: bool, + cursor: bool, + overlay_bitmask: u32, +} + +#[derive(Debug)] +struct ElementInstanceState { + properties: PlaneProperties, + active_planes: PlanesSnapshot, + failed_planes: PlanesSnapshot, +} + +#[derive(Debug)] +struct ElementState { + instances: Vec, + fb_cache: ElementFramebufferCache, +} + #[derive(Debug)] struct ElementFramebufferCache where @@ -305,14 +340,29 @@ impl ElementFramebufferCache where B: Framebuffer, { - fn get(&self, buffer: &UnderlyingStorage) -> Option, ExportBufferError>> { + fn get( + &self, + buffer: &UnderlyingStorage, + allow_opaque_fallback: bool, + ) -> Option, ExportBufferError>> { self.fb_cache - .get(&ElementFramebufferCacheKey::from(buffer)) + .get(&ElementFramebufferCacheKey::from_underlying_storage( + buffer, + allow_opaque_fallback, + )) .cloned() } - fn insert(&mut self, buffer: &UnderlyingStorage, fb: Result, ExportBufferError>) { - self.fb_cache.insert(ElementFramebufferCacheKey::from(buffer), fb); + fn insert( + &mut self, + buffer: &UnderlyingStorage, + fb: Result, ExportBufferError>, + allow_opaque_fallback: bool, + ) { + self.fb_cache.insert( + ElementFramebufferCacheKey::from_underlying_storage(buffer, allow_opaque_fallback), + fb, + ); } fn cleanup(&mut self) { @@ -342,37 +392,72 @@ where } } -#[derive(Debug)] -struct PlaneConfig { +#[derive(Debug, Copy, Clone, PartialEq)] +struct PlaneProperties { pub src: Rectangle, pub dst: Rectangle, pub transform: Transform, - pub damage_clips: Option, pub alpha: f32, + pub format: DrmFormat, +} + +impl PlaneProperties { + fn is_compatible(&self, other: &PlaneProperties) -> bool { + self.src == other.src + && self.dst == other.dst + && self.transform == other.transform + && self.alpha == other.alpha + && self.format == other.format + } +} + +struct ElementPlaneConfig<'a, B> { + z_index: usize, + geometry: Rectangle, + properties: PlaneProperties, + buffer: Owned, + failed_planes: &'a mut PlanesSnapshot, +} + +#[derive(Debug)] +struct PlaneConfig { + pub properties: PlaneProperties, pub buffer: Owned, + pub damage_clips: Option, pub plane_claim: PlaneClaim, pub sync: Option<(SyncPoint, Option>)>, } +impl PlaneConfig { + pub fn is_compatible(&self, other: &PlaneConfig) -> bool { + self.properties.is_compatible(&other.properties) + } +} + impl Clone for PlaneConfig { fn clone(&self) -> Self { Self { - src: self.src, - dst: self.dst, - transform: self.transform, - damage_clips: self.damage_clips.clone(), - alpha: self.alpha, + properties: self.properties, buffer: self.buffer.clone(), + damage_clips: self.damage_clips.clone(), plane_claim: self.plane_claim.clone(), sync: self.sync.clone(), } } } +#[derive(Debug, Clone)] +struct PlaneElementState { + id: Id, + commit: CommitCounter, + z_index: usize, +} + #[derive(Debug)] struct PlaneState { skip: bool, - element_state: Option<(Id, CommitCounter)>, + needs_test: bool, + element_state: Option, config: Option>, } @@ -380,6 +465,7 @@ impl Default for PlaneState { fn default() -> Self { Self { skip: true, + needs_test: false, element_state: Default::default(), config: Default::default(), } @@ -390,12 +476,21 @@ impl PlaneState { fn buffer(&self) -> Option<&B> { self.config.as_ref().map(|config| &*config.buffer) } + + fn is_compatible(&self, other: &Self) -> bool { + match (self.config.as_ref(), other.config.as_ref()) { + (Some(a), Some(b)) => a.is_compatible(b), + (None, None) => true, + _ => false, + } + } } impl Clone for PlaneState { fn clone(&self) -> Self { Self { skip: self.skip, + needs_test: self.needs_test, element_state: self.element_state.clone(), config: self.config.clone(), } @@ -422,7 +517,7 @@ impl> FrameState { state .config .as_ref() - .map(|config| config.dst.overlaps(element_geometry)) + .map(|config| config.properties.dst.overlaps(element_geometry)) }) .unwrap_or(false) } @@ -435,6 +530,12 @@ impl> FrameState { self.planes.get_mut(&handle) } + fn plane_properties(&self, handle: plane::Handle) -> Option<&PlaneProperties> { + self.plane_state(handle) + .and_then(|state| state.config.as_ref()) + .map(|config| &config.properties) + } + fn plane_buffer(&self, handle: plane::Handle) -> Option<&B> { self.plane_state(handle) .and_then(|state| state.config.as_ref().map(|config| &*config.buffer)) @@ -539,6 +640,44 @@ impl FrameState { if res.is_err() { // test failed, restore previous state self.planes.insert(plane, backup); + } else { + self.planes + .iter_mut() + .for_each(|(_, state)| state.needs_test = false); + } + + res + } + + #[profiling::function] + fn test_state_complete( + &mut self, + previous_frame: &Self, + surface: &DrmSurface, + supports_fencing: bool, + allow_modeset: bool, + ) -> Result<(), DrmError> { + let is_fully_compatible = self.planes.iter().all(|(handle, state)| { + !state.needs_test + || previous_frame + .plane_state(*handle) + .map(|other| state.is_compatible(other)) + .unwrap_or(false) + }); + + if is_fully_compatible { + self.planes + .iter_mut() + .for_each(|(_, state)| state.needs_test = false); + return Ok(()); + } + + let res = surface.test_state(self.build_planes(supports_fencing), allow_modeset); + + if res.is_ok() { + self.planes + .iter_mut() + .for_each(|(_, state)| state.needs_test = false); } res @@ -551,6 +690,7 @@ impl FrameState { supports_fencing: bool, event: bool, ) -> Result<(), crate::backend::drm::error::Error> { + debug_assert!(!self.planes.iter().any(|(_, state)| state.needs_test)); surface.commit(self.build_planes(supports_fencing), event) } @@ -561,6 +701,7 @@ impl FrameState { supports_fencing: bool, event: bool, ) -> Result<(), crate::backend::drm::error::Error> { + debug_assert!(!self.planes.iter().any(|(_, state)| state.needs_test)); surface.page_flip(self.build_planes(supports_fencing), event) } @@ -586,10 +727,10 @@ impl FrameState { .map(move |(handle, state)| super::surface::PlaneState { handle: *handle, config: state.config.as_mut().map(|config| super::PlaneConfig { - src: config.src, - dst: config.dst, - alpha: config.alpha, - transform: config.transform, + 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 @@ -1270,10 +1411,8 @@ where cursor_size: Size, cursor_state: Option>, - element_states: IndexMap< - Id, - ElementFramebufferCache>::Framebuffer>>, - >, + element_states: + IndexMap>::Framebuffer>>>, debug_flags: DebugFlags, span: tracing::Span, @@ -1309,7 +1448,7 @@ where /// - `cursor_size` as reported by the drm device, used for creating buffer for the cursor plane /// - `gbm` device used for creating buffers for the cursor plane, `None` will disable the cursor plane #[allow(clippy::too_many_arguments)] - #[instrument(skip(allocator, framebuffer_exporter))] + #[instrument(skip_all)] pub fn new( output_mode_source: impl Into + Debug, surface: DrmSurface, @@ -1549,17 +1688,21 @@ where let plane_state = PlaneState { skip: false, + needs_test: true, element_state: None, config: Some(PlaneConfig { - src: Rectangle::from_loc_and_size(Point::default(), dmabuf.size()).to_f64(), - dst: Rectangle::from_loc_and_size(Point::default(), mode_size), - transform: Transform::Normal, - alpha: 1.0, - damage_clips: None, + properties: PlaneProperties { + src: Rectangle::from_loc_and_size(Point::default(), dmabuf.size()).to_f64(), + dst: Rectangle::from_loc_and_size(Point::default(), mode_size), + transform: Transform::Normal, + alpha: 1.0, + format: buffer.format(), + }, buffer: Owned::from(DrmScanoutBuffer { buffer: ScanoutBuffer::Swapchain(buffer), fb: handle, }), + damage_clips: None, plane_claim, sync: None, }), @@ -1703,18 +1846,22 @@ where })?; let primary_plane_state = PlaneState { skip: false, + needs_test: false, element_state: None, config: Some(PlaneConfig { - src: Rectangle::from_loc_and_size(Point::default(), dmabuf.size()).to_f64(), - dst: Rectangle::from_loc_and_size(Point::default(), current_size), - // NOTE: We do not apply the transform to the primary plane as this is handled by the dtr/renderer - transform: Transform::Normal, - alpha: 1.0, - damage_clips: None, + properties: PlaneProperties { + src: Rectangle::from_loc_and_size(Point::default(), dmabuf.size()).to_f64(), + dst: Rectangle::from_loc_and_size(Point::default(), current_size), + // NOTE: We do not apply the transform to the primary plane as this is handled by the dtr/renderer + transform: Transform::Normal, + alpha: 1.0, + format: primary_plane_buffer.format(), + }, buffer: Owned::from(DrmScanoutBuffer { buffer: ScanoutBuffer::Swapchain(primary_plane_buffer), fb, }), + damage_clips: None, plane_claim, sync: None, }), @@ -1722,7 +1869,7 @@ where // unconditionally set the primary plane state // if this would fail the test we are screwed anyway - next_frame_state.set_state(self.planes.primary.handle, primary_plane_state); + next_frame_state.set_state(self.planes.primary.handle, primary_plane_state.clone()); // This holds all elements that are visible on the output // A element is considered visible if it intersects with the output geometry @@ -1796,7 +1943,7 @@ where let mut cursor_plane_element: Option<&'a E> = None; let output_elements_len = output_elements.len(); - for (index, (element, element_visible_area)) in output_elements.into_iter().enumerate() { + for (index, (element, element_visible_area)) in output_elements.iter().enumerate() { let element_id = element.id(); let element_geometry = element.geometry(output_scale); let remaining_elements = output_elements_len - index; @@ -1828,7 +1975,8 @@ where match self.try_assign_element( renderer, - element, + *element, + index, &mut element_states, &primary_plane_elements, output_scale, @@ -1853,7 +2001,7 @@ where } else { render_element_states.states.insert( element_id.clone(), - RenderElementState::zero_copy(element_visible_area), + RenderElementState::zero_copy(*element_visible_area), ); } } @@ -1874,7 +2022,7 @@ where // Cleanup old state (e.g. old dmabuffers) for element_state in element_states.values_mut() { - element_state.cleanup(); + element_state.fb_cache.cleanup(); } self.element_states = element_states; @@ -1884,6 +2032,78 @@ where .map(|(state, _)| state) .unwrap_or(&self.current_frame); + // Check if the next frame state is fully compatible with the previous frame state. + // If not do a single atomic commit test and when that fails render everything that failed + // the test on the primary plane. This will also automatically correct any mistake we made + // during plane assignment and start the full test cycle on the next frame. + if next_frame_state + .test_state_complete(previous_state, &self.surface, self.supports_fencing, false) + .is_err() + { + trace!("atomic test failed for frame, resetting frame"); + + let mut removed_overlay_elements: Vec<(usize, &E)> = Vec::with_capacity( + next_frame_state + .planes + .iter() + .filter(|(_, state)| state.needs_test) + .count(), + ); + for (plane, state) in next_frame_state.planes.iter_mut() { + // We can skip everything that is known to work already + if !state.needs_test { + continue; + } + + // Check if the element we are potentially going to remove is + // on the primary plane, cursor plane or an overlay plane + let element = if *plane == self.planes.primary.handle { + primary_plane_scanout_element.take() + } else if self + .planes + .cursor + .as_ref() + .map(|p| *plane == p.handle) + .unwrap_or(false) + { + cursor_plane_element.take() + } else { + overlay_plane_elements.remove(plane) + }; + + // If we have no element on this plane skip the rest + let Some(element) = element else { + continue; + }; + + // Reset the plane config and state + state.config = None; + state.skip = false; + state.needs_test = false; + let element_z_index = state.element_state.take().map(|s| s.z_index).unwrap_or_default(); + removed_overlay_elements.push((element_z_index, element)); + // Note: This might not be completely correct if the same element is present + // multiple times and only gets removed once. But this is pretty unlikely to + // happen and will only result in reporting wrong visible area size and scan-out state + // for a single frame. + render_element_states.states.remove(element.id()); + } + + // If we removed any element from some plane we have + // to make sure we actually have a slot on the primary + // plane we can render into + if !removed_overlay_elements.is_empty() { + next_frame_state.set_state(self.planes.primary.handle, primary_plane_state); + } + + removed_overlay_elements.sort_by_key(|(z_index, _)| *z_index); + primary_plane_elements = removed_overlay_elements + .into_iter() + .map(|(_, element)| element) + .chain(primary_plane_elements.into_iter()) + .collect(); + } + // If a plane has been moved or no longer has a buffer we need to report that as damage for (handle, previous_plane_state) in previous_state.planes.iter() { if let Some(previous_config) = previous_plane_state.config.as_ref() { @@ -1897,21 +2117,25 @@ where self.overlay_plane_element_ids.remove_plane(handle); } if next_state - .map(|next_config| next_config.dst != previous_config.dst) + .map(|next_config| next_config.properties.dst != previous_config.properties.dst) .unwrap_or(true) { - output_damage.push(previous_config.dst); + output_damage.push(previous_config.properties.dst); if let Some(next_config) = next_state { trace!( "damaging move {:?}: {:?} -> {:?}", handle, - previous_config.dst, - next_config.dst + previous_config.properties.dst, + next_config.properties.dst ); - output_damage.push(next_config.dst); + output_damage.push(next_config.properties.dst); } else { - trace!("damaging removed {:?}: {:?}", handle, previous_config.dst); + trace!( + "damaging removed {:?}: {:?}", + handle, + previous_config.properties.dst + ); } } } @@ -2046,8 +2270,8 @@ where output_damage.extend(render_damage.clone()); config.damage_clips = PlaneDamageClips::from_damage( self.surface.device_fd(), - config.src, - config.dst, + config.properties.src, + config.properties.dst, render_damage, ) .ok() @@ -2353,9 +2577,10 @@ where &mut self, renderer: &mut R, element: &'a E, + element_zindex: usize, element_states: &mut IndexMap< Id, - ElementFramebufferCache>::Framebuffer>>, + ElementState>::Framebuffer>>, >, primary_plane_elements: &[&'a E], scale: Scale, @@ -2369,20 +2594,47 @@ where R: Renderer + Bind + Offscreen + ExportMem, E: RenderElement, { + // Check if we have a free plane, otherwise we can exit early + if (self.planes.overlay.is_empty() + || self + .planes + .overlay + .iter() + .all(|plane| frame_state.is_assigned(plane.handle))) + && self + .planes + .cursor + .as_ref() + .map(|plane| frame_state.is_assigned(plane.handle)) + .unwrap_or(true) + && (!try_assign_primary_plane + || frame_state + .plane_state(self.planes.primary.handle) + .map(|state| state.element_state.is_some()) + .unwrap_or(true)) + { + trace!( + "skipping direct scan-out for element {:?}, no free planes", + element.id() + ); + return Err(None); + }; + let mut rendering_reason: Option = None; if try_assign_primary_plane { match self.try_assign_primary_plane( renderer, element, + element_zindex, element_states, scale, frame_state, output_damage, output_transform, output_geometry, - )? { - Ok(plane) => { + ) { + Ok(Ok(plane)) => { trace!( "assigned element {:?} to primary {:?}", element.id(), @@ -2390,14 +2642,28 @@ where ); return Ok(plane); } - Err(err) => rendering_reason = rendering_reason.or(err), - } + Ok(Err(err)) => rendering_reason = rendering_reason.or(err), + Err(_) => {} + }; } - if let Some(plane) = self.try_assign_cursor_plane( + let mut element_config = self.element_config( renderer, element, + element_zindex, element_states, + frame_state, + scale, + output_transform, + output_geometry, + false, + ); + + if let Some(plane) = self.try_assign_cursor_plane( + renderer, + element, + element_zindex, + element_config.as_mut().ok(), scale, frame_state, output_damage, @@ -2412,16 +2678,15 @@ where return Ok(plane); } + let element_config = element_config?; + match self.try_assign_overlay_plane( - renderer, element, - element_states, + element_config, primary_plane_elements, scale, frame_state, output_damage, - output_transform, - output_geometry, )? { Ok(plane) => { trace!("assigned element {:?} to overlay plane", element.id()); @@ -2433,6 +2698,76 @@ where Err(rendering_reason) } + #[allow(clippy::too_many_arguments)] + #[instrument(level = "trace", skip_all)] + #[profiling::function] + fn try_assign_primary_plane<'a, R, E>( + &self, + renderer: &mut R, + element: &'a E, + element_zindex: usize, + element_states: &mut IndexMap< + Id, + ElementState>::Framebuffer>>, + >, + scale: Scale, + frame_state: &mut Frame, + output_damage: &mut Vec>, + output_transform: Transform, + output_geometry: Rectangle, + ) -> Result>, ExportBufferError> + where + R: Renderer, + E: RenderElement, + { + let element_config = self.element_config( + renderer, + element, + element_zindex, + element_states, + frame_state, + scale, + output_transform, + output_geometry, + true, + )?; + + let has_underlay = self + .planes + .overlay + .iter() + .filter(|plane| self.planes.primary.zpos.unwrap_or_default() > plane.zpos.unwrap_or_default()) + .any(|plane| frame_state.is_assigned(plane.handle)); + + if has_underlay { + trace!( + "failed to assign element {:?} to primary {:?}, already has underlay", + element.id(), + self.planes.primary.handle + ); + return Ok(Err(None)); + } + + if element_config.failed_planes.primary { + return Ok(Err(Some(RenderingReason::ScanoutFailed))); + } + + let res = self.try_assign_plane( + element, + &element_config, + &self.planes.primary, + scale, + frame_state, + output_damage, + ); + + if let Ok(Err(Some(RenderingReason::ScanoutFailed))) = res { + element_config.failed_planes.primary = true; + } + + res + } + #[allow(clippy::too_many_arguments)] #[instrument(level = "trace", skip_all)] #[profiling::function] @@ -2440,9 +2775,15 @@ where &mut self, renderer: &mut R, element: &E, - element_states: &mut IndexMap< - Id, - ElementFramebufferCache>::Framebuffer>>, + element_zindex: usize, + element_config: Option< + &mut ElementPlaneConfig< + '_, + DrmScanoutBuffer< + ::Buffer, + ::Buffer>>::Framebuffer, + >, + >, >, scale: Scale, frame_state: &mut Frame, @@ -2485,30 +2826,35 @@ where // if the element exposes the underlying storage we can try to do // direct scan-out - if let Some(underlying_storage) = element.underlying_storage(renderer) { - trace!( - "trying to assign element {:?} for direct scan-out on cursor {:?}", - element.id(), - plane_info.handle, - ); - if let Ok(Ok(plane)) = self.try_assign_plane( - element, - element_geometry, - &underlying_storage, - plane_info, - element_states, - scale, - frame_state, - output_damage, - output_transform, - output_geometry, - ) { + if let Some(element_config) = element_config { + if !element_config.failed_planes.cursor { trace!( - "assigned element {:?} for direct scan-out on cursor {:?}", + "trying to assign element {:?} for direct scan-out on cursor {:?}", element.id(), plane_info.handle, ); - return Some(plane); + + match self.try_assign_plane( + element, + element_config, + plane_info, + scale, + frame_state, + output_damage, + ) { + Ok(Ok(plane)) => { + trace!( + "assigned element {:?} for direct scan-out on cursor {:?}", + element.id(), + plane_info.handle, + ); + return Some(plane); + } + Ok(Err(Some(RenderingReason::ScanoutFailed))) => { + element_config.failed_planes.cursor = true; + } + _ => {} + } } } @@ -2544,8 +2890,9 @@ where .map(|s| s != scale) .unwrap_or(true) || previous_element_state - .map(|(id, commit_counter)| { - id != element.id() || !element.damage_since(scale, Some(*commit_counter)).is_empty() + .map(|element_state| { + element_state.id != *element.id() + || !element.damage_since(scale, Some(element_state.commit)).is_empty() }) .unwrap_or(true); @@ -2556,28 +2903,20 @@ where state .config .as_ref() - .map(|config| config.dst.loc != cursor_plane_location) + .map(|config| config.properties.dst.loc != cursor_plane_location) }) - .unwrap_or_default(); + .unwrap_or(true); // ok, nothing changed, try to keep the previous state if !render && !reposition { let mut plane_state = previous_state.plane_state(plane_info.handle).unwrap().clone(); plane_state.skip = true; - - let res = frame_state.test_state( - &self.surface, - self.supports_fencing, - plane_info.handle, - plane_state, - false, - ); - - if res.is_ok() { - return Some(plane_info.into()); - } else { - return None; - } + // Note: we know that we had a cusor plane in the + // previous frame and that nothing changed. In this + // case skip the whole testing + plane_state.needs_test = false; + frame_state.set_state(plane_info.handle, plane_state); + return Some(plane_info.into()); } // we no not have to re-render but update the planes location @@ -2585,21 +2924,14 @@ where trace!("repositioning cursor plane"); let mut plane_state = previous_state.plane_state(plane_info.handle).unwrap().clone(); plane_state.skip = false; + // Note: we know that we had a cusor plane in the + // previous frame, so we assume a simple location change + // does not not to be tested + plane_state.needs_test = false; let config = plane_state.config.as_mut().unwrap(); - config.dst.loc = cursor_plane_location; - let res = frame_state.test_state( - &self.surface, - self.supports_fencing, - plane_info.handle, - plane_state, - false, - ); - - if res.is_ok() { - return Some(plane_info.into()); - } else { - return None; - } + config.properties.dst.loc = cursor_plane_location; + frame_state.set_state(plane_info.handle, plane_state); + return Some(plane_info.into()); } trace!( @@ -2730,35 +3062,73 @@ where let src = Rectangle::from_loc_and_size(Point::default(), cursor_buffer_size).to_f64(); let dst = Rectangle::from_loc_and_size(cursor_plane_location, self.cursor_size); - let config = Some(PlaneConfig { - src, - dst, - alpha: 1.0, - transform: Transform::Normal, - damage_clips: None, + let config = PlaneConfig { + properties: PlaneProperties { + src, + dst, + alpha: 1.0, + transform: Transform::Normal, + format: framebuffer.format(), + }, buffer: Owned::from(DrmScanoutBuffer { buffer: ScanoutBuffer::Cursor(cursor_buffer), fb: OwnedFramebuffer::new(DrmFramebuffer::Gbm(framebuffer)), }), + damage_clips: None, plane_claim, sync: None, - }); + }; + let is_compatible = previous_state + .plane_state(plane_info.handle) + .map(|state| { + state + .config + .as_ref() + .map(|other| { + // Note: We do not use the plane config `is_compatible` test + // here as we exclude the destination location from the test + other.properties.src == config.properties.src + && other.properties.dst.size == config.properties.dst.size + && other.properties.alpha == config.properties.alpha + && other.properties.transform == config.properties.transform + && other.properties.format == config.properties.format + }) + .unwrap_or(false) + }) + .unwrap_or(false); let plane_state = PlaneState { skip: false, - element_state: Some((element.id().clone(), element.current_commit())), - config, + // Note: we assume we only have to test if the plane is + // not compatible. This should only happen if we either + // had no cursor plane before or we did direct scan-out + // on it. A simple re-position without re-render is + // already handled earlier. + needs_test: !is_compatible, + element_state: Some(PlaneElementState { + id: element.id().clone(), + commit: element.current_commit(), + z_index: element_zindex, + }), + config: Some(config), }; - let res = frame_state.test_state( - &self.surface, - self.supports_fencing, - plane_info.handle, - plane_state, - false, - ); + let res = if is_compatible { + frame_state.set_state(plane_info.handle, plane_state); + true + } else { + frame_state + .test_state( + &self.surface, + self.supports_fencing, + plane_info.handle, + plane_state, + false, + ) + .is_ok() + }; - if res.is_ok() { + if res { cursor_state.previous_output_scale = Some(scale); cursor_state.previous_output_transform = Some(output_transform); output_damage.push(dst); @@ -2772,20 +3142,260 @@ where #[allow(clippy::too_many_arguments)] #[instrument(level = "trace", skip_all)] #[profiling::function] - fn try_assign_overlay_plane<'a, R, E>( + fn element_config<'a, R, E>( &self, renderer: &mut R, - element: &'a E, - element_states: &mut IndexMap< + element: &E, + element_zindex: usize, + element_states: &'a mut IndexMap< Id, - ElementFramebufferCache>::Framebuffer>>, + ElementState>::Framebuffer>>, + >, + frame_state: &mut Frame, + scale: Scale, + output_transform: Transform, + output_geometry: Rectangle, + allow_opaque_fallback: bool, + ) -> Result< + ElementPlaneConfig< + 'a, + DrmScanoutBuffer< + ::Buffer, + ::Buffer>>::Framebuffer, + >, + >, + ExportBufferError, + > + where + R: Renderer, + E: RenderElement, + { + let element_id = element.id(); + let element_geometry = element.geometry(scale); + + // We can only try to do direct scan-out for element that provide a underlying storage + let underlying_storage = element + .underlying_storage(renderer) + .ok_or(ExportBufferError::NoUnderlyingStorage)?; + + // First we try to find a state in our new states, this is important if + // we got the same id multiple times. If we can't find it we use the previous + // state if available + if !element_states.contains_key(element_id) { + let previous_fb_cache = self + .element_states + .get(element_id) + .map(|state| state.fb_cache.clone()) + .unwrap_or_default(); + element_states.insert( + element_id.clone(), + ElementState { + instances: Vec::with_capacity(1), + fb_cache: previous_fb_cache, + }, + ); + } + let element_fb_cache = element_states + .get_mut(element_id) + .map(|state| &mut state.fb_cache) + .unwrap(); + + let cached_fb = element_fb_cache.get(&underlying_storage, allow_opaque_fallback); + + if cached_fb.is_none() { + trace!( + "no cached fb, exporting new fb for element {:?} underlying storage {:?}", + element_id, + &underlying_storage + ); + + let fb = self + .framebuffer_exporter + .add_framebuffer( + self.surface.device_fd(), + ExportBuffer::from(&underlying_storage), + allow_opaque_fallback, + ) + .map_err(|err| { + trace!("failed to add framebuffer: {:?}", err); + ExportBufferError::ExportFailed + }) + .and_then(|fb| { + fb.map(|fb| OwnedFramebuffer::new(DrmFramebuffer::Exporter(fb))) + .ok_or(ExportBufferError::Unsupported) + }); + + if fb.is_err() { + trace!( + "could not import framebuffer for element {:?} underlying storage {:?}", + element_id, + &underlying_storage + ); + } + + element_fb_cache.insert(&underlying_storage, fb, allow_opaque_fallback); + } else { + trace!( + "using cached fb for element {:?} underlying storage {:?}", + element_id, + &underlying_storage + ); + } + + let fb = element_fb_cache + .get(&underlying_storage, allow_opaque_fallback) + .unwrap()?; + + let transform = apply_output_transform( + apply_underlying_storage_transform(element.transform(), &underlying_storage), + output_transform, + ); + + let src = element.src(); + let dst = output_transform.transform_rect_in(element_geometry, &output_geometry.size); + let alpha = element.alpha(); + let properties = PlaneProperties { + src, + dst, + alpha, + transform, + format: fb.format(), + }; + + if !element_states + .get(element_id) + .unwrap() + .instances + .iter() + .any(|i| i.properties == properties) + { + let overlay_bitmask = + self.planes + .overlay + .iter() + .enumerate() + .fold(0u32, |mut acc, (index, plane)| { + if frame_state.is_assigned(plane.handle) { + acc |= 1 << index; + } + acc + }); + let current_plane_snapshot = PlanesSnapshot { + primary: frame_state.is_assigned(self.planes.primary.handle), + cursor: self + .planes + .cursor + .as_ref() + .map(|plane| frame_state.is_assigned(plane.handle)) + .unwrap_or(false), + overlay_bitmask, + }; + + let element_state = element_states.get_mut(element_id).unwrap(); + element_state.instances.push(ElementInstanceState { + properties, + active_planes: current_plane_snapshot, + failed_planes: Default::default(), + }); + + if let Some(previous_state) = self.element_states.get(element_id) { + // lets look if we find a previous instance with exactly the same properties. + // if we find one we can test if nothing changed and re-use the failed tests + let matching_instance = previous_state + .instances + .iter() + .find(|i| i.properties == properties); + + if let Some(matching_instance) = matching_instance { + if current_plane_snapshot == matching_instance.active_planes { + let previous_frame_state = self + .pending_frame + .as_ref() + .map(|(state, _)| state) + .unwrap_or(&self.current_frame); + + // Note: we ignore the cursor plane here as this would result + // in constant re-tests of cursor moves and we do not expect + // that to influence the test state of our elements. + // Adding or removing cursor can influence the other planes, but + // is already covered in the active planes check. + let primary_plane_changed = current_plane_snapshot + .primary + .then(|| { + frame_state.plane_properties(self.planes.primary.handle) + != previous_frame_state.plane_properties(self.planes.primary.handle) + }) + .unwrap_or(false); + + let overlay_plane_changed = + self.planes.overlay.iter().enumerate().any(|(index, plane)| { + // we only want to test planes that are currently in use + if current_plane_snapshot.overlay_bitmask & (1 << index) == 0 { + return false; + } + + frame_state.plane_properties(plane.handle) + != previous_frame_state.plane_properties(plane.handle) + }); + + if !(primary_plane_changed || overlay_plane_changed) { + // we now know that nothing changed and we can assume any previouly failed + // test will again fail + let instance_state = element_state + .instances + .iter_mut() + .find(|i| i.properties == properties) + .unwrap(); + instance_state.failed_planes = matching_instance.failed_planes; + } + } + } + } + } + + let failed_planes = element_states + .get_mut(element_id) + .unwrap() + .instances + .iter_mut() + .find_map(|i| { + if i.properties == properties { + Some(&mut i.failed_planes) + } else { + None + } + }) + .unwrap(); + + Ok(ElementPlaneConfig { + properties, + z_index: element_zindex, + geometry: element_geometry, + buffer: Owned::from(DrmScanoutBuffer { + fb, + buffer: ScanoutBuffer::from(underlying_storage.clone()), + }), + failed_planes, + }) + } + + #[allow(clippy::too_many_arguments)] + #[instrument(level = "trace", skip_all)] + #[profiling::function] + fn try_assign_overlay_plane<'a, R, E>( + &self, + element: &'a E, + element_config: ElementPlaneConfig< + '_, + DrmScanoutBuffer< + ::Buffer, + ::Buffer>>::Framebuffer, + >, >, primary_plane_elements: &[&'a E], scale: Scale, frame_state: &mut Frame, output_damage: &mut Vec>, - output_transform: Transform, - output_geometry: Rectangle, ) -> Result>, ExportBufferError> where R: Renderer, @@ -2808,21 +3418,54 @@ where return Ok(Err(None)); } - // We can only try to do direct scan-out for element that provide a underlying storage - let underlying_storage = element - .underlying_storage(renderer) - .ok_or(ExportBufferError::NoUnderlyingStorage)?; - - let element_geometry = element.geometry(scale); - let overlaps_with_primary_plane_element = primary_plane_elements.iter().any(|e| { let other_geometry = e.geometry(scale); - other_geometry.overlaps(element_geometry) + other_geometry.overlaps(element_config.geometry) }); - let mut rendering_reason: Option = None; + let primary_plane_has_alpha = frame_state + .plane_buffer(self.planes.primary.handle) + .map(|state| has_alpha(state.format().code)) + .unwrap_or(false); + + let previous_frame_state = self + .pending_frame + .as_ref() + .map(|(state, _)| state) + .unwrap_or(&self.current_frame); + + // We consider a plane compatible if the z-index of the previous assigned + // element is or equal to our z-index and the properties (src/dst/format/...) + // are equal. The reason for the z-index limitation is that we do not want + // to assign ourself to the same plane if our z-index changed. That could + // result in assigning the element on a lower plane as necessary and then + // blocking direct scan-out for some other element + let is_plane_compatible = |plane: &&PlaneInfo| { + previous_frame_state + .plane_state(plane.handle) + .map(|state| { + state + .element_state + .as_ref() + .map(|state| state.z_index <= element_config.z_index) + .unwrap_or(false) + && state + .config + .as_ref() + .map(|config| config.properties.is_compatible(&element_config.properties)) + .unwrap_or(false) + }) + .unwrap_or(false) + }; - for plane in self.planes.overlay.iter() { + let mut test_overlay_plane = |plane: &PlaneInfo, + element_config: &ElementPlaneConfig< + '_, + DrmScanoutBuffer< + ::Buffer, + ::Buffer>>::Framebuffer, + >, + >| { // something is already assigned to our overlay plane if frame_state.is_assigned(plane.handle) { trace!( @@ -2831,20 +3474,20 @@ where plane.zpos, element_id, ); - continue; + return Ok(Err(None)); } // test if the plane represents an underlay let is_underlay = self.planes.primary.zpos.unwrap_or_default() > plane.zpos.unwrap_or_default(); - if is_underlay && (!element_is_opaque(element, scale) || self.primary_is_opaque) { + if is_underlay && !(element_is_opaque(element, scale) && primary_plane_has_alpha) { trace!( "skipping direct scan-out on underlay {:?} with zpos {:?}, element {:?} is not opaque or primary plane has no alpha channel", plane.handle, plane.zpos, element_id ); - continue; + return Ok(Err(None)); } // if the element overlaps with an element on @@ -2865,86 +3508,64 @@ where info.handle != plane.handle && info.zpos.unwrap_or_default() <= plane.zpos.unwrap_or_default() }) - .any(|overlapping_plane| frame_state.overlaps(overlapping_plane.handle, element_geometry)); + .any(|overlapping_plane| { + frame_state.overlaps(overlapping_plane.handle, element_config.geometry) + }); // if we overlap we a plane below which already // has an element assigned we can not use the // plane for direct scan-out if overlaps_with_plane_underneath { trace!( - "skipping direct scan-out on {:?} with zpos {:?}, element {:?} geometry {:?} overlaps with plane underneath", plane.handle, plane.zpos, element_id, element_geometry, + "skipping direct scan-out on {:?} with zpos {:?}, element {:?} geometry {:?} overlaps with plane underneath", plane.handle, plane.zpos, element_id, element_config.geometry, ); - continue; + return Ok(Err(None)); } - match self.try_assign_plane( - element, - element_geometry, - &underlying_storage, - plane, - element_states, - scale, - frame_state, - output_damage, - output_transform, - output_geometry, - )? { - Ok(plane) => return Ok(Ok(plane)), - Err(err) => rendering_reason = rendering_reason.or(err), + self.try_assign_plane(element, element_config, plane, scale, frame_state, output_damage) + }; + + // First try to assign the element to a compatible plane, this can save us + // from some atomic testing + for plane in self.planes.overlay.iter().filter(is_plane_compatible) { + if let Ok(plane_assignment) = test_overlay_plane(plane, &element_config)? { + trace!( + "assigned element {:?} geometry {:?} to compatible {:?} with zpos {:?}", + element_id, + element_config.geometry, + plane.handle, + plane.zpos, + ); + return Ok(Ok(plane_assignment)); } } - Ok(Err(rendering_reason)) - } - - #[allow(clippy::too_many_arguments)] - #[instrument(level = "trace", skip_all)] - #[profiling::function] - fn try_assign_primary_plane( - &self, - renderer: &mut R, - element: &E, - element_states: &mut IndexMap< - Id, - ElementFramebufferCache>::Framebuffer>>, - >, - scale: Scale, - frame_state: &mut Frame, - output_damage: &mut Vec>, - output_transform: Transform, - output_geometry: Rectangle, - ) -> Result>, ExportBufferError> - where - R: Renderer, - E: RenderElement, - { - // We can only try to do direct scan-out for element that provide a underlying storage - let underlying_storage = element - .underlying_storage(renderer) - .ok_or(ExportBufferError::NoUnderlyingStorage)?; - - // TODO: We should check if there is already an element assigned to the primary plane for completeness here + // If we found no compatible plane fall back to walk all available planes + let mut rendering_reason: Option = None; + for (index, plane) in self.planes.overlay.iter().enumerate() { + // if the tested element state already tells us that this failed skip the test + if element_config.failed_planes.overlay_bitmask & (1 << index) != 0 { + 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)); + continue; + } - trace!( - "trying to assign element {:?} to primary {:?}", - element.id(), - self.planes.primary.handle - ); + match test_overlay_plane(plane, &element_config)? { + Ok(plane) => return Ok(Ok(plane)), + Err(err) => { + // if the test failed save that in the tested element state + if let Some(RenderingReason::ScanoutFailed) = err { + element_config.failed_planes.overlay_bitmask |= 1 << index; + } - let element_geometry = element.geometry(scale); + rendering_reason = rendering_reason.or(err) + } + } + } - self.try_assign_plane( - element, - element_geometry, - &underlying_storage, - &self.planes.primary, - element_states, - scale, - frame_state, - output_damage, - output_transform, - output_geometry, - ) + Ok(Err(rendering_reason)) } #[allow(clippy::too_many_arguments)] @@ -2953,18 +3574,17 @@ where fn try_assign_plane( &self, element: &E, - element_geometry: Rectangle, - underlying_storage: &UnderlyingStorage, - plane: &PlaneInfo, - element_states: &mut IndexMap< - Id, - ElementFramebufferCache>::Framebuffer>>, + element_config: &ElementPlaneConfig< + '_, + DrmScanoutBuffer< + ::Buffer, + ::Buffer>>::Framebuffer, + >, >, + plane: &PlaneInfo, scale: Scale, frame_state: &mut Frame, output_damage: &mut Vec>, - output_transform: Transform, - output_geometry: Rectangle, ) -> Result>, ExportBufferError> where R: Renderer, @@ -2972,66 +3592,6 @@ where { let element_id = element.id(); - // First we try to find a state in our new states, this is important if - // we got the same id multiple times. If we can't find it we use the previous - // state if available - let mut element_state = element_states - .get(element_id) - .or_else(|| self.element_states.get(element_id)) - .cloned() - .unwrap_or_default(); - - element_state.cleanup(); - - let cached_fb = element_state.get(underlying_storage); - - if cached_fb.is_none() { - trace!( - "no cached fb, exporting new fb for element {:?} underlying storage {:?}", - element_id, - &underlying_storage - ); - - let fb = self - .framebuffer_exporter - .add_framebuffer( - self.surface.device_fd(), - ExportBuffer::from(underlying_storage), - if plane.type_ == PlaneType::Primary { - self.primary_is_opaque - } else { - false // TODO: Check if element_is_opaque and allow fallback for underlays - }, - ) - .map_err(|err| { - trace!("failed to add framebuffer: {:?}", err); - ExportBufferError::ExportFailed - }) - .and_then(|fb| { - fb.map(|fb| OwnedFramebuffer::new(DrmFramebuffer::Exporter(fb))) - .ok_or(ExportBufferError::Unsupported) - }); - - if fb.is_err() { - trace!( - "could not import framebuffer for element {:?} underlying storage {:?}", - element_id, - &underlying_storage - ); - } - - element_state.insert(underlying_storage, fb); - } else { - trace!( - "using cached fb for element {:?} underlying storage {:?}", - element_id, - &underlying_storage - ); - } - - element_states.insert(element_id.clone(), element_state.clone()); - let fb = element_state.get(underlying_storage).unwrap()?; - let plane_claim = match self.surface.claim_plane(plane.handle) { Some(claim) => claim, None => { @@ -3041,24 +3601,19 @@ where }; // Try to assign the element to a plane - trace!("testing direct scan-out for element {:?} on {:?} with zpos {:?}: fb: {:?}, underlying storage: {:?}, element_geometry: {:?}", element_id, plane.handle, plane.zpos, &fb, &underlying_storage, element_geometry); + 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(&fb.format()) { + if !plane.formats.contains(&element_config.properties.format) { trace!( - "skipping direct scan-out on plane {:?} with zpos {:?} for element {:?}, format {:?} not supported", + "skipping direct scan-out on {:?} with zpos {:?} for element {:?}, format {:?} not supported", plane.handle, plane.zpos, element_id, - fb.format(), + element_config.properties.format, ); return Ok(Err(Some(RenderingReason::FormatUnsupported))); } - let transform = apply_output_transform( - apply_underlying_storage_transform(element.transform(), underlying_storage), - output_transform, - ); - let previous_state = self .pending_frame .as_ref() @@ -3066,22 +3621,48 @@ where .unwrap_or(&self.current_frame); let previous_commit = previous_state.planes.get(&plane.handle).and_then(|state| { - state.element_state.as_ref().and_then( - |(id, counter)| { - if id == element_id { - Some(*counter) - } else { - None - } - }, - ) + state.element_state.as_ref().and_then(|state| { + if state.id == *element_id { + Some(state.commit) + } else { + None + } + }) }); let element_damage = element.damage_since(scale, previous_commit); - let src = element.src(); - let dst = output_transform.transform_rect_in(element_geometry, &output_geometry.size); - let alpha = element.alpha(); + let damage_clips = if element_damage.is_empty() { + None + } else { + PlaneDamageClips::from_damage( + self.surface.device_fd(), + element_config.properties.src, + element_config.geometry, + element_damage.clone(), + ) + .ok() + .flatten() + }; + + let config = PlaneConfig { + properties: element_config.properties, + buffer: element_config.buffer.clone(), + damage_clips, + plane_claim, + sync: None, + }; + + let is_compatible = previous_state + .plane_state(plane.handle) + .map(|state| { + state + .config + .as_ref() + .map(|c| c.is_compatible(&config)) + .unwrap_or(false) + }) + .unwrap_or(false); // We can only skip the plane update if we have no damage and if // the src/dst/alpha properties are unchanged. Also we can not skip if @@ -3094,61 +3675,46 @@ where state .config .as_ref() - .map(|config| { - config.src == src - && config.dst == dst - && config.transform == transform - && config.buffer.fb == fb - && config.alpha == alpha - }) + .map(|c| is_compatible && c.buffer.fb == config.buffer.fb) .unwrap_or(false) }) .unwrap_or(false); let element_output_damage = element_damage - .iter() - .cloned() + .into_iter() .map(|mut rect| { - rect.loc += element_geometry.loc; + rect.loc += element_config.geometry.loc; rect }) .collect::>(); - let damage_clips = if element_damage.is_empty() { - None - } else { - PlaneDamageClips::from_damage(self.surface.device_fd(), src, element_geometry, element_damage) - .ok() - .flatten() - }; - let plane_state = PlaneState { skip, - element_state: Some((element_id.clone(), element.current_commit())), - config: Some(PlaneConfig { - src, - dst, - alpha, - transform, - damage_clips, - buffer: Owned::from(DrmScanoutBuffer { - fb, - buffer: ScanoutBuffer::from(underlying_storage.clone()), - }), - plane_claim, - sync: None, + needs_test: true, + element_state: Some(PlaneElementState { + id: element_id.clone(), + commit: element.current_commit(), + z_index: element_config.z_index, }), + config: Some(config), }; - let res = frame_state.test_state( - &self.surface, - self.supports_fencing, - plane.handle, - plane_state, - false, - ); + let res = if is_compatible { + frame_state.set_state(plane.handle, plane_state); + true + } else { + frame_state + .test_state( + &self.surface, + self.supports_fencing, + plane.handle, + plane_state, + false, + ) + .is_ok() + }; - if res.is_ok() { + if res { output_damage.extend(element_output_damage); trace!(