Skip to main content

vello_cpu/
util.rs

1// Copyright 2025 the Vello Authors
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3
4use crate::peniko::ImageQuality;
5use vello_common::encode::EncodedImage;
6use vello_common::fearless_simd::{Simd, SimdBase, f32x4, u8x32};
7use vello_common::math::FloatExt;
8use vello_common::tile::Tile;
9use vello_common::util::Div255Ext;
10
11pub(crate) mod scalar {
12    /// Perform an approximate division by 255.
13    ///
14    /// There are three reasons for having this method.
15    /// 1) Divisions are slower than shifting + adding, and the compiler does not seem to replace
16    ///    divisions by 255 with an equivalent (this was verified by benchmarking; doing / 255 was
17    ///    significantly slower).
18    /// 2) Integer divisions are usually not available in SIMD, so this provides a good baseline
19    ///    implementation.
20    /// 3) There are two options for performing the division: One is to perform the division
21    ///    in a way that completely preserves the rounding semantics of a integer division by
22    ///    255. This could be achieved using the implementation `(val + 1 + (val >> 8)) >> 8`.
23    ///    The second approach (used here) has slightly different rounding behavior to a
24    ///    normal division by 255, but is much faster (see <https://github.com/linebender/vello/issues/904>)
25    ///    and therefore preferable for the high-performance pipeline.
26    ///
27    /// Four properties worth mentioning:
28    /// - This actually calculates the ceiling of `val / 256`.
29    /// - Within the allowed range for `val`, rounding errors do not appear for values divisible by 255, i.e. any call `div_255(val * 255)` will always yield `val`.
30    /// - If there is a discrepancy, this division will always yield a value 1 higher than the original.
31    /// - This holds for values of `val` up to and including `65279`. You should not call this function with higher values.
32    #[inline(always)]
33    pub(crate) const fn div_255(val: u16) -> u16 {
34        debug_assert!(
35            val < 65280,
36            "the properties of `div_255` do not hold for values of `65280` or greater"
37        );
38        (val + 255) >> 8
39    }
40
41    #[cfg(test)]
42    mod tests {
43        use crate::util::scalar::div_255;
44
45        #[test]
46        fn div_255_properties() {
47            for i in 0_u16..256 * 255 {
48                let expected = i / 255;
49                let actual = div_255(i);
50
51                assert!(
52                    expected <= actual,
53                    "In case of a discrepancy, the division should yield a value higher than the original."
54                );
55
56                let diff = expected.abs_diff(actual);
57                assert!(diff <= 1, "Rounding error shouldn't be higher than 1.");
58
59                if i % 255 == 0 {
60                    assert_eq!(diff, 0, "Division should be accurate for multiples of 255.");
61                }
62            }
63        }
64    }
65}
66
67pub(crate) trait NormalizedMulExt {
68    fn normalized_mul(self, other: Self) -> Self;
69}
70
71impl<S: Simd> NormalizedMulExt for u8x32<S> {
72    #[inline(always)]
73    fn normalized_mul(self, other: Self) -> Self {
74        let divided = (self.simd.widen_u8x32(self) * other.simd.widen_u8x32(other)).div_255();
75        self.simd.narrow_u16x32(divided)
76    }
77}
78
79pub(crate) trait EncodedImageExt {
80    fn has_skew(&self) -> bool;
81    fn nearest_neighbor(&self) -> bool;
82}
83
84impl EncodedImageExt for EncodedImage {
85    fn has_skew(&self) -> bool {
86        !(self.x_advance.y as f32).is_nearly_zero() || !(self.y_advance.x as f32).is_nearly_zero()
87    }
88
89    fn nearest_neighbor(&self) -> bool {
90        self.sampler.quality == ImageQuality::Low
91    }
92}
93
94pub(crate) trait Premultiply {
95    fn premultiply(self, alphas: Self) -> Self;
96    fn unpremultiply(self, alphas: Self) -> Self;
97}
98
99impl<S: Simd> Premultiply for f32x4<S> {
100    #[inline(always)]
101    fn premultiply(self, alphas: Self) -> Self {
102        self * alphas
103    }
104
105    #[inline(always)]
106    fn unpremultiply(self, alphas: Self) -> Self {
107        let zero = Self::splat(alphas.simd, 0.0);
108        let divided = self / alphas;
109
110        self.simd
111            .select_f32x4(self.simd.simd_eq_f32x4(alphas, zero), zero, divided)
112    }
113}
114
115/// A horizontal span in pixel coordinates.
116#[derive(Debug, Clone, Copy, PartialEq, Eq)]
117#[doc(hidden)]
118pub struct Span {
119    /// The horizontal start position in pixels.
120    x: u16,
121    /// The horizontal span width in pixels.
122    width: u16,
123}
124
125impl Span {
126    /// Creates a span from pixel coordinates.
127    pub fn new(x: u16, width: u16) -> Self {
128        Self { x, width }
129    }
130
131    /// Creates a span from tile coordinates.
132    pub fn new_tile(tile_x: u16, tile_width: u16) -> Self {
133        Self {
134            x: tile_x * Tile::WIDTH,
135            width: tile_width * Tile::WIDTH,
136        }
137    }
138
139    /// Returns the horizontal start position in tile coordinates.
140    pub fn tile_x(self) -> u16 {
141        self.x / Tile::WIDTH
142    }
143
144    /// Returns the exclusive horizontal end position in tile coordinates.
145    pub fn tile_end(self) -> u16 {
146        self.pixel_end().div_ceil(Tile::WIDTH)
147    }
148
149    /// Extends this span to include another span.
150    pub fn extend(&mut self, other: Self) {
151        let x = self.x.min(other.x);
152        let end = self.pixel_end().max(other.pixel_end());
153        *self = Self::new(x, end.saturating_sub(x));
154    }
155
156    /// Returns the intersection of this span with another span.
157    pub fn intersect(self, other: Self) -> Option<Self> {
158        let x = self.x.max(other.x);
159        let end = self.pixel_end().min(other.pixel_end());
160        (x < end).then(|| Self::new(x, end - x))
161    }
162
163    /// Returns the horizontal start position in pixels.
164    pub fn pixel_x(self) -> u16 {
165        self.x
166    }
167
168    /// Returns the horizontal span width in pixels.
169    pub fn pixel_width(self) -> u16 {
170        self.width
171    }
172
173    /// Returns the exclusive horizontal end position in pixels.
174    pub fn pixel_end(self) -> u16 {
175        self.pixel_x().saturating_add(self.pixel_width())
176    }
177}