linebender / linebender/vello

`Mix::Clip` doesn't interact properly with blends

Open
#1,198 0 comments 0 reactions 1 assignee View on GitHub

@DJMcNab is already working on this.

Since Sep 8, 2025.

  • #1202 by @dfrg — open
C-research
Dominant language
Rust
Stars
4.3k
Forks
300
Avg merge
2d 13h
Merged PRs (30d)
61

Description

`Mix::Clip` (or if we get #1192, `add_clip_layer`) [is defined as](https://docs.rs/peniko/0.4.0/peniko/enum.Mix.html#variant.Clip):

> `Clip` is the same as `Normal`, but the latter always creates an isolated blend group and the former can optimize that out.

To validate this, I created the following test scene:

Code for example (long)

```rust
pub(super) fn clipped_blend(scene: &mut Scene, params: &mut SceneParams<'_>) {
params.resolution = Some(Vec2::new(1000., 1200.));
let transform = Affine::translate((10., 100.));
params.text.add_run(
&mut *scene,
None,
32.,
Color::WHITE,
transform.then_translate((10., -30.).into()),
None,
&Style::Fill(Fill::EvenOdd),
"No Clip",
);

scene.fill(
Fill::EvenOdd,
transform,
palette::css::BLUE,
None,
&Rect::from_origin_size((0., 0.), (400., 400.)),
);
scene.push_layer(
Mix::Multiply,
1.0,
transform,
&Triangle::from_coords((200., 0.), (0., 400.), (400., 400.)),
);
scene.fill(
Fill::EvenOdd,
transform,
palette::css::AQUAMARINE,
None,
&Rect::from_origin_size((0., 0.), (400., 400.)),
);
scene.pop_layer();

let transform = Affine::translate((0., 600.));
params.text.add_run(
&mut *scene,
None,
32.,
Color::WHITE,
transform.then_translate((10., -30.).into()),
None,
&Style::Fill(Fill::EvenOdd),
"Mix::Normal",
);

scene.fill(
Fill::EvenOdd,
transform,
palette::css::BLUE,
None,
&Rect::from_origin_size((0., 0.), (400., 400.)),
);
let layer_shape = Triangle::from_coords((200., 0.), (0., 400.), (400., 400.));
scene.push_layer(Mix::Normal, 1.0, transform, &layer_shape);
scene.push_layer(Mix::Multiply, 1.0, transform, &layer_shape);
scene.fill(
Fill::EvenOdd,
transform,
palette::css::AQUAMARINE,
None,
&Rect::from_origin_size((0., 0.), (400., 400.)),
);
scene.pop_layer();
scene.pop_layer();

let transform = Affine::translate((500., 600.));
params.text.add_run(
&mut *scene,
None,
32.,
Color::WHITE,
transform.then_translate((10., -30.).into()),
None,
&Style::Fill(Fill::EvenOdd),
"Mix::Clip",
);

scene.fill(
Fill::EvenOdd,
transform,
palette::css::BLUE,
None,
&Rect::from_origin_size((0., 0.), (400., 400.)),
);
let layer_shape = Triangle::from_coords((200., 0.), (0., 400.), (400., 400.));
scene.push_layer(Mix::Clip, 1.0, transform, &layer_shape);
scene.push_layer(Mix::Multiply, 1.0, transform, &layer_shape);
scene.fill(
Fill::EvenOdd,
transform,
palette::css::AQUAMARINE,
None,
&Rect::from_origin_size((0., 0.), (400., 400.)),
);
scene.pop_layer();
scene.pop_layer();
}
```


This shows `Mix::Clip` followed by a `Multiply` blend, and both of the two reasonable rendering results from the blends; either the same as there not being a clip, or it being equivalent to `Mix::Normal`.
This however renders as:

Three triangles, the top one labelled No Clip is Dark Blue on Lighter Blue, the bottom left one labelled Mix::Normal is Aquamarine on lighter Blue, and the bottom right one labelled Mix::Clip is Dark blue with a blocky aquamarine border on lighter blue.

This is discussed in [#vello > Mix::Clip optimisation: Interaction with layers](https://xi.zulipchat.com/#narrow/channel/197075-vello/topic/Mix.3A.3AClip.20optimisation.3A.20Interaction.20with.20layers/with/538168481). Essentially, the fixes look like making the Clip act the same as No Clip, which likely requires us to implement non-isolated blending. For clip layers in particular, that's pretty easy, although the general case of non-isolated blending is pretty gnarly because the web compositing spec is pretty lax about properly specifying the semantics.

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.