Group damage regions by area increase
This commit is contained in:
parent
6270c33ed9
commit
f8cd1faa28
4 changed files with 199 additions and 108 deletions
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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(®ion) 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(®ion);
|
||||||
|
|
||||||
|
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();
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue