Group damage regions by area increase

This commit is contained in:
Héctor Ramón Jiménez 2023-04-05 04:10:00 +02:00
parent 6270c33ed9
commit f8cd1faa28
No known key found for this signature in database
GPG key ID: 140CC052C94F138E
4 changed files with 199 additions and 108 deletions

View file

@ -66,6 +66,11 @@ impl Rectangle<f32> {
Size::new(self.width, self.height) Size::new(self.width, self.height)
} }
/// Returns the area of the [`Rectangle`].
pub fn area(&self) -> f32 {
self.width * self.height
}
/// Returns true if the given [`Point`] is contained in the [`Rectangle`]. /// Returns true if the given [`Point`] is contained in the [`Rectangle`].
pub fn contains(&self, point: Point) -> bool { pub fn contains(&self, point: Point) -> bool {
self.x <= point.x self.x <= point.x
@ -74,6 +79,15 @@ impl Rectangle<f32> {
&& point.y <= self.y + self.height && point.y <= self.y + self.height
} }
/// Returns true if the current [`Rectangle`] is completely within the given
/// `container`.
pub fn is_within(&self, container: &Rectangle) -> bool {
container.contains(self.position())
&& container.contains(
self.position() + Vector::new(self.width, self.height),
)
}
/// Computes the intersection with the given [`Rectangle`]. /// Computes the intersection with the given [`Rectangle`].
pub fn intersection( pub fn intersection(
&self, &self,

View file

@ -178,8 +178,8 @@ impl Primitive {
} }
Self::Quad { bounds, .. } Self::Quad { bounds, .. }
| Self::Image { bounds, .. } | Self::Image { bounds, .. }
| Self::Svg { bounds, .. } | Self::Svg { bounds, .. } => bounds.expand(1.0),
| Self::Clip { bounds, .. } => bounds.expand(1.0), Self::Clip { bounds, .. } => *bounds,
Self::SolidMesh { size, .. } | Self::GradientMesh { size, .. } => { Self::SolidMesh { size, .. } | Self::GradientMesh { size, .. } => {
Rectangle::with_size(*size) Rectangle::with_size(*size)
} }

View file

@ -49,7 +49,7 @@ impl Backend {
primitives: &[Primitive], primitives: &[Primitive],
viewport: &Viewport, viewport: &Viewport,
background_color: Color, background_color: Color,
_overlay: &[T], overlay: &[T],
) { ) {
let physical_size = viewport.physical_size(); let physical_size = viewport.physical_size();
@ -70,37 +70,20 @@ impl Backend {
self.last_size = physical_size; self.last_size = physical_size;
let scale_factor = viewport.scale_factor() as f32; let scale_factor = viewport.scale_factor() as f32;
let physical_bounds = Rectangle {
x: 0.0,
y: 0.0,
width: physical_size.width as f32,
height: physical_size.height as f32,
};
dbg!(damage.len()); let damage = group_damage(damage, scale_factor, physical_size);
'draw_regions: for (i, region) in damage.iter().enumerate() { if !overlay.is_empty() {
for previous in damage.iter().take(i) { pixels.fill(into_color(background_color));
if previous.contains(region.position()) }
&& previous.contains(
region.position()
+ Vector::new(region.width, region.height),
)
{
continue 'draw_regions;
}
}
let region = *region * scale_factor;
let Some(region) = physical_bounds.intersection(&region) else { continue };
for region in damage {
let path = tiny_skia::PathBuilder::from_rect( let path = tiny_skia::PathBuilder::from_rect(
tiny_skia::Rect::from_xywh( tiny_skia::Rect::from_xywh(
region.x, region.x,
region.y, region.y,
region.width.min(viewport.physical_width() as f32), region.width,
region.height.min(viewport.physical_height() as f32), region.height,
) )
.expect("Create damage rectangle"), .expect("Create damage rectangle"),
); );
@ -111,6 +94,7 @@ impl Backend {
shader: tiny_skia::Shader::SolidColor(into_color( shader: tiny_skia::Shader::SolidColor(into_color(
background_color, background_color,
)), )),
anti_alias: false,
..Default::default() ..Default::default()
}, },
tiny_skia::FillRule::default(), tiny_skia::FillRule::default(),
@ -131,49 +115,62 @@ impl Backend {
); );
} }
//pixels.stroke_path( if !overlay.is_empty() {
// &path, pixels.stroke_path(
// &tiny_skia::Paint { &path,
// shader: tiny_skia::Shader::SolidColor(into_color( &tiny_skia::Paint {
// Color::from_rgb(1.0, 0.0, 0.0), shader: tiny_skia::Shader::SolidColor(into_color(
// )), Color::from_rgb(1.0, 0.0, 0.0),
// anti_alias: true, )),
// ..tiny_skia::Paint::default() anti_alias: false,
// }, ..tiny_skia::Paint::default()
// &tiny_skia::Stroke { },
// width: 1.0, &tiny_skia::Stroke {
// ..tiny_skia::Stroke::default() width: 1.0,
// }, ..tiny_skia::Stroke::default()
// tiny_skia::Transform::identity(), },
// None, tiny_skia::Transform::identity(),
//); None,
);
}
} }
//for (i, text) in overlay.iter().enumerate() { if !overlay.is_empty() {
// const OVERLAY_TEXT_SIZE: f32 = 20.0; let bounds = Rectangle {
x: 0.0,
y: 0.0,
width: viewport.physical_width() as f32,
height: viewport.physical_height() as f32,
};
// self.draw_primitive( adjust_clip_mask(clip_mask, pixels, bounds);
// &Primitive::Text {
// content: text.as_ref().to_owned(), for (i, text) in overlay.iter().enumerate() {
// size: OVERLAY_TEXT_SIZE, const OVERLAY_TEXT_SIZE: f32 = 20.0;
// bounds: Rectangle {
// x: 10.0, self.draw_primitive(
// y: 10.0 + i as f32 * OVERLAY_TEXT_SIZE * 1.2, &Primitive::Text {
// width: f32::INFINITY, content: text.as_ref().to_owned(),
// height: f32::INFINITY, size: OVERLAY_TEXT_SIZE,
// }, bounds: Rectangle {
// color: Color::BLACK, x: 10.0,
// font: Font::MONOSPACE, y: 10.0 + i as f32 * OVERLAY_TEXT_SIZE * 1.2,
// horizontal_alignment: alignment::Horizontal::Left, width: bounds.width - 1.0,
// vertical_alignment: alignment::Vertical::Top, height: bounds.height - 1.0,
// }, },
// pixels, color: Color::BLACK,
// clip_mask, font: Font::MONOSPACE,
// Rectangle::EMPTY, horizontal_alignment: alignment::Horizontal::Left,
// scale_factor, vertical_alignment: alignment::Vertical::Top,
// Vector::ZERO, },
// ); pixels,
//} clip_mask,
bounds,
scale_factor,
Vector::ZERO,
);
}
}
self.text_pipeline.trim_cache(); self.text_pipeline.trim_cache();
@ -201,12 +198,15 @@ impl Backend {
border_width, border_width,
border_color, border_color,
} => { } => {
if !clip_bounds let physical_bounds = (*bounds + translation) * scale_factor;
.intersects(&((*bounds + translation) * scale_factor))
{ if !clip_bounds.intersects(&physical_bounds) {
return; return;
} }
let clip_mask = (!physical_bounds.is_within(&clip_bounds))
.then(|| clip_mask as &_);
let transform = tiny_skia::Transform::from_translate( let transform = tiny_skia::Transform::from_translate(
translation.x, translation.x,
translation.y, translation.y,
@ -230,7 +230,7 @@ impl Backend {
}, },
tiny_skia::FillRule::EvenOdd, tiny_skia::FillRule::EvenOdd,
transform, transform,
Some(clip_mask), clip_mask,
); );
if *border_width > 0.0 { if *border_width > 0.0 {
@ -248,7 +248,7 @@ impl Backend {
..tiny_skia::Stroke::default() ..tiny_skia::Stroke::default()
}, },
transform, transform,
Some(clip_mask), clip_mask,
); );
} }
} }
@ -261,12 +261,16 @@ impl Backend {
horizontal_alignment, horizontal_alignment,
vertical_alignment, vertical_alignment,
} => { } => {
if !clip_bounds.intersects( let physical_bounds =
&((primitive.bounds() + translation) * scale_factor), (primitive.bounds() + translation) * scale_factor;
) {
if !clip_bounds.intersects(&physical_bounds) {
return; return;
} }
let clip_mask = (!physical_bounds.is_within(&clip_bounds))
.then(|| clip_mask as &_);
self.text_pipeline.draw( self.text_pipeline.draw(
content, content,
(*bounds + translation) * scale_factor, (*bounds + translation) * scale_factor,
@ -276,7 +280,7 @@ impl Backend {
*horizontal_alignment, *horizontal_alignment,
*vertical_alignment, *vertical_alignment,
pixels, pixels,
Some(clip_mask), clip_mask,
); );
} }
#[cfg(feature = "image")] #[cfg(feature = "image")]
@ -323,18 +327,21 @@ impl Backend {
} => { } => {
let bounds = path.bounds(); let bounds = path.bounds();
if !clip_bounds.intersects( let physical_bounds = (Rectangle {
&((Rectangle { x: bounds.x(),
x: bounds.x(), y: bounds.y(),
y: bounds.y(), width: bounds.width(),
width: bounds.width(), height: bounds.height(),
height: bounds.height(), } + translation)
} + translation) * scale_factor;
* scale_factor),
) { if !clip_bounds.intersects(&physical_bounds) {
return; return;
} }
let clip_mask = (!physical_bounds.is_within(&clip_bounds))
.then(|| clip_mask as &_);
pixels.fill_path( pixels.fill_path(
path, path,
paint, paint,
@ -342,7 +349,7 @@ impl Backend {
transform transform
.post_translate(translation.x, translation.y) .post_translate(translation.x, translation.y)
.post_scale(scale_factor, scale_factor), .post_scale(scale_factor, scale_factor),
Some(clip_mask), clip_mask,
); );
} }
Primitive::Stroke { Primitive::Stroke {
@ -353,18 +360,21 @@ impl Backend {
} => { } => {
let bounds = path.bounds(); let bounds = path.bounds();
if !clip_bounds.intersects( let physical_bounds = (Rectangle {
&((Rectangle { x: bounds.x(),
x: bounds.x(), y: bounds.y(),
y: bounds.y(), width: bounds.width(),
width: bounds.width(), height: bounds.height(),
height: bounds.height(), } + translation)
} + translation) * scale_factor;
* scale_factor),
) { if !clip_bounds.intersects(&physical_bounds) {
return; return;
} }
let clip_mask = (!physical_bounds.is_within(&clip_bounds))
.then(|| clip_mask as &_);
pixels.stroke_path( pixels.stroke_path(
path, path,
paint, paint,
@ -372,7 +382,7 @@ impl Backend {
transform transform
.post_translate(translation.x, translation.y) .post_translate(translation.x, translation.y)
.post_scale(scale_factor, scale_factor), .post_scale(scale_factor, scale_factor),
Some(clip_mask), clip_mask,
); );
} }
Primitive::Group { primitives } => { Primitive::Group { primitives } => {
@ -403,15 +413,26 @@ impl Backend {
Primitive::Clip { bounds, content } => { Primitive::Clip { bounds, content } => {
let bounds = (*bounds + translation) * scale_factor; let bounds = (*bounds + translation) * scale_factor;
if bounds.x + bounds.width <= 0.0 if bounds == clip_bounds {
|| bounds.y + bounds.height <= 0.0 self.draw_primitive(
|| bounds.x as u32 >= pixels.width() content,
|| bounds.y as u32 >= pixels.height() pixels,
{ clip_mask,
return; bounds,
} scale_factor,
translation,
);
} else if let Some(bounds) = clip_bounds.intersection(&bounds) {
if bounds.x + bounds.width <= 0.0
|| bounds.y + bounds.height <= 0.0
|| bounds.x as u32 >= pixels.width()
|| bounds.y as u32 >= pixels.height()
|| bounds.width <= 1.0
|| bounds.height <= 1.0
{
return;
}
if let Some(bounds) = clip_bounds.intersection(&bounds) {
adjust_clip_mask(clip_mask, pixels, bounds); adjust_clip_mask(clip_mask, pixels, bounds);
self.draw_primitive( self.draw_primitive(
@ -614,11 +635,57 @@ fn adjust_clip_mask(
pixels.height(), pixels.height(),
&path, &path,
tiny_skia::FillRule::EvenOdd, tiny_skia::FillRule::EvenOdd,
true, false,
) )
.expect("Set path of clipping area"); .expect("Set path of clipping area");
} }
fn group_damage(
mut damage: Vec<Rectangle>,
scale_factor: f32,
bounds: Size<u32>,
) -> Vec<Rectangle> {
use std::cmp::Ordering;
const AREA_THRESHOLD: f32 = 20_000.0;
let bounds = Rectangle {
x: 0.0,
y: 0.0,
width: bounds.width as f32,
height: bounds.height as f32,
};
damage.sort_by(|a, b| {
a.x.partial_cmp(&b.x)
.unwrap_or(Ordering::Equal)
.then_with(|| a.y.partial_cmp(&b.y).unwrap_or(Ordering::Equal))
});
let mut output = Vec::new();
let mut scaled = damage
.into_iter()
.filter_map(|region| (region * scale_factor).intersection(&bounds))
.filter(|region| region.width >= 1.0 && region.height >= 1.0);
if let Some(mut current) = scaled.next() {
for region in scaled {
let union = current.union(&region);
if union.area() - current.area() - region.area() <= AREA_THRESHOLD {
current = union;
} else {
output.push(current);
current = region;
}
}
output.push(current);
}
output
}
impl iced_graphics::Backend for Backend { impl iced_graphics::Backend for Backend {
fn trim_measurements(&mut self) { fn trim_measurements(&mut self) {
self.text_pipeline.trim_measurement_cache(); self.text_pipeline.trim_measurement_cache();

View file

@ -336,6 +336,7 @@ struct Cache {
entries: FxHashMap<KeyHash, cosmic_text::Buffer>, entries: FxHashMap<KeyHash, cosmic_text::Buffer>,
recently_used: FxHashSet<KeyHash>, recently_used: FxHashSet<KeyHash>,
hasher: HashBuilder, hasher: HashBuilder,
trim_count: usize,
} }
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
@ -345,11 +346,14 @@ type HashBuilder = twox_hash::RandomXxHashBuilder64;
type HashBuilder = std::hash::BuildHasherDefault<twox_hash::XxHash64>; type HashBuilder = std::hash::BuildHasherDefault<twox_hash::XxHash64>;
impl Cache { impl Cache {
const TRIM_INTERVAL: usize = 300;
fn new() -> Self { fn new() -> Self {
Self { Self {
entries: FxHashMap::default(), entries: FxHashMap::default(),
recently_used: FxHashSet::default(), recently_used: FxHashSet::default(),
hasher: HashBuilder::default(), hasher: HashBuilder::default(),
trim_count: 0,
} }
} }
@ -404,10 +408,16 @@ impl Cache {
} }
fn trim(&mut self) { fn trim(&mut self) {
self.entries if self.trim_count > Self::TRIM_INTERVAL {
.retain(|key, _| self.recently_used.contains(key)); self.entries
.retain(|key, _| self.recently_used.contains(key));
self.recently_used.clear(); self.recently_used.clear();
self.trim_count = 0;
} else {
self.trim_count += 1;
}
} }
} }