Invalidate blur from per-output sampled backdrop damage

This commit is contained in:
Samuel Onoja committed 2026-10-02 04:27:07 +01:00
1 parent 3d6cee125d
commit 325835f40a
13 files changed
+273 -82

No files matched your search

+228
View File
@@ -0,0 +1,228 @@
use super::*;
#[derive(Debug, Default)]
pub(super) struct Backdrop {
pub commit: CommitCounter,
sources: Vec<Source>,
region: Option<Rectangle<i32, Physical>>,
}
#[derive(Debug)]
struct Source {
id: Id,
commit: CommitCounter,
geometry: Rectangle<i32, Physical>,
alpha: f32,
src: Rectangle<f64, Buffer>,
transform: Transform,
visible: Vec<Rectangle<i32, Physical>>,
}
impl Backdrop {
fn update<E: Element>(
&mut self,
elements: &[E],
region: Rectangle<i32, Physical>,
scale: RenderScale<f64>,
) -> 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::<Vec<_>>();
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<i32, Physical>,
damage: Vec<Rectangle<i32, Physical>>,
opaque: bool,
alpha: f32,
}
impl TestElement {
fn new(rect: Rectangle<i32, Physical>) -> Self {
Self {
id: Id::new(),
commit: CommitCounter::default(),
rect,
damage: vec![],
opaque: false,
alpha: 1.0,
}
}
fn repaint(&mut self, damage: Vec<Rectangle<i32, Physical>>) {
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<f64, Buffer> {
Rectangle::from_size((100.0, 100.0).into())
}
fn geometry(&self, _: RenderScale<f64>) -> Rectangle<i32, Physical> {
self.rect
}
fn damage_since(&self, _: RenderScale<f64>, commit: Option<CommitCounter>) -> DamageSet<i32, Physical> {
if commit == Some(self.commit) {
DamageSet::default()
} else {
DamageSet::from_slice(&self.damage)
}
}
fn opaque_regions(&self, _: RenderScale<f64>) -> OpaqueRegions<i32, Physical> {
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<i32, Physical> {
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()));
}
}
+19 -10
View File
@@ -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::<GlesTexture>::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(&parameters, 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(&parameters, 2.0));
}
match &mut cached.element {
MaterialElement::Fill(element) => {
element.resize(
+11 -9
View File
@@ -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<std::sync::Mutex<backdrop::Backdrop>>,
geometry: Rectangle<i32, Logical>,
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<f64, Buffer> {
@@ -182,7 +185,7 @@ impl Element for BlurRenderElement {
}
fn damage_since(&self, scale: RenderScale<f64>, commit: Option<CommitCounter>) -> DamageSet<i32, Physical> {
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<f64>) -> OpaqueRegions<i32, Physical> {
@@ -311,7 +314,6 @@ struct MaterialParameters {
presentation_alpha: f32,
background_opacity: f32,
blur: f32,
scene_generation: u64,
sample_geometry: Rectangle<i32, Logical>,
sample_physical: Rectangle<i32, Physical>,
sample_framebuffer: [f32; 4],
@@ -341,8 +343,8 @@ struct CachedMaterial {
#[derive(Debug, Default)]
pub(crate) struct MaterialBuffers {
contexts: HashMap<(ErasedContextId, usize), CachedMaterial>,
captures: HashMap<ErasedContextId, BlurCapture>,
contexts: HashMap<(ErasedContextId, ferese_core::OutputId, usize), CachedMaterial>,
captures: HashMap<(ErasedContextId, ferese_core::OutputId, usize), BlurCapture>,
}
#[derive(Clone, Debug, PartialEq)]
+6 -2
View File
@@ -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);
}
}
}
+1
View File
@@ -340,6 +340,7 @@ pub(crate) fn sampled_output_elements(
}
elements.push(previous.into());
}
backdrop::update(&elements, scale);
elements
}