Merge pull request #1630 from samhyun/perf/partial-damage-margin

M8: an edit renders where the changed layer paints, its effects' margin once
This commit is contained in:
BFlat authored and GitHub committed 2026-10-30 15:07:13 +07:00
commit e94350a778
2 files changed
+37 -6

No files matched your search

+5 -4
View File
@@ -550,14 +550,15 @@ pub fn effect_margin(n: &Node) -> f64 {
layer + others
}
/// Everything node `id` can paint, in page coordinates: its paint bounds (glyphs of overflowing
/// text included) grown by its effects' margin. What a change to the node can affect on screen.
/// Everything node `id` can paint, in page coordinates: its paint bounds, effects and glyphs of
/// overflowing text included. What a change to the node can affect on screen. Its effects' margin
/// (what they read from outside, see [`effect_margin`]) isn't added again: drawing the area
/// takes in what lies that far off, as culling does.
pub fn paint_bounds(doc: &Document, id: NodeId) -> Option<Rect> {
let n = doc.node(id)?;
let m = effect_margin(n).abs();
let world = doc.world_transform(id);
let local = local_paint_bounds(doc, n, world, 0, None);
Some(figcraft_geom::transform_rect_bbox(world, local).inflate(m, m))
Some(figcraft_geom::transform_rect_bbox(world, local))
}
/// The local → page map of a layer drawn at `xf` (local → device): the view's scale taken out.
+32 -2
View File
@@ -188,9 +188,9 @@ fn edits_under_and_beside_effects_match() {
let beside = edit(&across, |d| d.get_mut(s.rect).unwrap().transform = Affine::translate((10.0, 110.0)));
assert!(partial(check(&mut r, &beside, s.page, &v)));
// A child of a group with a drop shadow: the shadow moves too, past the group's box with the
// child (a view large enough that this stays a part of it).
// child.
let g1 = s.doc.find_by_name("G1", None)[0];
let v = View { origin: (260.0, 0.0), scale: 1.0, width: 400, height: 300 };
let v = View { origin: (260.0, 0.0), scale: 1.0, width: 300, height: 220 };
check(&mut r, &beside, s.page, &v);
let shadowed = edit(&beside, |d| d.get_mut(g1).unwrap().transform = Affine::translate((30.0, 20.0)));
assert!(partial(check(&mut r, &shadowed, s.page, &v)));
@@ -448,6 +448,36 @@ fn effect_margins_too_large_to_add_stay_large() {
assert_eq!(m, 2.0 * crate::draw::MAX_REACH);
}
#[test]
fn a_moved_layer_renders_where_its_effects_paint_and_no_further() {
fonts();
let shadow = |dy: f64, r: f64| Effect {
kind: EffectKind::DropShadow(Shadow { color: Color::BLACK, offset: (0.0, dy), radius: r, ..Shadow::default() }),
..Effect::drop_shadow()
};
let blur = Effect { kind: EffectKind::LayerBlur(figcraft_doc::Blur::uniform(30.0)), ..Effect::layer_blur() };
for (why, effects) in [("a shadow falling 40", vec![shadow(40.0, 8.0)]), ("a blur of 30", vec![blur])] {
let mut d = Document::new("moved");
let p = d.first_page().unwrap();
let id = d.create(p, NodeKind::Rectangle, "R", 300.0, 200.0, 120.0, 120.0).unwrap();
solid(d.node_mut(id).unwrap(), "3366FF");
d.node_mut(id).unwrap().effects = effects;
let before = Arc::new(d);
let after = edit(&before, |d| d.get_mut(id).unwrap().transform = Affine::translate((315.0, 210.0)));
let v = View { origin: (0.0, 0.0), scale: 1.0, width: 1000, height: 800 };
let mut r = Retained::default();
check(&mut r, &before, p, &v);
// Where it painted and where it paints now, a pixel round each for rounding out; a quarter
// more, as layers grow by a shadow's reach on every side, not only towards its offset.
let area = |doc: &Document| doc.world_render_bounds(id).map_or(0.0, |b| (b.width() + 2.0) * (b.height() + 2.0));
let most = (area(&before) + area(&after)) * 1.25;
match check(&mut r, &after, p, &v) {
Work::Partial { pixels } => assert!(pixels as f64 <= most, "{why}: {pixels} pixels rendered, where it paints is {most}"),
w => panic!("{why}: {w:?}"),
}
}
}
/// Every channel of `a` within `tol` of the same channel of `b` from row `rows_down` on.
fn assert_within(a: &Pixmap, b: &Pixmap, rows_down: u32, tol: u8, what: &str) {
assert_eq!(a.width(), b.width(), "{what}: widths");