vello_cpu/dispatch/
single_threaded.rs

1// Copyright 2025 the Vello Authors
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3
4use crate::RenderMode;
5use crate::dispatch::Dispatcher;
6use crate::fine::{F32Kernel, Fine, FineKernel, U8Kernel};
7use crate::kurbo::{Affine, BezPath, Stroke};
8use crate::peniko::{BlendMode, Fill};
9use crate::region::Regions;
10use vello_common::coarse::{MODE_CPU, Wide};
11use vello_common::encode::EncodedPaint;
12use vello_common::fearless_simd::{Level, Simd, dispatch};
13use vello_common::mask::Mask;
14use vello_common::paint::Paint;
15use vello_common::strip::Strip;
16use vello_common::strip_generator::{StripGenerator, StripStorage};
17
18#[derive(Debug)]
19pub(crate) struct SingleThreadedDispatcher {
20    wide: Wide,
21    strip_generator: StripGenerator,
22    strip_storage: StripStorage,
23    level: Level,
24}
25
26impl SingleThreadedDispatcher {
27    pub(crate) fn new(width: u16, height: u16, level: Level) -> Self {
28        let wide = Wide::<MODE_CPU>::new(width, height);
29        let strip_generator = StripGenerator::new(width, height, level);
30        let strip_storage = StripStorage::default();
31
32        Self {
33            wide,
34            strip_generator,
35            strip_storage,
36            level,
37        }
38    }
39
40    fn rasterize_f32(
41        &self,
42        buffer: &mut [u8],
43        width: u16,
44        height: u16,
45        encoded_paints: &[EncodedPaint],
46    ) {
47        dispatch!(self.level, simd => self.rasterize_with::<_, F32Kernel>(simd, buffer, width, height, encoded_paints));
48    }
49
50    fn rasterize_u8(
51        &self,
52        buffer: &mut [u8],
53        width: u16,
54        height: u16,
55        encoded_paints: &[EncodedPaint],
56    ) {
57        dispatch!(self.level, simd => self.rasterize_with::<_, U8Kernel>(simd, buffer, width, height, encoded_paints));
58    }
59
60    fn rasterize_with<S: Simd, F: FineKernel<S>>(
61        &self,
62        simd: S,
63        buffer: &mut [u8],
64        width: u16,
65        height: u16,
66        encoded_paints: &[EncodedPaint],
67    ) {
68        let mut buffer = Regions::new(width, height, buffer);
69        let mut fine = Fine::<S, F>::new(simd);
70
71        buffer.update_regions(|region| {
72            let x = region.x;
73            let y = region.y;
74
75            let wtile = self.wide.get(x, y);
76            fine.set_coords(x, y);
77
78            fine.clear(wtile.bg);
79            for cmd in &wtile.cmds {
80                fine.run_cmd(cmd, &self.strip_storage.alphas, encoded_paints);
81            }
82
83            fine.pack(region);
84        });
85    }
86}
87
88impl Dispatcher for SingleThreadedDispatcher {
89    fn wide(&self) -> &Wide {
90        &self.wide
91    }
92
93    fn fill_path(
94        &mut self,
95        path: &BezPath,
96        fill_rule: Fill,
97        transform: Affine,
98        paint: Paint,
99        aliasing_threshold: Option<u8>,
100    ) {
101        let wide = &mut self.wide;
102
103        self.strip_generator.generate_filled_path(
104            path,
105            fill_rule,
106            transform,
107            aliasing_threshold,
108            &mut self.strip_storage,
109        );
110
111        wide.generate(&self.strip_storage.strips, paint, 0);
112    }
113
114    fn stroke_path(
115        &mut self,
116        path: &BezPath,
117        stroke: &Stroke,
118        transform: Affine,
119        paint: Paint,
120        aliasing_threshold: Option<u8>,
121    ) {
122        let wide = &mut self.wide;
123
124        self.strip_generator.generate_stroked_path(
125            path,
126            stroke,
127            transform,
128            aliasing_threshold,
129            &mut self.strip_storage,
130        );
131
132        wide.generate(&self.strip_storage.strips, paint, 0);
133    }
134
135    fn push_layer(
136        &mut self,
137        clip_path: Option<&BezPath>,
138        fill_rule: Fill,
139        clip_transform: Affine,
140        blend_mode: BlendMode,
141        opacity: f32,
142        aliasing_threshold: Option<u8>,
143        mask: Option<Mask>,
144    ) {
145        let clip = if let Some(c) = clip_path {
146            self.strip_generator.generate_filled_path(
147                c,
148                fill_rule,
149                clip_transform,
150                aliasing_threshold,
151                &mut self.strip_storage,
152            );
153
154            Some(self.strip_storage.strips.as_slice())
155        } else {
156            None
157        };
158
159        self.wide.push_layer(clip, blend_mode, mask, opacity, 0);
160    }
161
162    fn pop_layer(&mut self) {
163        self.wide.pop_layer();
164    }
165
166    fn reset(&mut self) {
167        self.wide.reset();
168        self.strip_generator.reset();
169        self.strip_storage.clear();
170    }
171
172    fn flush(&mut self) {}
173
174    fn rasterize(
175        &self,
176        buffer: &mut [u8],
177        render_mode: RenderMode,
178        width: u16,
179        height: u16,
180        encoded_paints: &[EncodedPaint],
181    ) {
182        match render_mode {
183            RenderMode::OptimizeSpeed => self.rasterize_u8(buffer, width, height, encoded_paints),
184            RenderMode::OptimizeQuality => {
185                self.rasterize_f32(buffer, width, height, encoded_paints);
186            }
187        }
188    }
189
190    fn generate_wide_cmd(&mut self, strip_buf: &[Strip], paint: Paint) {
191        self.wide.generate(strip_buf, paint, 0);
192    }
193
194    fn strip_storage_mut(&mut self) -> &mut StripStorage {
195        &mut self.strip_storage
196    }
197}
198
199#[cfg(test)]
200mod tests {
201    use super::*;
202    use crate::kurbo::Rect;
203    use vello_common::color::palette::css::BLUE;
204    use vello_common::kurbo::Shape;
205    use vello_common::paint::PremulColor;
206
207    #[test]
208    fn buffers_cleared_on_reset() {
209        let mut dispatcher = SingleThreadedDispatcher::new(100, 100, Level::new());
210
211        dispatcher.fill_path(
212            &Rect::new(0.0, 0.0, 50.0, 50.0).to_path(0.1),
213            Fill::NonZero,
214            Affine::IDENTITY,
215            Paint::Solid(PremulColor::from_alpha_color(BLUE)),
216            None,
217        );
218
219        // Ensure there is data to clear.
220        assert!(!dispatcher.strip_storage.alphas.is_empty());
221        assert!(!dispatcher.wide.get(0, 0).cmds.is_empty());
222
223        dispatcher.reset();
224
225        // Verify buffers are cleared.
226        assert!(dispatcher.strip_storage.alphas.is_empty());
227        assert!(dispatcher.wide.get(0, 0).cmds.is_empty());
228    }
229}