1use crate::clip::ClipRect;
9
10pub const DEFAULT_TILE_SIZE: u32 = 64;
12
13#[derive(Clone, Copy, Debug, PartialEq, Eq)]
16pub struct Tile {
17 pub x: u32,
19 pub y: u32,
21 pub w: u32,
23 pub h: u32,
25}
26
27impl Tile {
28 pub fn clip_rect(&self) -> ClipRect {
30 ClipRect {
31 x0: self.x as i64,
32 y0: self.y as i64,
33 x1: (self.x + self.w) as i64,
34 y1: (self.y + self.h) as i64,
35 }
36 }
37}
38
39pub struct TileIter {
42 rect: ClipRect,
43 fb_w: u32,
44 fb_h: u32,
45 tile_size: u32,
46 cur_x: u32,
47 cur_y: u32,
48 done: bool,
49}
50
51impl TileIter {
52 fn new(rect: ClipRect, fb_w: u32, fb_h: u32, tile_size: u32) -> Self {
53 let ts = tile_size.max(1);
54 let rx0 = rect.x0.max(0) as u32;
56 let ry0 = rect.y0.max(0) as u32;
57 let clamped = ClipRect {
58 x0: rx0 as i64,
59 y0: ry0 as i64,
60 x1: (rect.x1 as u32).min(fb_w) as i64,
61 y1: (rect.y1 as u32).min(fb_h) as i64,
62 };
63 let done = clamped.is_empty();
64 Self {
65 rect: clamped,
66 fb_w,
67 fb_h,
68 tile_size: ts,
69 cur_x: rx0,
70 cur_y: ry0,
71 done,
72 }
73 }
74}
75
76impl Iterator for TileIter {
77 type Item = Tile;
78
79 fn next(&mut self) -> Option<Tile> {
80 if self.done {
81 return None;
82 }
83 let x0 = self.cur_x;
84 let y0 = self.cur_y;
85 if x0 >= self.fb_w || y0 >= self.fb_h {
86 return None;
87 }
88 if (x0 as i64) >= self.rect.x1 || (y0 as i64) >= self.rect.y1 {
89 return None;
90 }
91
92 let x_end = ((x0 + self.tile_size) as i64).min(self.rect.x1) as u32;
93 let y_end = ((y0 + self.tile_size) as i64).min(self.rect.y1) as u32;
94 let w = x_end.saturating_sub(x0);
95 let h = y_end.saturating_sub(y0);
96
97 if w == 0 || h == 0 {
98 return None;
99 }
100
101 let tile = Tile { x: x0, y: y0, w, h };
102
103 let next_x = x0 + self.tile_size;
105 if (next_x as i64) < self.rect.x1 {
106 self.cur_x = next_x;
107 } else {
108 self.cur_x = self.rect.x0 as u32;
110 self.cur_y = y0 + self.tile_size;
111 if (self.cur_y as i64) >= self.rect.y1 {
112 self.done = true;
113 }
114 }
115
116 Some(tile)
117 }
118}
119
120pub fn tiles_for(rect: ClipRect, fb_w: u32, fb_h: u32) -> TileIter {
123 TileIter::new(rect, fb_w, fb_h, DEFAULT_TILE_SIZE)
124}
125
126pub fn render_tiles<F>(rect: ClipRect, fb_w: u32, fb_h: u32, mut f: F)
131where
132 F: FnMut(Tile),
133{
134 for tile in tiles_for(rect, fb_w, fb_h) {
135 f(tile);
136 }
137}
138
139pub fn collect_tiles(rect: ClipRect, fb_w: u32, fb_h: u32) -> Vec<Tile> {
144 tiles_for(rect, fb_w, fb_h).collect()
145}
146
147#[cfg(feature = "parallel")]
170pub fn render_parallel<F>(tiles: &[Tile], render_fn: F) -> Vec<(Tile, Vec<u32>)>
171where
172 F: Fn(&Tile) -> Vec<u32> + Send + Sync,
173{
174 use rayon::prelude::*;
175 tiles
176 .par_iter()
177 .map(|tile| (*tile, render_fn(tile)))
178 .collect()
179}
180
181#[derive(Debug, Clone)]
193pub struct DirtyRegion {
194 tiles_wide: u32,
196 tiles_tall: u32,
198 dirty: Vec<bool>,
200 all_dirty: bool,
202}
203
204impl DirtyRegion {
205 pub fn new(fb_width: u32, fb_height: u32, tile_size: u32) -> Self {
210 let ts = tile_size.max(1);
211 let tw = fb_width.div_ceil(ts);
212 let th = fb_height.div_ceil(ts);
213 Self {
214 tiles_wide: tw,
215 tiles_tall: th,
216 dirty: vec![true; (tw * th) as usize],
217 all_dirty: true,
218 }
219 }
220
221 pub fn mark_rect(&mut self, x: u32, y: u32, width: u32, height: u32, tile_size: u32) {
226 if self.all_dirty {
227 return;
228 }
229 let ts = tile_size.max(1);
230 let tx_start = x / ts;
231 let ty_start = y / ts;
232 let tx_end = (x + width).div_ceil(ts);
233 let ty_end = (y + height).div_ceil(ts);
234 for ty in ty_start..ty_end.min(self.tiles_tall) {
235 for tx in tx_start..tx_end.min(self.tiles_wide) {
236 let idx = (ty * self.tiles_wide + tx) as usize;
237 if idx < self.dirty.len() {
238 self.dirty[idx] = true;
239 }
240 }
241 }
242 }
243
244 pub fn mark_tile(&mut self, tx: u32, ty: u32) {
248 if self.all_dirty {
249 return;
250 }
251 let idx = (ty * self.tiles_wide + tx) as usize;
252 if idx < self.dirty.len() {
253 self.dirty[idx] = true;
254 }
255 }
256
257 pub fn is_tile_dirty(&self, tx: u32, ty: u32) -> bool {
259 if self.all_dirty {
260 return true;
261 }
262 let idx = (ty * self.tiles_wide + tx) as usize;
263 self.dirty.get(idx).copied().unwrap_or(false)
264 }
265
266 pub fn clear_all(&mut self) {
271 self.dirty.fill(false);
272 self.all_dirty = false;
273 }
274
275 pub fn invalidate_all(&mut self) {
279 self.dirty.fill(true);
280 self.all_dirty = true;
281 }
282
283 pub fn dirty_tiles(&self) -> impl Iterator<Item = (u32, u32)> + '_ {
285 (0..self.tiles_tall).flat_map(move |ty| {
286 (0..self.tiles_wide).filter_map(move |tx| {
287 if self.is_tile_dirty(tx, ty) {
288 Some((tx, ty))
289 } else {
290 None
291 }
292 })
293 })
294 }
295
296 pub fn dirty_count(&self) -> usize {
298 if self.all_dirty {
299 (self.tiles_wide * self.tiles_tall) as usize
300 } else {
301 self.dirty.iter().filter(|&&d| d).count()
302 }
303 }
304
305 pub fn total_tiles(&self) -> usize {
307 (self.tiles_wide * self.tiles_tall) as usize
308 }
309}
310
311#[cfg(test)]
316mod tests {
317 use super::*;
318
319 #[test]
320 fn tiles_cover_full_frame() {
321 let w = 200u32;
322 let h = 150u32;
323 let rect = ClipRect::full(w, h);
324 let tiles: Vec<Tile> = tiles_for(rect, w, h).collect();
325
326 let mut coverage = vec![0u32; (w * h) as usize];
328 for tile in &tiles {
329 for ty in tile.y..tile.y + tile.h {
330 for tx in tile.x..tile.x + tile.w {
331 coverage[(ty * w + tx) as usize] += 1;
332 }
333 }
334 }
335 for (i, &c) in coverage.iter().enumerate() {
336 assert_eq!(c, 1, "pixel {i} covered {c} times (expected exactly 1)");
337 }
338 }
339
340 #[test]
341 fn tile_covers_frame() {
342 let w = 64u32;
344 let h = 64u32;
345 let rect = ClipRect::full(w, h);
346 let tiles: Vec<Tile> = tiles_for(rect, w, h).collect();
347 assert_eq!(tiles.len(), 1);
349 assert_eq!(
350 tiles[0],
351 Tile {
352 x: 0,
353 y: 0,
354 w: 64,
355 h: 64
356 }
357 );
358 }
359
360 #[test]
361 fn tiles_non_overlapping() {
362 let w = 128u32;
364 let h = 128u32;
365 let rect = ClipRect::full(w, h);
366 let tiles: Vec<Tile> = tiles_for(rect, w, h).collect();
367 assert_eq!(tiles.len(), 4);
368 let mut coverage = vec![0u32; (w * h) as usize];
370 for tile in &tiles {
371 for ty in tile.y..tile.y + tile.h {
372 for tx in tile.x..tile.x + tile.w {
373 coverage[(ty * w + tx) as usize] += 1;
374 }
375 }
376 }
377 for &c in &coverage {
378 assert_eq!(c, 1, "every pixel must be covered exactly once");
379 }
380 }
381
382 #[test]
383 fn partial_boundary_tiles() {
384 let w = 70u32;
386 let h = 70u32;
387 let rect = ClipRect::full(w, h);
388 let tiles: Vec<Tile> = tiles_for(rect, w, h).collect();
389 assert_eq!(tiles.len(), 4);
391 let mut coverage = vec![0u32; (w * h) as usize];
392 for tile in &tiles {
393 for ty in tile.y..tile.y + tile.h {
394 for tx in tile.x..tile.x + tile.w {
395 coverage[(ty * w + tx) as usize] += 1;
396 }
397 }
398 }
399 for (i, &c) in coverage.iter().enumerate() {
400 assert_eq!(c, 1, "pixel {i} covered {c} times");
401 }
402 }
403
404 #[test]
405 fn tile_clip_rect_correct() {
406 let tile = Tile {
407 x: 64,
408 y: 128,
409 w: 32,
410 h: 16,
411 };
412 let clip = tile.clip_rect();
413 assert_eq!(clip.x0, 64);
414 assert_eq!(clip.y0, 128);
415 assert_eq!(clip.x1, 96);
416 assert_eq!(clip.y1, 144);
417 }
418
419 #[test]
420 fn render_tiles_visits_all() {
421 let w = 100u32;
422 let h = 100u32;
423 let rect = ClipRect::full(w, h);
424 let mut count = 0u32;
425 render_tiles(rect, w, h, |_tile| {
426 count += 1;
427 });
428 assert_eq!(count, 4);
430 }
431
432 #[test]
433 fn collect_tiles_returns_same_as_iterator() {
434 let w = 128u32;
435 let h = 128u32;
436 let rect = ClipRect::full(w, h);
437 let via_iter: Vec<Tile> = tiles_for(rect, w, h).collect();
438 let via_collect = collect_tiles(rect, w, h);
439 assert_eq!(via_iter, via_collect);
440 }
441
442 #[cfg(feature = "parallel")]
445 #[test]
446 fn parallel_output_matches_sequential() {
447 let w = 128u32;
448 let h = 128u32;
449 let rect = ClipRect::full(w, h);
450
451 let render_fn = |tile: &Tile| -> Vec<u32> {
454 let colour: u32 =
455 0xFF000000 | ((tile.x & 0xFF) << 16) | ((tile.y & 0xFF) << 8) | (tile.w & 0xFF);
456 vec![colour; (tile.w * tile.h) as usize]
457 };
458
459 let tiles = collect_tiles(rect, w, h);
461 let sequential: Vec<(Tile, Vec<u32>)> = tiles.iter().map(|t| (*t, render_fn(t))).collect();
462
463 let parallel = super::render_parallel(&tiles, render_fn);
465
466 let mut seq_sorted = sequential;
468 let mut par_sorted = parallel;
469 seq_sorted.sort_by_key(|(t, _)| (t.y, t.x));
470 par_sorted.sort_by_key(|(t, _)| (t.y, t.x));
471
472 assert_eq!(seq_sorted.len(), par_sorted.len(), "tile count mismatch");
473 for ((st, sp), (pt, pp)) in seq_sorted.iter().zip(par_sorted.iter()) {
474 assert_eq!(st, pt, "tile metadata mismatch");
475 assert_eq!(sp, pp, "pixel data mismatch for tile ({},{})", st.x, st.y);
476 }
477 }
478}