crabshell_render/
damage.rs1use std::collections::VecDeque;
11use std::sync::atomic::{AtomicU64, Ordering};
12
13use crabshell_ui::Rect;
14
15const HISTORY: usize = 4;
18
19const MARGIN: u32 = 1;
22
23#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
25pub struct PixelRect {
26 pub x: u32,
28 pub y: u32,
30 pub width: u32,
32 pub height: u32,
34}
35
36impl PixelRect {
37 pub const fn new(x: u32, y: u32, width: u32, height: u32) -> PixelRect {
39 PixelRect {
40 x,
41 y,
42 width,
43 height,
44 }
45 }
46
47 pub fn right(&self) -> u32 {
49 self.x + self.width
50 }
51
52 pub fn bottom(&self) -> u32 {
54 self.y + self.height
55 }
56
57 pub fn area(&self) -> u64 {
59 u64::from(self.width) * u64::from(self.height)
60 }
61
62 pub fn overlaps(&self, other: &PixelRect) -> bool {
64 self.x < other.right()
65 && other.x < self.right()
66 && self.y < other.bottom()
67 && other.y < self.bottom()
68 }
69
70 fn union(&self, other: &PixelRect) -> PixelRect {
72 let x = self.x.min(other.x);
73 let y = self.y.min(other.y);
74 PixelRect::new(
75 x,
76 y,
77 self.right().max(other.right()) - x,
78 self.bottom().max(other.bottom()) - y,
79 )
80 }
81}
82
83pub(crate) fn to_pixels(rect: Rect, scale: f32, width: u32, height: u32) -> Option<PixelRect> {
87 if !(rect.width > 0.0 && rect.height > 0.0) {
88 return None;
89 }
90 let left = ((rect.x * scale).floor() as u32).saturating_sub(MARGIN);
92 let top = ((rect.y * scale).floor() as u32).saturating_sub(MARGIN);
93 let right = ((rect.right() * scale).ceil() as u32)
94 .saturating_add(MARGIN)
95 .min(width);
96 let bottom = ((rect.bottom() * scale).ceil() as u32)
97 .saturating_add(MARGIN)
98 .min(height);
99 (left < right && top < bottom).then(|| PixelRect::new(left, top, right - left, bottom - top))
100}
101
102pub(crate) fn merge(mut rects: Vec<PixelRect>) -> Vec<PixelRect> {
106 rects.retain(|rect| rect.area() > 0);
107 let mut merged = true;
108 while merged {
109 merged = false;
110 'outer: for i in 0..rects.len() {
111 for j in i + 1..rects.len() {
112 if rects[i].overlaps(&rects[j]) {
113 let other = rects.swap_remove(j);
114 rects[i] = rects[i].union(&other);
115 merged = true;
116 break 'outer;
117 }
118 }
119 }
120 }
121 rects
122}
123
124#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
127pub struct BufferAge {
128 drawn: Option<(u64, u64)>,
130}
131
132static HISTORIES: AtomicU64 = AtomicU64::new(0);
135
136#[derive(Debug)]
138pub struct DamageHistory {
139 id: u64,
140 next: u64,
142 shape: Option<(u32, u32, u32)>,
145 recent: VecDeque<(u64, Vec<PixelRect>)>,
147}
148
149#[derive(Debug, PartialEq, Eq)]
151pub(crate) struct Plan {
152 pub(crate) repaint: Vec<PixelRect>,
155 pub(crate) report: Vec<PixelRect>,
158}
159
160impl Default for DamageHistory {
161 fn default() -> Self {
162 Self::new()
163 }
164}
165
166impl DamageHistory {
167 pub fn new() -> DamageHistory {
169 DamageHistory {
170 id: HISTORIES.fetch_add(1, Ordering::Relaxed),
171 next: 0,
172 shape: None,
173 recent: VecDeque::new(),
174 }
175 }
176
177 pub(crate) fn plan(
181 &mut self,
182 damage: &[Rect],
183 scale: f32,
184 width: u32,
185 height: u32,
186 age: &mut BufferAge,
187 ) -> Plan {
188 let full = vec![PixelRect::new(0, 0, width, height)];
189 let frame = self.next;
190 self.next += 1;
191
192 let shape = (width, height, scale.to_bits());
193 let mut changed = if self.shape == Some(shape) {
194 merge(
195 damage
196 .iter()
197 .filter_map(|rect| to_pixels(*rect, scale, width, height))
198 .collect(),
199 )
200 } else {
201 self.shape = Some(shape);
204 self.recent.clear();
205 full.clone()
206 };
207 if more_than_half(&changed, width, height) {
208 changed = full.clone();
209 }
210
211 let missed = match age.drawn {
214 Some((history, drawn))
215 if history == self.id
216 && self
217 .recent
218 .front()
219 .is_none_or(|(first, _)| *first <= drawn + 1) =>
220 {
221 Some(
222 self
223 .recent
224 .iter()
225 .filter(|(number, _)| *number > drawn)
226 .flat_map(|(_, rects)| rects.iter().copied())
227 .collect::<Vec<_>>(),
228 )
229 }
230 _ => None,
231 };
232 let repaint = match missed {
233 Some(missed) => {
234 let repaint = merge(changed.iter().copied().chain(missed).collect());
235 if more_than_half(&repaint, width, height) {
236 full
237 } else {
238 repaint
239 }
240 }
241 None => full,
242 };
243
244 self.recent.push_back((frame, changed.clone()));
245 while self.recent.len() > HISTORY {
246 self.recent.pop_front();
247 }
248 age.drawn = Some((self.id, frame));
249 Plan {
250 repaint,
251 report: changed,
252 }
253 }
254}
255
256fn more_than_half(rects: &[PixelRect], width: u32, height: u32) -> bool {
259 let covered: u64 = rects.iter().map(PixelRect::area).sum();
260 covered * 2 > u64::from(width) * u64::from(height)
261}