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.
This commit is contained in:
MikeeI 2026-10-05 07:00:44 +02:00 committed by GitHub
parent a65b5ca80f
commit fb79564f2a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 18 additions and 10 deletions

View File

@ -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(

View File

@ -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}");
}
}
}