use crate::geometry::{PixelRect, Rect};
const MERGE_SLACK: f64 = 1.3;
const MAX_RECTS: usize = 16;
const FULL_FRAME_RATIO: f64 = 0.6;
const REDUCE_LIMIT: usize = 64;
#[derive(Copy, Clone, Debug)]
struct ClippedRect {
bounds: Rect,
clip: Rect,
}
#[derive(Debug, Default)]
pub(crate) struct DamageAccumulator {
clipped: Vec<ClippedRect>,
full: bool,
out: Vec<PixelRect>,
pre_merge_pixels: u64,
}
impl DamageAccumulator {
pub(crate) fn new() -> DamageAccumulator {
DamageAccumulator::default()
}
pub(crate) fn add_clipped(&mut self, bounds: Rect, clip: Rect) {
if bounds.is_empty() || clip.is_empty() {
return;
}
self.clipped.push(ClippedRect { bounds, clip });
}
pub(crate) fn add_full(&mut self) {
self.full = true;
}
pub(crate) fn is_empty(&self) -> bool {
!self.full && self.clipped.is_empty()
}
pub(crate) fn finish(
&mut self,
scale: f32,
surface_width: u32,
surface_height: u32,
) -> &[PixelRect] {
self.out.clear();
self.pre_merge_pixels = 0;
let Some(surface) = surface_rect(surface_width, surface_height) else {
return &self.out;
};
if self.full {
self.pre_merge_pixels = surface.area();
self.out.push(surface);
return &self.out;
}
for rect in &self.clipped {
let bounds = PixelRect::from_logical(rect.bounds, scale, surface_width, surface_height);
let clip = PixelRect::from_logical(rect.clip, scale, surface_width, surface_height);
if let (Some(bounds), Some(clip)) = (bounds, clip)
&& let Some(intersection) = bounds.intersect(clip)
{
self.out.push(intersection);
}
}
self.pre_merge_pixels = self.out.iter().map(|rect| rect.area()).sum();
self.merge(surface);
&self.out
}
pub(crate) fn finish_measured(
&mut self,
scale: f32,
surface_width: u32,
surface_height: u32,
) -> (&[PixelRect], u64) {
self.finish(scale, surface_width, surface_height);
let pixels = self.pre_merge_pixels;
(self.out.as_slice(), pixels)
}
pub(crate) fn clear(&mut self) {
self.clipped.clear();
self.out.clear();
self.full = false;
}
fn merge(&mut self, surface: PixelRect) {
loop {
let before = self.out.len();
let mut i = 0;
while i < self.out.len() {
let mut j = i + 1;
while j < self.out.len() {
if should_merge(self.out[i], self.out[j]) {
self.out[i] = self.out[i].union(self.out[j]);
self.out.swap_remove(j);
} else {
j += 1;
}
}
i += 1;
}
if self.out.len() == before {
break;
}
}
if self.out.len() > REDUCE_LIMIT {
self.out.clear();
self.out.push(surface);
return;
}
while self.out.len() > MAX_RECTS {
let (i, j) = cheapest_pair(&self.out);
self.out[i] = self.out[i].union(self.out[j]);
self.out.swap_remove(j);
}
let total: u128 = self.out.iter().map(|rect| u128::from(rect.area())).sum();
if total as f64 > FULL_FRAME_RATIO * surface.area() as f64 {
self.out.clear();
self.out.push(surface);
}
}
}
fn surface_rect(width: u32, height: u32) -> Option<PixelRect> {
if width == 0 || height == 0 {
return None;
}
Some(PixelRect {
x: 0,
y: 0,
width,
height,
})
}
fn should_merge(a: PixelRect, b: PixelRect) -> bool {
let combined = a.area() as f64 + b.area() as f64;
a.union(b).area() as f64 <= MERGE_SLACK * combined
}
fn cheapest_pair(rects: &[PixelRect]) -> (usize, usize) {
debug_assert!(rects.len() >= 2, "a pair needs two rectangles");
let mut best = (0, 1);
let mut best_added = u64::MAX;
for i in 0..rects.len() {
for j in (i + 1)..rects.len() {
let pair = rects[i].area().saturating_add(rects[j].area());
let added = rects[i].union(rects[j]).area().saturating_sub(pair);
if added < best_added {
best_added = added;
best = (i, j);
}
}
}
best
}
#[cfg(test)]
#[path = "damage_tests.rs"]
mod tests;