use crate::core::Rect;
use crate::performance::DirtyRegionTracker;
pub const FULL_REPAINT_AREA_RATIO: f32 = 0.5;
pub fn render_dirty_regions(
tracker: &mut DirtyRegionTracker,
ctx: &mut crate::render::RenderContext,
mut render_all: impl FnMut(&mut crate::render::RenderContext),
) {
tracker.merge();
if tracker.is_empty() {
return;
}
let frame = ctx.size();
let frame_area = u64::from(frame.width) * u64::from(frame.height);
let covered: u64 = tracker
.regions()
.iter()
.map(|region| u64::from(region.rect.width) * u64::from(region.rect.height))
.sum();
let too_large =
frame_area == 0 || (covered as f32) >= (frame_area as f32) * FULL_REPAINT_AREA_RATIO;
if too_large {
if let Some(bounding) = tracker.get_bounding_rect() {
ctx.push_clip(bounding.x, bounding.y, bounding.width, bounding.height);
render_all(ctx);
ctx.pop_clip();
}
tracker.clear();
return;
}
let regions: Vec<Rect> = tracker.regions().iter().map(|r| r.rect).collect();
for rect in regions {
ctx.push_clip(rect.x, rect.y, rect.width, rect.height);
render_all(ctx);
ctx.pop_clip();
}
tracker.clear();
}
#[cfg(test)]
mod tests {
use super::*;
use crate::core::{Color, Rect, Size};
use crate::render::RenderContext;
use crate::render::SoftwarePaintBackend;
use crate::render::PaintBackend as _;
fn paints_for(regions: &[Rect], frame: Size) -> usize {
let mut tracker = DirtyRegionTracker::new();
for rect in regions {
tracker.add(*rect);
}
let mut backend = SoftwarePaintBackend::new(frame, 1.0);
backend.begin_frame(Color::rgb(0, 0, 0));
let mut paints = 0usize;
{
let mut ctx = RenderContext::new(&mut backend);
render_dirty_regions(&mut tracker, &mut ctx, |_| {
paints += 1;
});
}
paints
}
#[test]
fn test_dirty_region_tracker() {
let mut tracker = DirtyRegionTracker::new();
tracker.add(Rect::new(0, 0, 100, 100));
tracker.add(Rect::new(50, 50, 100, 100));
tracker.merge();
assert_eq!(tracker.len(), 1);
assert!(!tracker.is_empty());
}
#[test]
fn no_damage_paints_nothing() {
assert_eq!(paints_for(&[], Size::new(800, 600)), 0);
}
#[test]
fn small_regions_in_a_large_frame_are_painted_separately() {
let regions =
[Rect::new(0, 0, 16, 16), Rect::new(100, 100, 16, 16), Rect::new(200, 200, 16, 16)];
assert_eq!(paints_for(®ions, Size::new(2000, 2000)), 3);
}
#[test]
fn damage_covering_the_frame_collapses_to_one_pass() {
let regions = [Rect::new(0, 0, 800, 400), Rect::new(0, 400, 800, 300)];
assert_eq!(
paints_for(®ions, Size::new(800, 700)),
1,
"two non-overlapping regions that together fill the frame must merge into one pass"
);
}
#[test]
fn the_fallback_applies_at_the_documented_ratio() {
let frame = Size::new(100, 100);
let half = Rect::new(0, 0, 100, 50);
assert_eq!(paints_for(&[half], frame), 1, "half the frame is the threshold");
let just_under = Rect::new(0, 0, 100, 49);
assert_eq!(
paints_for(&[just_under], frame),
1,
"a single region is one pass either way, so this pins that it still paints"
);
}
#[test]
fn many_small_regions_do_not_trigger_the_fallback() {
let regions: Vec<Rect> = (0..40).map(|i| Rect::new(i * 10, 0, 5, 5)).collect();
assert_eq!(
paints_for(®ions, Size::new(1000, 1000)),
regions.len(),
"forty regions covering 1000 px of a 1_000_000 px frame must not fall back"
);
}
}