vello_gpu: keep culled destructive layers so they still clear their parent
diff --git a/vello_gpu/src/schedule/mod.rs b/vello_gpu/src/schedule/mod.rs index 103abd8..e8d7968 100644 --- a/vello_gpu/src/schedule/mod.rs +++ b/vello_gpu/src/schedule/mod.rs
@@ -494,18 +494,9 @@ let layer = &self.recorder.layers[layer_id as usize]; - // TODO: Change recording so layers with empty bboxes are not emitted in the first place. - let mut bbox = if layer.bbox.is_empty() { - if layer.props.blend_mode.is_destructive() { - // Unlike in the non-destructive case, empty *destructive* layers are - // not a no-op, and we need to clear the whole parent region. - parent_bbox - } else { - return Ok(None); - } - } else { - layer.bbox - }; + let clip_bbox = layer.props.clip_path.as_ref().map(|clip| clip.bbox); + let is_destructive = layer.props.blend_mode.is_destructive(); + let mut bbox = layer.bbox; // We cannot do this for filter layers, because the filter needs to be applied to the whole // region _BEFORE_ applying clips. Instead, the clip is simply applied when sampling from @@ -513,22 +504,26 @@ // bbox and the part of the parent that is drawn to. match &layer.kind { RecordedLayerKind::Regular => { - bbox = layer - .props - .clip_path - .as_ref() - .map_or(bbox, |clip| bbox.intersect(clip.bbox)) + bbox = clip_bbox + .map_or(bbox, |clip| bbox.intersect(clip)) .intersect(parent_bbox); } RecordedLayerKind::Filter { placement, .. } => { // TODO: Also constrain filter layers to the input region needed for the visible // part of the parent. - if placement.dest_bbox().intersect(parent_bbox).is_empty() { + if !is_destructive && placement.dest_bbox().intersect(parent_bbox).is_empty() { return Ok(None); } } } + // Unlike in the non-destructive case, empty *destructive* layers are not a no-op, and we + // need to clear the parent region within the clip. + if bbox.is_empty() && is_destructive { + bbox = clip_bbox.map_or(parent_bbox, |clip| parent_bbox.intersect(clip)); + } + + // TODO: Change recording so layers with empty bboxes are not emitted in the first place. if bbox.is_empty() { return Ok(None); }
diff --git a/vello_tests/snapshots/compose_clear_empty_filter_layer.png b/vello_tests/snapshots/compose_clear_empty_filter_layer.png new file mode 100644 index 0000000..ac3451d --- /dev/null +++ b/vello_tests/snapshots/compose_clear_empty_filter_layer.png
@@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:56f57423e0ff37d0284ae02f3dfa6ceffad893302f86b5aa4d17dabb5ba1b25d +size 82
diff --git a/vello_tests/snapshots/compose_copy_outside_parent_clip_clears_parent.png b/vello_tests/snapshots/compose_copy_outside_parent_clip_clears_parent.png new file mode 100644 index 0000000..ac3451d --- /dev/null +++ b/vello_tests/snapshots/compose_copy_outside_parent_clip_clears_parent.png
@@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:56f57423e0ff37d0284ae02f3dfa6ceffad893302f86b5aa4d17dabb5ba1b25d +size 82
diff --git a/vello_tests/snapshots/offset_destructive_filter_layer_outside_target.png b/vello_tests/snapshots/offset_destructive_filter_layer_outside_target.png new file mode 100644 index 0000000..a351311 --- /dev/null +++ b/vello_tests/snapshots/offset_destructive_filter_layer_outside_target.png
@@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5df0da52aaba923344202c1e30e377e11cce5ceef303d4dc1c460078637e908c +size 98
diff --git a/vello_tests/tests/compose.rs b/vello_tests/tests/compose.rs index f9a9164..9f69811 100644 --- a/vello_tests/tests/compose.rs +++ b/vello_tests/tests/compose.rs
@@ -3,6 +3,7 @@ use crate::renderer::Renderer; use vello_common::color::palette::css::{BLUE, GREEN, LIME, PURPLE, RED, YELLOW}; +use vello_common::filter_effects::{Filter, FilterPrimitive}; use vello_common::kurbo::{Affine, Circle, Point, Rect, Shape}; use vello_common::peniko::{BlendMode, Color, Compose, Mix}; use vello_dev_macros::vello_test; @@ -467,3 +468,34 @@ fn compose_non_isolated_plus(ctx: &mut impl Renderer) { compose_non_isolated(ctx, Compose::Plus); } + +#[vello_test] +fn compose_copy_outside_parent_clip_clears_parent(ctx: &mut impl Renderer) { + ctx.push_clip_layer(&Rect::new(0.0, 0.0, 50.0, 50.0).to_path(0.1)); + ctx.set_paint(BLUE); + ctx.fill_rect(&Rect::new(0.0, 0.0, 100.0, 100.0)); + ctx.push_blend_layer(BlendMode::new(Mix::Normal, Compose::Copy)); + ctx.set_paint(RED); + ctx.fill_rect(&Rect::new(60.0, 60.0, 90.0, 90.0)); + ctx.pop_layer(); + ctx.pop_layer(); +} + +#[vello_test(skip_multithreaded)] +fn compose_clear_empty_filter_layer(ctx: &mut impl Renderer) { + ctx.push_layer(None, None, None, None, None); + ctx.set_paint(BLUE); + ctx.fill_rect(&Rect::new(10.0, 10.0, 90.0, 90.0)); + ctx.push_layer( + None, + Some(BlendMode::new(Mix::Normal, Compose::Clear)), + None, + None, + Some(Filter::from_primitive(FilterPrimitive::Offset { + dx: 0.0, + dy: 0.0, + })), + ); + ctx.pop_layer(); + ctx.pop_layer(); +}
diff --git a/vello_tests/tests/offset.rs b/vello_tests/tests/offset.rs index e173abb..d545cf3 100644 --- a/vello_tests/tests/offset.rs +++ b/vello_tests/tests/offset.rs
@@ -183,3 +183,23 @@ ctx.fill_path(&hole); ctx.pop_layer(); } + +#[vello_test(skip_multithreaded)] +fn offset_destructive_filter_layer_outside_target(ctx: &mut impl Renderer) { + ctx.set_offset((-50, 0)); + ctx.set_paint(BLUE); + ctx.fill_rect(&Rect::new(0.0, 0.0, 100.0, 100.0)); + ctx.push_layer( + None, + Some(BlendMode::new(Mix::Normal, Compose::Copy)), + None, + None, + Some(Filter::from_primitive(FilterPrimitive::GaussianBlur { + std_deviation: 2.0, + edge_mode: EdgeMode::None, + })), + ); + ctx.set_paint(RED); + ctx.fill_rect(&Rect::new(5.0, 5.0, 25.0, 25.0)); + ctx.pop_layer(); +}