Skip to main content

lgui_core/core/scene/render/
damage.rs

1use super::{primitive::*, *};
2
3/// Computes the pixels that can change when a retained layer replaces one command list with
4/// another. Returned rectangles use the same local coordinate space as `previous` and `next`.
5pub fn compositing_layer_damage(
6    previous: &[ScenePrimitive],
7    next: &[ScenePrimitive],
8    layer_bounds: UiRect,
9) -> Vec<UiRect> {
10    let mut damage = LayerDamageAccumulator::new(layer_bounds);
11    collect_command_damage(previous, next, &mut damage);
12    damage.finish()
13}
14
15struct LayerDamageAccumulator {
16    bounds: UiRect,
17    rects: Vec<UiRect>,
18    full: bool,
19}
20
21impl LayerDamageAccumulator {
22    fn new(bounds: UiRect) -> Self {
23        Self {
24            bounds,
25            rects: Vec::new(),
26            full: false,
27        }
28    }
29
30    fn add(&mut self, rect: UiRect) {
31        if self.full {
32            return;
33        }
34        let Some(rect) = rect.inflate(2.0, 2.0).intersect(self.bounds) else {
35            return;
36        };
37        if rect.width() <= 0.0 || rect.height() <= 0.0 {
38            return;
39        }
40        let mut merged = rect;
41        let mut index = 0;
42        while index < self.rects.len() {
43            if self.rects[index]
44                .inflate(2.0, 2.0)
45                .intersect(merged)
46                .is_some()
47            {
48                merged = self.rects.remove(index).union(merged);
49            } else {
50                index += 1;
51            }
52        }
53        self.rects.push(merged);
54        let bounds_area = rect_area(self.bounds);
55        let dirty_area = self.rects.iter().copied().map(rect_area).sum::<f64>();
56        if self.rects.len() > 12 || bounds_area <= 0.0 || dirty_area / bounds_area >= 0.90 {
57            self.full = true;
58            self.rects.clear();
59        }
60    }
61
62    fn finish(self) -> Vec<UiRect> {
63        if self.full {
64            vec![self.bounds]
65        } else {
66            self.rects
67        }
68    }
69}
70
71fn collect_command_damage(
72    previous: &[ScenePrimitive],
73    next: &[ScenePrimitive],
74    damage: &mut LayerDamageAccumulator,
75) {
76    let previous_by_id = previous
77        .iter()
78        .enumerate()
79        .map(|(index, command)| (command.id(), (index, command)))
80        .collect::<HashMap<_, _>>();
81    let next_by_id = next
82        .iter()
83        .enumerate()
84        .map(|(index, command)| (command.id(), (index, command)))
85        .collect::<HashMap<_, _>>();
86
87    for (id, (previous_index, previous_command)) in &previous_by_id {
88        let Some((next_index, next_command)) = next_by_id.get(id) else {
89            damage.add(previous_command.paint_bounds());
90            continue;
91        };
92        if previous_command.signature() == next_command.signature() && previous_index == next_index
93        {
94            continue;
95        }
96        if previous_index != next_index
97            || !collect_nested_command_damage(previous_command, next_command, damage)
98        {
99            damage.add(previous_command.paint_bounds());
100            damage.add(next_command.paint_bounds());
101        }
102    }
103
104    for (id, (_, command)) in next_by_id {
105        if !previous_by_id.contains_key(id) {
106            damage.add(command.paint_bounds());
107        }
108    }
109}
110
111fn collect_nested_command_damage(
112    previous: &ScenePrimitive,
113    next: &ScenePrimitive,
114    damage: &mut LayerDamageAccumulator,
115) -> bool {
116    match (previous, next) {
117        (
118            ScenePrimitive::CompositingLayer {
119                rect: previous_rect,
120                spec: previous_spec,
121                commands: previous_commands,
122                phase: previous_phase,
123                ..
124            },
125            ScenePrimitive::CompositingLayer {
126                rect: next_rect,
127                spec: next_spec,
128                commands: next_commands,
129                phase: next_phase,
130                ..
131            },
132        ) if previous_rect == next_rect
133            && previous_spec == next_spec
134            && next_spec.shadow.is_none()
135            && next_spec.transform.is_identity()
136            && previous_phase == next_phase =>
137        {
138            let local_bounds = UiRect::new(0.0, 0.0, next_rect.width(), next_rect.height());
139            for rect in compositing_layer_damage(previous_commands, next_commands, local_bounds) {
140                damage.add(rect.translate(next_rect.left, next_rect.top));
141            }
142            true
143        }
144        (
145            ScenePrimitive::Clip {
146                rect: previous_rect,
147                commands: previous_commands,
148                phase: previous_phase,
149                ..
150            },
151            ScenePrimitive::Clip {
152                rect: next_rect,
153                commands: next_commands,
154                phase: next_phase,
155                ..
156            },
157        ) if previous_rect == next_rect && previous_phase == next_phase => {
158            let mut nested = LayerDamageAccumulator::new(*next_rect);
159            collect_command_damage(previous_commands, next_commands, &mut nested);
160            for rect in nested.finish() {
161                damage.add(rect);
162            }
163            true
164        }
165        (
166            ScenePrimitive::ClipPath {
167                rect: previous_rect,
168                path: previous_path,
169                commands: previous_commands,
170                phase: previous_phase,
171                ..
172            },
173            ScenePrimitive::ClipPath {
174                rect: next_rect,
175                path: next_path,
176                commands: next_commands,
177                phase: next_phase,
178                ..
179            },
180        ) if previous_rect == next_rect
181            && previous_path == next_path
182            && previous_phase == next_phase =>
183        {
184            let mut nested = LayerDamageAccumulator::new(*next_rect);
185            collect_command_damage(previous_commands, next_commands, &mut nested);
186            for rect in nested.finish() {
187                damage.add(rect);
188            }
189            true
190        }
191        _ => false,
192    }
193}
194
195fn rect_area(rect: UiRect) -> f64 {
196    rect.width().max(0.0) as f64 * rect.height().max(0.0) as f64
197}