canvas/
vello_cpu_backend.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5use std::cell::RefCell;
6use std::collections::HashMap;
7use std::sync::Arc;
8
9use canvas_traits::canvas::{
10    CompositionOptions, CompositionOrBlending, CompositionStyle, FillOrStrokeStyle, FillRule,
11    LineOptions, Path, ShadowOptions, TextRun,
12};
13use compositing_traits::SerializableImageData;
14use euclid::default::{Point2D, Rect, Size2D, Transform2D};
15use fonts::FontIdentifier;
16use ipc_channel::ipc::IpcSharedMemory;
17use kurbo::Shape;
18use pixels::{Snapshot, SnapshotAlphaMode, SnapshotPixelFormat};
19use vello_cpu::{kurbo, peniko};
20use webrender_api::{ImageDescriptor, ImageDescriptorFlags};
21
22use crate::backend::{Convert, GenericDrawTarget};
23use crate::canvas_data::Filter;
24
25thread_local! {
26    /// The shared font cache used by all canvases that render on a thread.
27    static SHARED_FONT_CACHE: RefCell<HashMap<FontIdentifier, peniko::Font>> = RefCell::default();
28}
29
30#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
31enum State {
32    /// Scene is drawing. It will be consumed when rendered.
33    Drawing,
34    /// Scene is already rendered
35    /// Before next draw we need to put current rendering
36    /// in the background by calling [`VelloCPUDrawTarget::ensure_drawing`].
37    Rendered,
38}
39
40pub(crate) struct VelloCPUDrawTarget {
41    /// Because this is stateful context
42    /// caller cannot assume anything about transform, paint, stroke,
43    /// so it should provide it's own used by each command
44    /// but it can assume paint_transform to be identity
45    /// and fill rule to be `peniko::Fill::NonZero`
46    ///
47    /// This is because paint_transform is rarely set,
48    /// so it's cheaper to always reset it after use.
49    ctx: vello_cpu::RenderContext,
50    pixmap: vello_cpu::Pixmap,
51    clips: Vec<Path>,
52    state: State,
53}
54
55impl VelloCPUDrawTarget {
56    fn with_composition(
57        &mut self,
58        composition_operation: CompositionOrBlending,
59        f: impl FnOnce(&mut Self),
60    ) {
61        // Fast-path for default and most common composition operation
62        if composition_operation == CompositionOrBlending::Composition(CompositionStyle::SourceOver)
63        {
64            f(self);
65            return;
66        }
67        self.ctx.push_blend_layer(composition_operation.convert());
68        f(self);
69        self.ctx.pop_layer();
70    }
71
72    fn ignore_clips(&mut self, f: impl FnOnce(&mut Self)) {
73        // pop all clip layers
74        for _ in &self.clips {
75            self.ctx.pop_layer();
76        }
77        f(self);
78        // push all clip layers back
79        for path in &self.clips {
80            self.ctx.push_clip_layer(&path.0);
81        }
82    }
83
84    fn ensure_drawing(&mut self) {
85        match self.state {
86            State::Drawing => {},
87            State::Rendered => {
88                self.ignore_clips(|self_| {
89                    self_.ctx.set_transform(kurbo::Affine::IDENTITY);
90                    self_.ctx.set_paint(vello_cpu::Image {
91                        source: vello_cpu::ImageSource::Pixmap(Arc::new(self_.pixmap.clone())),
92                        x_extend: peniko::Extend::Pad,
93                        y_extend: peniko::Extend::Pad,
94                        quality: peniko::ImageQuality::Low,
95                    });
96                    self_.ctx.fill_rect(&kurbo::Rect::from_origin_size(
97                        (0., 0.),
98                        self_.size().cast(),
99                    ));
100                });
101                self.state = State::Drawing;
102            },
103        }
104    }
105
106    fn pixmap(&mut self) -> &[u8] {
107        if self.state == State::Drawing {
108            self.ignore_clips(|self_| {
109                self_.ctx.flush();
110                self_.ctx.render_to_pixmap(&mut self_.pixmap);
111                self_.ctx.reset();
112                self_.state = State::Rendered;
113            });
114        }
115
116        self.pixmap.data_as_u8_slice()
117    }
118
119    fn size(&self) -> Size2D<u32> {
120        Size2D::new(self.ctx.width(), self.ctx.height()).cast()
121    }
122
123    fn is_viewport_cleared(&mut self, rect: &Rect<f32>, transform: Transform2D<f64>) -> bool {
124        let transformed_rect = transform.outer_transformed_rect(&rect.cast());
125        if transformed_rect.is_empty() {
126            return false;
127        }
128        let viewport: Rect<f64> = Rect::from_size(self.get_size().cast());
129        let Some(clip) = self.clips.iter().try_fold(viewport, |acc, e| {
130            acc.intersection(&e.0.bounding_box().into())
131        }) else {
132            // clip makes no visible side effects
133            return false;
134        };
135        transformed_rect.cast().contains_rect(&viewport) && // whole viewport is cleared
136            clip.contains_rect(&viewport) // viewport is not clipped
137    }
138}
139
140impl GenericDrawTarget for VelloCPUDrawTarget {
141    type SourceSurface = Arc<vello_cpu::Pixmap>;
142
143    fn new(size: Size2D<u32>) -> Self {
144        let size = size.cast();
145        Self {
146            ctx: vello_cpu::RenderContext::new(size.width, size.height),
147            pixmap: vello_cpu::Pixmap::new(size.width, size.height),
148            clips: Vec::new(),
149            state: State::Rendered,
150        }
151    }
152
153    fn clear_rect(&mut self, rect: &Rect<f32>, transform: Transform2D<f64>) {
154        // vello_cpu RenderingContext only ever grows,
155        // so we need to use every opportunity to shrink it
156        if self.is_viewport_cleared(rect, transform) {
157            self.ctx.reset();
158            self.clips.clear(); // no clips are affecting rendering
159            self.state = State::Drawing;
160            return;
161        }
162        self.ensure_drawing();
163        let rect: kurbo::Rect = rect.cast().into();
164        let mut clip_path = rect.to_path(0.1);
165        clip_path.apply_affine(transform.cast().into());
166        let blend_mode = peniko::Compose::Clear;
167        self.ctx.push_layer(
168            Some(&clip_path.to_path(0.1)),
169            Some(blend_mode.into()),
170            None,
171            None,
172        );
173        self.ctx.pop_layer();
174    }
175
176    fn copy_surface(
177        &mut self,
178        surface: Self::SourceSurface,
179        source: Rect<i32>,
180        destination: Point2D<i32>,
181    ) {
182        self.ensure_drawing();
183        let destination: kurbo::Point = destination.cast::<f64>().into();
184        let rect = kurbo::Rect::from_origin_size(destination, source.size.cast());
185        self.ctx.set_transform(kurbo::Affine::IDENTITY);
186        self.ignore_clips(|self_| {
187            // Clipped blending does not work correctly:
188            // https://github.com/linebender/vello/issues/1119
189            // self_.push_layer(Some(rect.to_path(0.1)), Some(peniko::Compose::Copy.into()), None, None);
190
191            self_.ctx.set_paint(vello_cpu::Image {
192                source: vello_cpu::ImageSource::Pixmap(surface),
193                x_extend: peniko::Extend::Pad,
194                y_extend: peniko::Extend::Pad,
195                quality: peniko::ImageQuality::Low,
196            });
197            self_.ctx.fill_rect(&rect);
198
199            // self_.ctx.pop_layer();
200        });
201    }
202
203    fn create_similar_draw_target(&self, size: &Size2D<i32>) -> Self {
204        Self::new(size.cast())
205    }
206
207    fn draw_surface(
208        &mut self,
209        mut surface: Self::SourceSurface,
210        dest: Rect<f64>,
211        source: Rect<f64>,
212        filter: Filter,
213        composition_options: CompositionOptions,
214        transform: Transform2D<f64>,
215    ) {
216        self.ensure_drawing();
217        let scale_up = dest.size.width > source.size.width || dest.size.height > source.size.height;
218        if composition_options.alpha != 1.0 {
219            Arc::get_mut(&mut surface)
220                .expect("surface should be owned")
221                .multiply_alpha((composition_options.alpha * 255.0) as u8);
222        }
223        self.with_composition(composition_options.composition_operation, move |self_| {
224            self_.ctx.set_transform(transform.cast().into());
225            self_.ctx.set_paint(vello_cpu::Image {
226                source: vello_cpu::ImageSource::Pixmap(surface),
227                x_extend: peniko::Extend::Pad,
228                y_extend: peniko::Extend::Pad,
229                // we should only do bicubic when scaling up
230                quality: if scale_up {
231                    filter.convert()
232                } else {
233                    peniko::ImageQuality::Low
234                },
235            });
236            self_.ctx.set_paint_transform(
237                kurbo::Affine::translate((dest.origin.x, dest.origin.y)).pre_scale_non_uniform(
238                    dest.size.width / source.size.width,
239                    dest.size.height / source.size.height,
240                ),
241            );
242            self_.ctx.fill_rect(&dest.into());
243            self_.ctx.reset_paint_transform();
244        })
245    }
246
247    fn draw_surface_with_shadow(
248        &self,
249        _surface: Self::SourceSurface,
250        _dest: &Point2D<f32>,
251        _shadow_options: ShadowOptions,
252        _composition_options: CompositionOptions,
253    ) {
254        log::warn!("no support for drawing shadows");
255        /*
256        We will need to do some changes to support drawing shadows with vello, as current abstraction is made for azure.
257        In vello we do not need new draw target (we will use layers) and we need to pass whole rect.
258        offsets will be applied to rect directly. shadow blur will be passed directly to let backend do transforms.
259        */
260        // self_.scene.draw_blurred_rounded_rect(self_.transform, rect, color, 0.0, sigma);
261    }
262
263    fn fill(
264        &mut self,
265        path: &Path,
266        fill_rule: FillRule,
267        style: FillOrStrokeStyle,
268        composition_options: CompositionOptions,
269        transform: Transform2D<f64>,
270    ) {
271        self.ensure_drawing();
272        self.with_composition(composition_options.composition_operation, |self_| {
273            self_.ctx.set_transform(transform.cast().into());
274            self_.ctx.set_fill_rule(fill_rule.convert());
275            self_.ctx.set_paint(paint(style, composition_options.alpha));
276            self_.ctx.fill_path(&path.0);
277        });
278        self.ctx.set_fill_rule(peniko::Fill::NonZero);
279    }
280
281    fn fill_text(
282        &mut self,
283        text_runs: Vec<TextRun>,
284        style: FillOrStrokeStyle,
285        composition_options: CompositionOptions,
286        transform: Transform2D<f64>,
287    ) {
288        self.ensure_drawing();
289        self.ctx.set_paint(paint(style, composition_options.alpha));
290        self.ctx.set_transform(transform.cast().into());
291        self.with_composition(composition_options.composition_operation, |self_| {
292            for text_run in text_runs.iter() {
293                SHARED_FONT_CACHE.with(|font_cache| {
294                    let identifier = &text_run.font.identifier;
295                    if !font_cache.borrow().contains_key(identifier) {
296                        let Some(font_data_and_index) = text_run.font.font_data_and_index() else {
297                            return;
298                        };
299                        let font = font_data_and_index.convert();
300                        font_cache.borrow_mut().insert(identifier.clone(), font);
301                    }
302
303                    let font_cache = font_cache.borrow();
304                    let Some(font) = font_cache.get(identifier) else {
305                        return;
306                    };
307                    self_
308                        .ctx
309                        .glyph_run(font)
310                        .font_size(text_run.pt_size)
311                        .fill_glyphs(text_run.glyphs_and_positions.iter().map(
312                            |glyph_and_position| vello_cpu::Glyph {
313                                id: glyph_and_position.id,
314                                x: glyph_and_position.point.x,
315                                y: glyph_and_position.point.y,
316                            },
317                        ));
318                });
319            }
320        })
321    }
322
323    fn fill_rect(
324        &mut self,
325        rect: &Rect<f32>,
326        style: FillOrStrokeStyle,
327        composition_options: CompositionOptions,
328        transform: Transform2D<f64>,
329    ) {
330        self.ensure_drawing();
331        self.with_composition(composition_options.composition_operation, |self_| {
332            self_.ctx.set_transform(transform.cast().into());
333            self_.ctx.set_paint(paint(style, composition_options.alpha));
334            self_.ctx.fill_rect(&rect.cast().into());
335        })
336    }
337
338    fn get_size(&self) -> Size2D<i32> {
339        self.size().cast()
340    }
341
342    fn pop_clip(&mut self) {
343        if self.clips.pop().is_some() {
344            self.ctx.pop_layer();
345        }
346    }
347
348    fn push_clip(&mut self, path: &Path, fill_rule: FillRule, transform: Transform2D<f64>) {
349        self.ctx.set_transform(transform.cast().into());
350        let mut path = path.clone();
351        path.transform(transform.cast());
352        self.ctx.set_fill_rule(fill_rule.convert());
353        self.ctx.push_clip_layer(&path.0);
354        self.clips.push(path);
355        self.ctx.set_fill_rule(peniko::Fill::NonZero);
356    }
357
358    fn push_clip_rect(&mut self, rect: &Rect<i32>) {
359        let mut path = Path::new();
360        let rect = rect.cast();
361        path.rect(
362            rect.origin.x,
363            rect.origin.y,
364            rect.size.width,
365            rect.size.height,
366        );
367        self.push_clip(&path, FillRule::Nonzero, Transform2D::identity());
368    }
369
370    fn stroke(
371        &mut self,
372        path: &Path,
373        style: FillOrStrokeStyle,
374        line_options: LineOptions,
375        composition_options: CompositionOptions,
376        transform: Transform2D<f64>,
377    ) {
378        self.ensure_drawing();
379        self.with_composition(composition_options.composition_operation, |self_| {
380            self_.ctx.set_transform(transform.cast().into());
381            self_.ctx.set_paint(paint(style, composition_options.alpha));
382            self_.ctx.set_stroke(line_options.convert());
383            self_.ctx.stroke_path(&path.0);
384        })
385    }
386
387    fn stroke_rect(
388        &mut self,
389        rect: &Rect<f32>,
390        style: FillOrStrokeStyle,
391        line_options: LineOptions,
392        composition_options: CompositionOptions,
393        transform: Transform2D<f64>,
394    ) {
395        self.ensure_drawing();
396        self.with_composition(composition_options.composition_operation, |self_| {
397            self_.ctx.set_transform(transform.cast().into());
398            self_.ctx.set_paint(paint(style, composition_options.alpha));
399            self_.ctx.set_stroke(line_options.convert());
400            self_.ctx.stroke_rect(&rect.cast().into());
401        })
402    }
403
404    fn image_descriptor_and_serializable_data(
405        &mut self,
406    ) -> (ImageDescriptor, SerializableImageData) {
407        let image_desc = ImageDescriptor {
408            format: webrender_api::ImageFormat::RGBA8,
409            size: self.size().cast().cast_unit(),
410            stride: None,
411            offset: 0,
412            flags: ImageDescriptorFlags::empty(),
413        };
414        let data = SerializableImageData::Raw(IpcSharedMemory::from_bytes(self.pixmap()));
415        (image_desc, data)
416    }
417
418    fn snapshot(&mut self) -> pixels::Snapshot {
419        Snapshot::from_vec(
420            self.size().cast(),
421            SnapshotPixelFormat::RGBA,
422            SnapshotAlphaMode::Transparent {
423                premultiplied: true,
424            },
425            self.pixmap().to_vec(),
426        )
427    }
428
429    fn surface(&mut self) -> Self::SourceSurface {
430        self.pixmap(); // sync pixmap
431        Arc::new(vello_cpu::Pixmap::from_parts(
432            self.pixmap.clone().take(),
433            self.pixmap.width(),
434            self.pixmap.height(),
435        ))
436    }
437
438    fn create_source_surface_from_data(&self, data: Snapshot) -> Option<Self::SourceSurface> {
439        Some(snapshot_as_pixmap(data))
440    }
441}
442
443fn snapshot_as_pixmap(data: Snapshot) -> Arc<vello_cpu::Pixmap> {
444    let size = data.size().cast();
445    let (data, _, _) = data.to_vec(
446        Some(SnapshotAlphaMode::Transparent {
447            premultiplied: true,
448        }),
449        Some(SnapshotPixelFormat::RGBA),
450    );
451    Arc::new(vello_cpu::Pixmap::from_parts(
452        bytemuck::cast_vec(data),
453        size.width,
454        size.height,
455    ))
456}
457
458impl Convert<vello_cpu::PaintType> for FillOrStrokeStyle {
459    fn convert(self) -> vello_cpu::PaintType {
460        use canvas_traits::canvas::FillOrStrokeStyle::*;
461        match self {
462            Color(absolute_color) => vello_cpu::PaintType::Solid(absolute_color.convert()),
463            LinearGradient(style) => {
464                let start = kurbo::Point::new(style.x0, style.y0);
465                let end = kurbo::Point::new(style.x1, style.y1);
466                let mut gradient = peniko::Gradient::new_linear(start, end);
467                gradient.stops = style.stops.convert();
468                vello_cpu::PaintType::Gradient(gradient)
469            },
470            RadialGradient(style) => {
471                let center1 = kurbo::Point::new(style.x0, style.y0);
472                let center2 = kurbo::Point::new(style.x1, style.y1);
473                let mut gradient = peniko::Gradient::new_two_point_radial(
474                    center1,
475                    style.r0 as f32,
476                    center2,
477                    style.r1 as f32,
478                );
479                gradient.stops = style.stops.convert();
480                vello_cpu::PaintType::Gradient(gradient)
481            },
482            Surface(surface_style) => {
483                let pixmap = snapshot_as_pixmap(surface_style.surface_data.to_owned());
484                vello_cpu::PaintType::Image(vello_cpu::Image {
485                    source: vello_cpu::ImageSource::Pixmap(pixmap),
486                    x_extend: if surface_style.repeat_x {
487                        peniko::Extend::Repeat
488                    } else {
489                        peniko::Extend::Pad
490                    },
491                    y_extend: if surface_style.repeat_y {
492                        peniko::Extend::Repeat
493                    } else {
494                        peniko::Extend::Pad
495                    },
496                    quality: peniko::ImageQuality::Low,
497                })
498            },
499        }
500    }
501}
502
503fn paint(style: FillOrStrokeStyle, alpha: f64) -> vello_cpu::PaintType {
504    assert!((0.0..=1.0).contains(&alpha));
505    let paint = style.convert();
506    if alpha == 1.0 {
507        paint
508    } else {
509        match paint {
510            vello_cpu::PaintType::Solid(alpha_color) => {
511                vello_cpu::PaintType::Solid(alpha_color.multiply_alpha(alpha as f32))
512            },
513            vello_cpu::PaintType::Gradient(gradient) => {
514                vello_cpu::PaintType::Gradient(gradient.multiply_alpha(alpha as f32))
515            },
516            vello_cpu::PaintType::Image(mut image) => {
517                match &mut image.source {
518                    vello_cpu::ImageSource::Pixmap(pixmap) => Arc::get_mut(pixmap)
519                        .expect("pixmap should not be shared with anyone at this point")
520                        .multiply_alpha((alpha * 255.0) as u8),
521                    vello_cpu::ImageSource::OpaqueId(_) => unimplemented!(),
522                };
523                vello_cpu::PaintType::Image(image)
524            },
525        }
526    }
527}