diff --git a/compositor/src/handlers/compositor.rs b/compositor/src/handlers/compositor.rs index fe89373..861ba01 100644 --- a/compositor/src/handlers/compositor.rs +++ b/compositor/src/handlers/compositor.rs @@ -14,29 +14,6 @@ use super::{layer_shell, xdg_shell}; use crate::Ferese; use crate::state::ClientState; -fn layer_affects_backdrop(layer: smithay::wayland::shell::wlr_layer::Layer) -> bool { - matches!( - layer, - smithay::wayland::shell::wlr_layer::Layer::Background | smithay::wayland::shell::wlr_layer::Layer::Bottom - ) -} - -impl Ferese { - fn surface_affects_backdrop(&self, surface: &WlSurface) -> bool { - let mut root = surface.clone(); - while let Some(parent) = get_parent(&root) { - root = parent; - } - if matches!(&self.cursor_status, smithay::input::pointer::CursorImageStatus::Surface(cursor) if cursor == &root) - { - return false; - } - self.space - .layer_for_surface(&root, smithay::desktop::WindowSurfaceType::ALL) - .is_none_or(|layer| layer_affects_backdrop(layer.cached_state().layer)) - } -} - impl CompositorHandler for Ferese { fn compositor_state(&mut self) -> &mut CompositorState { &mut self.compositor_state @@ -60,9 +37,6 @@ impl CompositorHandler for Ferese { fn commit(&mut self, surface: &WlSurface) { self.update_surface_preferences(surface); self.capture_resize_before_commit(surface); - if self.surface_affects_backdrop(surface) { - self.backdrop_generation = self.backdrop_generation.wrapping_add(1); - } on_commit_buffer_handler::(surface); if !is_sync_subsurface(surface) { let mut root = surface.clone(); @@ -109,7 +83,7 @@ impl CompositorHandler for Ferese { .retain(|_, lock| lock.wl_surface() != surface); self.focus_lock_surface(); } - self.backdrop_generation = self.backdrop_generation.wrapping_add(1); + crate::backends::direct::render_surface(self, surface); if self.idle_inhibitors.remove(surface).is_some() { self.refresh_idle_inhibition(); @@ -142,17 +116,3 @@ impl ShmHandler for Ferese { &self.shm_state } } - -#[cfg(test)] -mod tests { - use smithay::wayland::shell::wlr_layer::Layer; - - use super::*; - #[test] - fn panel_commits_do_not_invalidate_their_own_backdrop() { - assert!(!layer_affects_backdrop(Layer::Top)); - assert!(!layer_affects_backdrop(Layer::Overlay)); - assert!(layer_affects_backdrop(Layer::Bottom)); - assert!(layer_affects_backdrop(Layer::Background)); - } -} diff --git a/compositor/src/main.rs b/compositor/src/main.rs index 6c62927..04cde41 100644 --- a/compositor/src/main.rs +++ b/compositor/src/main.rs @@ -115,9 +115,6 @@ fn main() -> Result<(), Box> { // The decoder wakes the loop after publishing its result, including // when no output has a pending frame. let wallpaper_changed = state.wallpaper.poll(); - if wallpaper_changed { - state.backdrop_generation = state.backdrop_generation.wrapping_add(1); - } // All input/Wayland callbacks have returned, releasing seat locks. if state.focus_cycle.is_some() && (state.session_lock.active || state.input_capture.captures(1)) { state.cancel_focus_cycle(); diff --git a/compositor/src/render/backdrop.rs b/compositor/src/render/backdrop.rs new file mode 100644 index 0000000..7c5a61a --- /dev/null +++ b/compositor/src/render/backdrop.rs @@ -0,0 +1,228 @@ +use super::*; + +#[derive(Debug, Default)] +pub(super) struct Backdrop { + pub commit: CommitCounter, + sources: Vec, + region: Option>, +} + +#[derive(Debug)] +struct Source { + id: Id, + commit: CommitCounter, + geometry: Rectangle, + alpha: f32, + src: Rectangle, + transform: Transform, + visible: Vec>, +} + +impl Backdrop { + fn update( + &mut self, + elements: &[E], + region: Rectangle, + scale: RenderScale, + ) -> bool { + let mut uncovered = vec![region]; + let mut sources = Vec::new(); + let mut changed = self.region != Some(region); + + for element in elements { + let geometry = element.geometry(scale); + let visible = uncovered + .iter() + .filter_map(|rect| rect.intersection(geometry)) + .collect::>(); + if visible.is_empty() || element.alpha() == 0.0 { + continue; + } + + let previous = self.sources.iter().find(|source| &source.id == element.id()); + changed |= previous.is_none_or(|source| { + source.geometry != geometry + || source.alpha != element.alpha() + || source.src != element.src() + || source.transform != element.transform() + || source.visible != visible + }); + if let Some(previous) = previous { + changed |= element.damage_since(scale, Some(previous.commit)).iter().any(|damage| { + let damage = Rectangle::new(damage.loc + geometry.loc, damage.size); + visible.iter().any(|rect| rect.overlaps(damage)) + }); + } + + sources.push(Source { + id: element.id().clone(), + commit: element.current_commit(), + geometry, + alpha: element.alpha(), + src: element.src(), + transform: element.transform(), + visible, + }); + if element.alpha() == 1.0 { + for opaque in element.opaque_regions(scale).iter() { + let opaque = Rectangle::new(opaque.loc + geometry.loc, opaque.size); + uncovered = uncovered + .into_iter() + .flat_map(|rect| rect.subtract_rect(opaque)) + .collect(); + } + } + } + + // Membership and stacking changes matter even without buffer damage. + changed |= !self + .sources + .iter() + .map(|source| &source.id) + .eq(sources.iter().map(|source| &source.id)); + self.sources = sources; + self.region = Some(region); + if changed { + self.commit.increment(); + } + + changed + } +} + +pub(super) fn update(elements: &[AnimatedWindowRenderElement], scale: f64) { + // Resolve lower blurs first so their damage propagates to higher materials. + for (index, element) in elements.iter().enumerate().rev() { + if let AnimatedWindowRenderElement::Blur(blur) = element { + let changed = + blur.backdrop + .lock() + .unwrap() + .update(&elements[index + 1..], blur.geometry(scale.into()), scale.into()); + if changed { + blur.capture_dirty.store(true, Ordering::Relaxed); + } + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + struct TestElement { + id: Id, + commit: CommitCounter, + rect: Rectangle, + damage: Vec>, + opaque: bool, + alpha: f32, + } + + impl TestElement { + fn new(rect: Rectangle) -> Self { + Self { + id: Id::new(), + commit: CommitCounter::default(), + rect, + damage: vec![], + opaque: false, + alpha: 1.0, + } + } + + fn repaint(&mut self, damage: Vec>) { + self.commit.increment(); + self.damage = damage; + } + } + + impl Element for TestElement { + fn id(&self) -> &Id { + &self.id + } + fn current_commit(&self) -> CommitCounter { + self.commit + } + fn src(&self) -> Rectangle { + Rectangle::from_size((100.0, 100.0).into()) + } + fn geometry(&self, _: RenderScale) -> Rectangle { + self.rect + } + fn damage_since(&self, _: RenderScale, commit: Option) -> DamageSet { + if commit == Some(self.commit) { + DamageSet::default() + } else { + DamageSet::from_slice(&self.damage) + } + } + fn opaque_regions(&self, _: RenderScale) -> OpaqueRegions { + if self.opaque { + OpaqueRegions::from_slice(&[Rectangle::from_size(self.rect.size)]) + } else { + OpaqueRegions::default() + } + } + fn alpha(&self) -> f32 { + self.alpha + } + } + + fn rect(x: i32, y: i32, w: i32, h: i32) -> Rectangle { + Rectangle::new((x, y).into(), (w, h).into()) + } + + #[test] + fn only_damage_intersecting_the_sample_invalidates_its_output() { + let mut first = Backdrop::default(); + let mut second = Backdrop::default(); + let region = rect(20, 20, 30, 30); + let mut a = TestElement::new(rect(0, 0, 100, 100)); + let b = TestElement::new(rect(0, 0, 100, 100)); + assert!(first.update(&[&a], region, 1.0.into())); + assert!(second.update(&[&b], region, 1.0.into())); + + a.repaint(vec![]); + assert!(!first.update(&[&a], region, 1.0.into())); + a.repaint(vec![rect(60, 60, 10, 10)]); + assert!(!first.update(&[&a], region, 1.0.into())); + a.repaint(vec![rect(25, 25, 1, 1)]); + assert!(first.update(&[&a], region, 1.0.into())); + assert!(!second.update(&[&b], region, 1.0.into())); + assert!(!first.update(&[&a], region, 1.0.into())); + } + + #[test] + fn opaque_occlusion_hides_damage_but_fading_and_moving_reveal_it() { + let mut backdrop = Backdrop::default(); + let region = rect(0, 0, 100, 100); + let mut front = TestElement::new(rect(0, 0, 50, 100)); + front.opaque = true; + let mut back = TestElement::new(region); + assert!(backdrop.update(&[&front, &back], region, 1.0.into())); + + back.repaint(vec![rect(10, 10, 10, 10)]); + assert!(!backdrop.update(&[&front, &back], region, 1.0.into())); + front.alpha = 0.5; + assert!(backdrop.update(&[&front, &back], region, 1.0.into())); + back.repaint(vec![rect(10, 10, 10, 10)]); + assert!(backdrop.update(&[&front, &back], region, 1.0.into())); + front.rect.loc.x = 20; + assert!(backdrop.update(&[&front, &back], region, 1.0.into())); + assert!(!backdrop.update(&[&front, &back], region, 1.0.into())); + } + + #[test] + fn membership_and_stacking_changes_invalidate_without_buffer_damage() { + let mut backdrop = Backdrop::default(); + let region = rect(0, 0, 100, 100); + let a = TestElement::new(region); + let b = TestElement::new(region); + assert!(backdrop.update(&[&a, &b], region, 1.0.into())); + assert!(backdrop.update(&[&b, &a], region, 1.0.into())); + assert!(backdrop.update(&[&a], region, 1.0.into())); + assert!(backdrop.update(&[&a, &b], region, 1.0.into())); + assert!(!backdrop.update(&[&a, &b], region, 1.0.into())); + } +} diff --git a/compositor/src/render/materials.rs b/compositor/src/render/materials.rs index 91540a0..b18d60f 100644 --- a/compositor/src/render/materials.rs +++ b/compositor/src/render/materials.rs @@ -46,7 +46,8 @@ pub(super) fn append_material_surface( }) .collect(); if let Some(buffers) = state.render.surfaces.get_mut(surface) { - buffers.contexts.retain(|(_, index), _| *index < targets.len()); + buffers.contexts.retain(|(_, _, index), _| *index < targets.len()); + buffers.captures.retain(|(_, _, index), _| *index < targets.len()); } let material = (!materials.is_empty() && regions.is_none()).then_some(()); let clip = material.as_ref().and_then(|_| { @@ -181,7 +182,6 @@ pub(super) fn material_element( presentation_alpha, background_opacity, blur: (blur * scale) as f32, - scene_generation: if blur > 0.0 { state.backdrop_generation } else { 0 }, sample_geometry, sample_physical, sample_framebuffer: framebuffer_clip_rect(sample_physical, mode.size, transform), @@ -202,11 +202,13 @@ pub(super) fn material_element( .flatten(); let capture_rect = framebuffer_capture_rect(parameters.sample_framebuffer); let capture_size = Size::from((capture_rect[2], capture_rect[3])); + let output_id = state.output_id(output)?; + let capture_key = (context.clone(), output_id, index); let buffers = state.render.surfaces.entry(surface.clone()).or_default(); let capture = if let Some(blur_program) = blur_program { if buffers .captures - .get(&context) + .get(&capture_key) .is_none_or(|c| c.geometry != sample_geometry || c.texture.size() != capture_size) { let texture = Offscreen::::create_buffer(renderer, Fourcc::Abgr8888, capture_size) @@ -217,7 +219,7 @@ pub(super) fn material_element( .ok(); if let Some(texture) = texture { buffers.captures.insert( - context.clone(), + capture_key.clone(), BlurCapture { texture, dirty: Arc::new(AtomicBool::new(true)), @@ -225,16 +227,16 @@ pub(super) fn material_element( }, ); } else { - buffers.captures.remove(&context); + buffers.captures.remove(&capture_key); } } buffers .captures - .get(&context) + .get(&capture_key) .cloned() .map(|capture| (capture, blur_program)) } else { - buffers.captures.remove(&context); + buffers.captures.remove(&capture_key); None }; if capture.is_some() { @@ -261,7 +263,7 @@ pub(super) fn material_element( }; let cached = buffers .contexts - .entry((context, index)) + .entry((context, output_id, index)) .or_insert_with(|| CachedMaterial { element: make_element(), parameters: parameters.clone(), @@ -284,8 +286,15 @@ pub(super) fn material_element( cached.element = make_element(); } if cached.parameters != parameters { - cached.shadow.resize(parameters.shadow_bounds, None); - cached.shadow.update_uniforms(decoration_uniforms(¶meters, 2.0)); + if cached.parameters.shadow_rect != parameters.shadow_rect + || cached.parameters.shadow_values != parameters.shadow_values + || cached.parameters.shadow_bounds != parameters.shadow_bounds + || cached.parameters.radius != parameters.radius + || cached.parameters.visible_framebuffer != parameters.visible_framebuffer + { + cached.shadow.resize(parameters.shadow_bounds, None); + cached.shadow.update_uniforms(decoration_uniforms(¶meters, 2.0)); + } match &mut cached.element { MaterialElement::Fill(element) => { element.resize( diff --git a/compositor/src/render/mod.rs b/compositor/src/render/mod.rs index 2d0b53e..ac3d47a 100644 --- a/compositor/src/render/mod.rs +++ b/compositor/src/render/mod.rs @@ -1,3 +1,4 @@ +mod backdrop; mod capture; mod decorations; mod materials; @@ -130,7 +131,7 @@ struct BlurRenderElement { texture: GlesTexture, program: GlesTexProgram, id: Id, - commit: CommitCounter, + backdrop: Arc>, geometry: Rectangle, capture_rect: [i32; 4], uniforms: Arc<[Uniform<'static>]>, @@ -148,7 +149,7 @@ impl BlurRenderElement { texture, program, id: Id::new(), - commit: CommitCounter::default(), + backdrop: Arc::default(), geometry: parameters.sample_geometry, capture_rect: framebuffer_capture_rect(parameters.sample_framebuffer), uniforms: blur_uniforms(parameters).into(), @@ -156,11 +157,13 @@ impl BlurRenderElement { } fn update(&mut self, parameters: &MaterialParameters) { - self.capture_dirty.store(true, Ordering::Relaxed); + if self.capture_rect != framebuffer_capture_rect(parameters.sample_framebuffer) { + self.capture_dirty.store(true, Ordering::Relaxed); + } self.geometry = parameters.sample_geometry; self.capture_rect = framebuffer_capture_rect(parameters.sample_framebuffer); self.uniforms = blur_uniforms(parameters).into(); - self.commit.increment(); + self.backdrop.lock().unwrap().commit.increment(); } } @@ -170,7 +173,7 @@ impl Element for BlurRenderElement { } fn current_commit(&self) -> CommitCounter { - self.commit + self.backdrop.lock().unwrap().commit } fn src(&self) -> Rectangle { @@ -182,7 +185,7 @@ impl Element for BlurRenderElement { } fn damage_since(&self, scale: RenderScale, commit: Option) -> DamageSet { - blur_damage(self.geometry(scale).size, self.commit, commit) + blur_damage(self.geometry(scale).size, self.current_commit(), commit) } fn opaque_regions(&self, _scale: RenderScale) -> OpaqueRegions { @@ -311,7 +314,6 @@ struct MaterialParameters { presentation_alpha: f32, background_opacity: f32, blur: f32, - scene_generation: u64, sample_geometry: Rectangle, sample_physical: Rectangle, sample_framebuffer: [f32; 4], @@ -341,8 +343,8 @@ struct CachedMaterial { #[derive(Debug, Default)] pub(crate) struct MaterialBuffers { - contexts: HashMap<(ErasedContextId, usize), CachedMaterial>, - captures: HashMap, + contexts: HashMap<(ErasedContextId, ferese_core::OutputId, usize), CachedMaterial>, + captures: HashMap<(ErasedContextId, ferese_core::OutputId, usize), BlurCapture>, } #[derive(Clone, Debug, PartialEq)] diff --git a/compositor/src/render/resources.rs b/compositor/src/render/resources.rs index dede448..dd6e977 100644 --- a/compositor/src/render/resources.rs +++ b/compositor/src/render/resources.rs @@ -38,6 +38,10 @@ impl RenderResources { pub fn remove_output(&mut self, id: OutputId) { self.outputs.remove(&id); + for surface in self.surfaces.values_mut() { + surface.contexts.retain(|(_, output, _), _| *output != id); + surface.captures.retain(|(_, output, _), _| *output != id); + } } pub fn snapshot(&self, id: &WindowId) -> Option<&ResizeSnapshot> { @@ -105,8 +109,8 @@ impl RenderResources { } for surface in self.surfaces.values_mut() { - surface.contexts.retain(|(id, _), _| id != context); - surface.captures.remove(context); + surface.contexts.retain(|(id, _, _), _| id != context); + surface.captures.retain(|(id, _, _), _| id != context); } } } diff --git a/compositor/src/render/scene.rs b/compositor/src/render/scene.rs index d792e55..a34e472 100644 --- a/compositor/src/render/scene.rs +++ b/compositor/src/render/scene.rs @@ -340,6 +340,7 @@ pub(crate) fn sampled_output_elements( } elements.push(previous.into()); } + backdrop::update(&elements, scale); elements } diff --git a/compositor/src/state.rs b/compositor/src/state.rs index bb9af4e..82e0636 100644 --- a/compositor/src/state.rs +++ b/compositor/src/state.rs @@ -346,7 +346,6 @@ pub struct Ferese { pub(crate) pending_logout: Option, pub(crate) logout_query: Option, pub(crate) logout_owner: Option, - pub(crate) backdrop_generation: u64, viewport_animations: HashMap, paused_workspaces: HashSet, #[cfg(feature = "resize-metrics")] @@ -576,7 +575,6 @@ impl Ferese { pending_logout: None, logout_query: None, logout_owner: None, - backdrop_generation: 0, viewport_animations: HashMap::new(), paused_workspaces: HashSet::new(), #[cfg(feature = "resize-metrics")] @@ -761,7 +759,7 @@ impl Ferese { self.overview.set_font_family(config.overview_font_family); self.wallpaper.reload(config.wallpaper); - self.backdrop_generation = self.backdrop_generation.wrapping_add(1); + self.relayout(); crate::backends::direct::render_all(self); diff --git a/compositor/src/state/animation.rs b/compositor/src/state/animation.rs index 8ec62f3..3363bd4 100644 --- a/compositor/src/state/animation.rs +++ b/compositor/src/state/animation.rs @@ -468,9 +468,7 @@ impl Ferese { self.refresh_workspace_slide_offsets(); self.sync_window_stacking(); - if active_animation || dim_changed { - self.backdrop_generation = self.backdrop_generation.wrapping_add(1); - } + active_animation |= dim_changed; active_animation } diff --git a/compositor/src/state/layout.rs b/compositor/src/state/layout.rs index bb95a64..d7869e5 100644 --- a/compositor/src/state/layout.rs +++ b/compositor/src/state/layout.rs @@ -238,7 +238,7 @@ impl Ferese { } } - let mut layout_changed = self.unmap_invisible_windows(&visible); + self.unmap_invisible_windows(&visible); let now = self.start_time.elapsed(); @@ -268,7 +268,6 @@ impl Ferese { } else { PresentationMode::Normal }; - layout_changed |= !had_geometry || geometry.logical != rect; let mut requested_size = geometry.set_presentation_mode(rect, mode, now); if !was_mapped && (had_geometry || is_fullscreen) { geometry.settle_presentation(); @@ -405,10 +404,6 @@ impl Ferese { } } - if layout_changed { - self.backdrop_generation = self.backdrop_generation.wrapping_add(1); - } - self.sync_window_stacking(); self.retarget_overview(); self.send_shell_snapshots(); diff --git a/compositor/src/state/navigation.rs b/compositor/src/state/navigation.rs index 79055b4..71fbc25 100644 --- a/compositor/src/state/navigation.rs +++ b/compositor/src/state/navigation.rs @@ -71,7 +71,7 @@ impl Ferese { if let Some(swipe) = &mut self.focus_swipe { swipe.progress = progress; } - self.backdrop_generation = self.backdrop_generation.wrapping_add(1); + if let Some(output) = self .focus_swipe .as_ref() @@ -440,7 +440,7 @@ impl Ferese { .expect("gesture output exists") .held_progress = Some(progress); self.refresh_workspace_slide_offsets(); - self.backdrop_generation = self.backdrop_generation.wrapping_add(1); + if let Some(output) = self.outputs_by_id.get(&output).cloned() { self.defer_output_redraw(output); } @@ -464,7 +464,7 @@ impl Ferese { slide.gesture = Some((from, to, direction)); self.workspace_slides.insert(output, slide); self.swipe.mark_preview_started(); - self.backdrop_generation = self.backdrop_generation.wrapping_add(1); + self.relayout(); } diff --git a/compositor/src/theme.rs b/compositor/src/theme.rs index 966c613..9c1cce0 100644 --- a/compositor/src/theme.rs +++ b/compositor/src/theme.rs @@ -565,7 +565,7 @@ impl Ferese { self.theme_settings = Config::resolved_theme_settings(&presented).expect("validated resolved theme"); self.theme_engine.snapshot.presented = presented; self.theme_engine.publish(); - self.backdrop_generation = self.backdrop_generation.wrapping_add(1); + if complete { self.theme_engine.transition = None; } diff --git a/compositor/src/winit.rs b/compositor/src/winit.rs index acbf016..869a7ee 100644 --- a/compositor/src/winit.rs +++ b/compositor/src/winit.rs @@ -95,7 +95,6 @@ pub fn init(event_loop: &mut EventLoop, state: &mut Ferese) -> Result<() let mut backend = backend.borrow_mut(); match event { WinitEvent::Resized { size, scale_factor } => { - state.backdrop_generation = state.backdrop_generation.wrapping_add(1); let scale = normalized_scale(scale_factor); let rate = backend .window()