vello_cpu/fine/common/gradient/
radial.rs

1// Copyright 2025 the Vello Authors
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3
4use crate::fine::common::gradient::SimdGradientKind;
5use vello_common::encode::{FocalData, RadialKind};
6use vello_common::fearless_simd::{Simd, SimdBase, f32x8};
7
8pub(crate) enum SimdRadialKindInner<S: Simd> {
9    Radial {
10        bias: f32x8<S>,
11        scale: f32x8<S>,
12    },
13    Strip {
14        scaled_r0_squared: f32x8<S>,
15    },
16    Focal {
17        focal_data: FocalData,
18        fp0: f32x8<S>,
19        fp1: f32x8<S>,
20    },
21}
22
23pub(crate) struct SimdRadialKind<S: Simd> {
24    inner: SimdRadialKindInner<S>,
25}
26
27impl<S: Simd> SimdRadialKind<S> {
28    pub(crate) fn new(simd: S, kind: &RadialKind) -> Self {
29        let inner = match kind {
30            RadialKind::Radial { bias, scale } => SimdRadialKindInner::Radial {
31                bias: f32x8::splat(simd, *bias),
32                scale: f32x8::splat(simd, *scale),
33            },
34            RadialKind::Strip { scaled_r0_squared } => SimdRadialKindInner::Strip {
35                scaled_r0_squared: f32x8::splat(simd, *scaled_r0_squared),
36            },
37            RadialKind::Focal {
38                focal_data,
39                fp0,
40                fp1,
41            } => SimdRadialKindInner::Focal {
42                fp0: f32x8::splat(simd, *fp0),
43                fp1: f32x8::splat(simd, *fp1),
44                focal_data: *focal_data,
45            },
46        };
47
48        Self { inner }
49    }
50}
51
52impl<S: Simd> SimdGradientKind<S> for SimdRadialKind<S> {
53    #[inline(always)]
54    fn cur_pos(&self, x_pos: f32x8<S>, y_pos: f32x8<S>) -> f32x8<S> {
55        let simd = x_pos.simd;
56
57        match &self.inner {
58            SimdRadialKindInner::Radial { bias, scale } => {
59                let radius = (x_pos * x_pos + y_pos * y_pos).sqrt();
60
61                *bias + radius * *scale
62            }
63            SimdRadialKindInner::Strip { scaled_r0_squared } => {
64                let p1 = *scaled_r0_squared - y_pos * y_pos;
65
66                let mask = simd.simd_lt_f32x8(p1, f32x8::splat(simd, 0.0));
67                simd.select_f32x8(mask, f32x8::splat(simd, f32::NAN), x_pos + p1.sqrt())
68            }
69            SimdRadialKindInner::Focal {
70                focal_data,
71                fp0,
72                fp1,
73            } => {
74                let mut t = if focal_data.is_focal_on_circle() {
75                    x_pos + y_pos * y_pos / x_pos
76                } else if focal_data.is_well_behaved() {
77                    (x_pos * x_pos + y_pos * y_pos).sqrt() - x_pos * *fp0
78                } else if focal_data.is_swapped() || (1.0 - focal_data.f_focal_x < 0.0) {
79                    f32x8::splat(simd, -1.0) * (x_pos * x_pos - y_pos * y_pos).sqrt() - x_pos * *fp0
80                } else {
81                    (x_pos * x_pos - y_pos * y_pos).sqrt() - x_pos * *fp0
82                };
83
84                if !focal_data.is_well_behaved() {
85                    // Radii < 0 should be masked out, too.
86                    let is_degenerate = simd.simd_le_f32x8(t, f32x8::splat(simd, 0.0));
87
88                    t = simd.select_f32x8(is_degenerate, f32x8::splat(simd, f32::NAN), t);
89                }
90
91                if 1.0 - focal_data.f_focal_x < 0.0 {
92                    t = f32x8::splat(simd, -1.0) * t;
93                }
94
95                if !focal_data.is_natively_focal() {
96                    t += *fp1;
97                }
98
99                if focal_data.is_swapped() {
100                    t = f32x8::splat(simd, 1.0) - t;
101                }
102
103                t
104            }
105        }
106    }
107}