vello_cpu/fine/lowp/
gradient.rs

1// Copyright 2025 the Vello Authors
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3
4use crate::peniko;
5use core::slice::ChunksExact;
6use vello_common::encode::EncodedGradient;
7use vello_common::fearless_simd::*;
8
9/// An accelerated gradient painter for u8.
10///
11/// Assumes that the gradient has no undefined positions.
12#[derive(Debug)]
13pub(crate) struct GradientPainter<'a, S: Simd> {
14    gradient: &'a EncodedGradient,
15    lut: &'a [[u8; 4]],
16    t_vals: ChunksExact<'a, f32>,
17    scale_factor: f32x16<S>,
18    simd: S,
19}
20
21impl<'a, S: Simd> GradientPainter<'a, S> {
22    pub(crate) fn new(simd: S, gradient: &'a EncodedGradient, t_vals: &'a [f32]) -> Self {
23        let lut = gradient.u8_lut(simd);
24        let scale_factor = f32x16::splat(simd, lut.scale_factor());
25
26        Self {
27            gradient,
28            scale_factor,
29            lut: lut.lut(),
30            t_vals: t_vals.chunks_exact(16),
31            simd,
32        }
33    }
34}
35
36impl<S: Simd> Iterator for GradientPainter<'_, S> {
37    type Item = u8x64<S>;
38
39    #[inline(always)]
40    fn next(&mut self) -> Option<Self::Item> {
41        let extend = self.gradient.extend;
42        let pos = f32x16::from_slice(self.simd, self.t_vals.next()?);
43        let t_vals = apply_extend(pos, extend);
44        let indices = (t_vals * self.scale_factor).cvt_u32();
45
46        let mut vals = [0_u8; 64];
47        for (val, idx) in vals.chunks_exact_mut(4).zip(indices.val) {
48            val.copy_from_slice(&self.lut[idx as usize]);
49        }
50
51        Some(u8x64::from_slice(self.simd, &vals))
52    }
53}
54
55impl<S: Simd> crate::fine::Painter for GradientPainter<'_, S> {
56    fn paint_u8(&mut self, buf: &mut [u8]) {
57        for chunk in buf.chunks_exact_mut(64) {
58            chunk.copy_from_slice(&self.next().unwrap().val);
59        }
60    }
61
62    fn paint_f32(&mut self, _: &mut [f32]) {
63        unimplemented!()
64    }
65}
66
67// TODO: Maybe delete this method and use `apply_extend` from highp by splitting into two f32x8.
68#[inline(always)]
69pub(crate) fn apply_extend<S: Simd>(val: f32x16<S>, extend: peniko::Extend) -> f32x16<S> {
70    match extend {
71        peniko::Extend::Pad => val.max(0.0).min(1.0),
72        peniko::Extend::Repeat => (val - val.floor()).fract(),
73        // See <https://github.com/google/skia/blob/220738774f7a0ce4a6c7bd17519a336e5e5dea5b/src/opts/SkRasterPipeline_opts.h#L6472-L6475>
74        peniko::Extend::Reflect => ((val - 1.0) - 2.0 * ((val - 1.0) * 0.5).floor() - 1.0)
75            .abs()
76            .max(0.0)
77            .min(1.0),
78    }
79}