From fb79564f2ae2fc020e855bdbdffb5c5b79b8e570 Mon Sep 17 00:00:00 2001 From: MikeeI Date: Mon, 5 Oct 2026 07:00:44 +0200 Subject: [PATCH] perf(compose): remove redundant effect application halos (#110) Keep full-region neighbourhood effect maps while rendering content only for the output rectangle. Compare complete tiled output at U8, U16 and F32 depths rather than three sampled pixels. --- crates/compose/src/lib.rs | 10 +++++----- crates/compose/src/tests.rs | 18 +++++++++++++----- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/crates/compose/src/lib.rs b/crates/compose/src/lib.rs index d18b1648..c9d866a6 100644 --- a/crates/compose/src/lib.rs +++ b/crates/compose/src/lib.rs @@ -915,9 +915,9 @@ fn composite_layer_plain(layer: &Layer, clipped: &[Layer], backdrop: &mut Buffer } if effects::has_effects(layer) { - // Effects reach beyond the render rect: render the layer larger. - let big = rect.inflate(effects::margin(layer)); - let Some(mut content) = render_content(layer, big, cx) else { return }; + // Neighbourhoods are already captured by the full-region effect maps; + // content and effect application only need the output rectangle. + let Some(mut content) = render_content(layer, rect, cx) else { return }; for c in clipped.iter().filter(|c| c.visible) { composite_atop(c, &mut content, cx); } @@ -1164,8 +1164,8 @@ fn composite_atop_any(layer: &Layer, base: &mut Buffer, cx: &Ctx) { if effects::has_effects(layer) { // Effects of a clipped layer are clipped to the base too: render // them over the base (treated as opaque) and keep the base's alpha. - let big = rect.inflate(effects::margin(layer)); - let Some(content) = render_content(layer, big, cx) else { return }; + // The full-region effect maps already capture their neighbourhoods. + let Some(content) = render_content(layer, rect, cx) else { return }; let mut opaque = Buffer { rect, px: base.px.iter().map(|p| [p[0], p[1], p[2], 1.0]).collect() }; let maps = effect_maps(layer, cx); effects::composite_with_effects( diff --git a/crates/compose/src/tests.rs b/crates/compose/src/tests.rs index 2971b4dc..f451fe8a 100644 --- a/crates/compose/src/tests.rs +++ b/crates/compose/src/tests.rs @@ -600,11 +600,19 @@ fn group_effects_apply_to_group_shape() { #[test] fn effects_render_identically_in_tiles() { - let d = fx_doc(vec![Effect::DropShadow(Shadow { spread: 0.0, size: 5.0, ..shadow(4.0, 120.0) }), stroke(2.0, StrokePosition::Outside)]); - let full = flatten(&d); - for (x, y) in [(33, 33), (8, 20), (31, 12)] { - let t = render(&d, Rect::from_xywh(x, y, 1, 1)).px[0]; - assert!(close4(t, full.get(x, y)), "({x},{y})"); + for depth in [SampleType::U8, SampleType::U16, SampleType::F32] { + let mut d = fx_doc(vec![Effect::DropShadow(Shadow { spread: 0.0, size: 5.0, ..shadow(4.0, 120.0) }), stroke(2.0, StrokePosition::Outside)]); + d.depth = depth; + for layer in &mut d.layers { + if let Some(surface) = layer.surface_mut() { + *surface = surface.convert(photocraft_color::PixelFormat { sample: depth, ..surface.format() }); + } + } + let full = flatten(&d); + // Compare every boundary pixel: a few sampled points miss application-halo regressions. + for tile in [1, 7, 33] { + assert_eq!(render_tiled(&d, d.bounds(), tile), full, "{depth:?}, tile {tile}"); + } } }