Skip to main content

vello_cpu/fine/highp/
compose.rs

1// Copyright 2025 the Vello Authors
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3
4use crate::fine::Splat4thExt;
5use crate::peniko::{BlendMode, Compose};
6use vello_common::fearless_simd::*;
7
8pub(crate) trait ComposeExt {
9    fn compose<S: Simd>(
10        &self,
11        simd: S,
12        src_c: f32x16<S>,
13        bg_c: f32x16<S>,
14        alpha_mask: Option<f32x16<S>>,
15    ) -> f32x16<S>;
16}
17
18impl ComposeExt for BlendMode {
19    fn compose<S: Simd>(
20        &self,
21        simd: S,
22        src_c: f32x16<S>,
23        bg_c: f32x16<S>,
24        alpha_mask: Option<f32x16<S>>,
25    ) -> f32x16<S> {
26        // There some non-obvious subtleties worth highlighting here.
27        // We support two kinds of blending (in this case, we focus on compositing specifically):
28        // - Isolated blending, where layers as a whole are blended together with their backdrop.
29        //   If we are currently performing this kind of blending, `alpha_mask` will always be `None`.
30        //   After all, there is no concrete shape opacity associated with a layer. Instead, we are
31        //   just compositing the RGBA values at _all_ positions of the source layer with the backdrop
32        //   layer. For example, if the backdrop contains a green rectangle and source layer is just
33        //   empty, if we perform blending with `Compose::Clear`, then _everything_ will be cleared,
34        //   because we are compositing the whole source layer with the whole backdrop, and not
35        //   just the parts of the source layer that have actually be drawn on.
36        // - Non-isolated blending, where a single path is blended with the backdrop. In this case,
37        //   `alpha_mask` _might_ be `Some` and contain the alpha values of the strips we are currently
38        //   compositing. Remember that strips always have a fixed height of 4, because of this, the
39        //   strips might cover areas that aren't actually covered by the path (and just have an alpha
40        //   value of 0, or a value between 0-254 for anti-aliased parts). Because of this, for
41        //   non-isolated blending, we need to lerp the result with the backdrop using `alpha_mask`.
42
43        let mut res = match self.compose {
44            Compose::SrcOver => SrcOver::compose(simd, src_c, bg_c),
45            Compose::Clear => Clear::compose(simd, src_c, bg_c),
46            Compose::Copy => Copy::compose(simd, src_c, bg_c),
47            Compose::DestOver => DestOver::compose(simd, src_c, bg_c),
48            Compose::Dest => Dest::compose(simd, src_c, bg_c),
49            Compose::SrcIn => SrcIn::compose(simd, src_c, bg_c),
50            Compose::DestIn => DestIn::compose(simd, src_c, bg_c),
51            Compose::SrcOut => SrcOut::compose(simd, src_c, bg_c),
52            Compose::DestOut => DestOut::compose(simd, src_c, bg_c),
53            Compose::SrcAtop => SrcAtop::compose(simd, src_c, bg_c),
54            Compose::DestAtop => DestAtop::compose(simd, src_c, bg_c),
55            Compose::Xor => Xor::compose(simd, src_c, bg_c),
56            Compose::Plus => Plus::compose(simd, src_c, bg_c),
57            // Have not been able to find a formula for this, so just fallback to Plus.
58            Compose::PlusLighter => Plus::compose(simd, src_c, bg_c),
59        };
60
61        if let Some(alpha_mask) = alpha_mask {
62            let alpha_mask_inv = 1.0 - alpha_mask;
63            res = alpha_mask * res + alpha_mask_inv * bg_c;
64        }
65
66        res
67    }
68}
69
70macro_rules! compose {
71    ($name:ident, $fa:expr, $fb:expr, $sat:expr) => {
72        struct $name;
73
74        impl $name {
75            fn compose<S: Simd>(simd: S, src_c: f32x16<S>, bg_c: f32x16<S>) -> f32x16<S> {
76                let al_b = bg_c.splat_4th();
77                let al_s = src_c.splat_4th();
78
79                let fa = $fa(simd, al_s, al_b);
80                let fb = $fb(simd, al_s, al_b);
81
82                if $sat {
83                    (src_c * fa + fb * bg_c)
84                        .min(f32x16::splat(simd, 1.0))
85                        .max(f32x16::splat(simd, 0.0))
86                } else {
87                    src_c * fa + fb * bg_c
88                }
89            }
90        }
91    };
92}
93
94compose!(
95    Clear,
96    |simd, _, _| f32x16::splat(simd, 0.0),
97    |simd, _, _| f32x16::splat(simd, 0.0),
98    false
99);
100compose!(
101    Copy,
102    |simd, _, _| f32x16::splat(simd, 1.0),
103    |simd, _, _| f32x16::splat(simd, 0.0),
104    false
105);
106compose!(
107    SrcOver,
108    |simd, _, _| f32x16::splat(simd, 1.0),
109    |_, al_s: f32x16<S>, _| 1.0 - al_s,
110    false
111);
112compose!(
113    DestOver,
114    |_, _, al_b: f32x16<S>| 1.0 - al_b,
115    |simd, _, _| f32x16::splat(simd, 1.0),
116    false
117);
118compose!(
119    Dest,
120    |simd, _, _| f32x16::splat(simd, 0.0),
121    |simd, _, _| f32x16::splat(simd, 1.0),
122    false
123);
124compose!(
125    Xor,
126    |_, _, al_b: f32x16<S>| 1.0 - al_b,
127    |_, al_s: f32x16<S>, _| 1.0 - al_s,
128    false
129);
130compose!(
131    SrcIn,
132    |_, _, al_b: f32x16<S>| al_b,
133    |simd, _, _| f32x16::splat(simd, 0.0),
134    false
135);
136compose!(
137    DestIn,
138    |simd, _, _| f32x16::splat(simd, 0.0),
139    |_, al_s: f32x16<S>, _| al_s,
140    false
141);
142compose!(
143    SrcOut,
144    |_, _, al_b: f32x16<S>| 1.0 - al_b,
145    |simd, _, _| f32x16::splat(simd, 0.0),
146    false
147);
148compose!(
149    DestOut,
150    |simd, _, _| f32x16::splat(simd, 0.0),
151    |_, al_s: f32x16<S>, _| 1.0 - al_s,
152    false
153);
154compose!(
155    SrcAtop,
156    |_, _, al_b: f32x16<S>| al_b,
157    |_, al_s: f32x16<S>, _| 1.0 - al_s,
158    false
159);
160compose!(
161    DestAtop,
162    |_, _, al_b: f32x16<S>| 1.0 - al_b,
163    |_, al_s: f32x16<S>, _| al_s,
164    false
165);
166compose!(
167    Plus,
168    |simd, _, _| f32x16::splat(simd, 1.0),
169    |simd, _, _| f32x16::splat(simd, 1.0),
170    true
171);