lgui_core/core/scene/render/
damage.rs1use super::{primitive::*, *};
2
3pub 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}