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::FontData>> = 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                        image: vello_cpu::ImageSource::Pixmap(Arc::new(self_.pixmap.clone())),
92                        sampler: peniko::ImageSampler {
93                            x_extend: peniko::Extend::Pad,
94                            y_extend: peniko::Extend::Pad,
95                            quality: peniko::ImageQuality::Low,
96                            alpha: 1.0,
97                        },
98                    });
99                    self_.ctx.fill_rect(&kurbo::Rect::from_origin_size(
100                        (0., 0.),
101                        self_.size().cast(),
102                    ));
103                });
104                self.state = State::Drawing;
105            },
106        }
107    }
108
109    fn pixmap(&mut self) -> &[u8] {
110        if self.state == State::Drawing {
111            self.ignore_clips(|self_| {
112                self_.ctx.flush();
113                self_.ctx.render_to_pixmap(&mut self_.pixmap);
114                self_.ctx.reset();
115                self_.state = State::Rendered;
116            });
117        }
118
119        self.pixmap.data_as_u8_slice()
120    }
121
122    fn size(&self) -> Size2D<u32> {
123        Size2D::new(self.ctx.width(), self.ctx.height()).cast()
124    }
125
126    fn is_viewport_cleared(&mut self, rect: &Rect<f32>, transform: Transform2D<f64>) -> bool {
127        let transformed_rect = transform.outer_transformed_rect(&rect.cast());
128        if transformed_rect.is_empty() {
129            return false;
130        }
131        let viewport: Rect<f64> = Rect::from_size(self.get_size().cast());
132        let Some(clip) = self.clips.iter().try_fold(viewport, |acc, e| {
133            acc.intersection(&e.0.bounding_box().into())
134        }) else {
135            // clip makes no visible side effects
136            return false;
137        };
138        transformed_rect.cast().contains_rect(&viewport) && // whole viewport is cleared
139            clip.contains_rect(&viewport) // viewport is not clipped
140    }
141}
142
143impl GenericDrawTarget for VelloCPUDrawTarget {
144    type SourceSurface = Arc<vello_cpu::Pixmap>;
145
146    fn new(size: Size2D<u32>) -> Self {
147        let size = size.cast();
148        Self {
149            ctx: vello_cpu::RenderContext::new(size.width, size.height),
150            pixmap: vello_cpu::Pixmap::new(size.width, size.height),
151            clips: Vec::new(),
152            state: State::Rendered,
153        }
154    }
155
156    fn clear_rect(&mut self, rect: &Rect<f32>, transform: Transform2D<f64>) {
157        // vello_cpu RenderingContext only ever grows,
158        // so we need to use every opportunity to shrink it
159        if self.is_viewport_cleared(rect, transform) {
160            self.ctx.reset();
161            self.clips.clear(); // no clips are affecting rendering
162            self.state = State::Drawing;
163            return;
164        }
165        self.ensure_drawing();
166        let rect: kurbo::Rect = rect.cast().into();
167        let mut clip_path = rect.to_path(0.1);
168        clip_path.apply_affine(transform.cast().into());
169        let blend_mode = peniko::Compose::Clear;
170        self.ctx.push_layer(
171            Some(&clip_path.to_path(0.1)),
172            Some(blend_mode.into()),
173            None,
174            None,
175        );
176        self.ctx.pop_layer();
177    }
178
179    fn copy_surface(
180        &mut self,
181        surface: Self::SourceSurface,
182        source: Rect<i32>,
183        destination: Point2D<i32>,
184    ) {
185        self.ensure_drawing();
186        let destination: kurbo::Point = destination.cast::<f64>().into();
187        let rect = kurbo::Rect::from_origin_size(destination, source.size.cast());
188        self.ctx.set_transform(kurbo::Affine::IDENTITY);
189        self.ignore_clips(|self_| {
190            // Clipped blending does not work correctly:
191            // https://github.com/linebender/vello/issues/1119
192            // self_.push_layer(Some(rect.to_path(0.1)), Some(peniko::Compose::Copy.into()), None, None);
193
194            self_.ctx.set_paint(vello_cpu::Image {
195                image: vello_cpu::ImageSource::Pixmap(surface),
196                sampler: peniko::ImageSampler {
197                    x_extend: peniko::Extend::Pad,
198                    y_extend: peniko::Extend::Pad,
199                    quality: peniko::ImageQuality::Low,
200                    alpha: 1.0,
201                },
202            });
203            self_.ctx.fill_rect(&rect);
204
205            // self_.ctx.pop_layer();
206        });
207    }
208
209    fn create_similar_draw_target(&self, size: &Size2D<i32>) -> Self {
210        Self::new(size.cast())
211    }
212
213    fn draw_surface(
214        &mut self,
215        mut surface: Self::SourceSurface,
216        dest: Rect<f64>,
217        source: Rect<f64>,
218        filter: Filter,
219        composition_options: CompositionOptions,
220        transform: Transform2D<f64>,
221    ) {
222        self.ensure_drawing();
223        let scale_up = dest.size.width > source.size.width || dest.size.height > source.size.height;
224        if composition_options.alpha != 1.0 {
225            Arc::get_mut(&mut surface)
226                .expect("surface should be owned")
227                .multiply_alpha((composition_options.alpha * 255.0) as u8);
228        }
229        self.with_composition(composition_options.composition_operation, move |self_| {
230            self_.ctx.set_transform(transform.cast().into());
231            self_.ctx.set_paint(vello_cpu::Image {
232                image: vello_cpu::ImageSource::Pixmap(surface),
233                sampler: peniko::ImageSampler {
234                    x_extend: peniko::Extend::Pad,
235                    y_extend: peniko::Extend::Pad,
236                    // we should only do bicubic when scaling up
237                    quality: if scale_up {
238                        filter.convert()
239                    } else {
240                        peniko::ImageQuality::Low
241                    },
242                    alpha: 1.0,
243                },
244            });
245            self_.ctx.set_paint_transform(
246                kurbo::Affine::translate((dest.origin.x, dest.origin.y)).pre_scale_non_uniform(
247                    dest.size.width / source.size.width,
248                    dest.size.height / source.size.height,
249                ),
250            );
251            self_.ctx.fill_rect(&dest.into());
252            self_.ctx.reset_paint_transform();
253        })
254    }
255
256    fn draw_surface_with_shadow(
257        &self,
258        _surface: Self::SourceSurface,
259        _dest: &Point2D<f32>,
260        _shadow_options: ShadowOptions,
261        _composition_options: CompositionOptions,
262    ) {
263        log::warn!("no support for drawing shadows");
264        /*
265        We will need to do some changes to support drawing shadows with vello, as current abstraction is made for azure.
266        In vello we do not need new draw target (we will use layers) and we need to pass whole rect.
267        offsets will be applied to rect directly. shadow blur will be passed directly to let backend do transforms.
268        */
269        // self_.scene.draw_blurred_rounded_rect(self_.transform, rect, color, 0.0, sigma);
270    }
271
272    fn fill(
273        &mut self,
274        path: &Path,
275        fill_rule: FillRule,
276        style: FillOrStrokeStyle,
277        composition_options: CompositionOptions,
278        transform: Transform2D<f64>,
279    ) {
280        self.ensure_drawing();
281        self.with_composition(composition_options.composition_operation, |self_| {
282            self_.ctx.set_transform(transform.cast().into());
283            self_.ctx.set_fill_rule(fill_rule.convert());
284            self_.ctx.set_paint(paint(style, composition_options.alpha));
285            self_.ctx.fill_path(&path.0);
286        });
287        self.ctx.set_fill_rule(peniko::Fill::NonZero);
288    }
289
290    fn fill_text(
291        &mut self,
292        text_runs: Vec<TextRun>,
293        style: FillOrStrokeStyle,
294        composition_options: CompositionOptions,
295        transform: Transform2D<f64>,
296    ) {
297        self.ensure_drawing();
298        self.ctx.set_paint(paint(style, composition_options.alpha));
299        self.ctx.set_transform(transform.cast().into());
300        self.with_composition(composition_options.composition_operation, |self_| {
301            for text_run in text_runs.iter() {
302                SHARED_FONT_CACHE.with(|font_cache| {
303                    let identifier = &text_run.font.identifier;
304                    if !font_cache.borrow().contains_key(identifier) {
305                        let Some(font_data_and_index) = text_run.font.font_data_and_index() else {
306                            return;
307                        };
308                        let font = font_data_and_index.convert();
309                        font_cache.borrow_mut().insert(identifier.clone(), font);
310                    }
311
312                    let font_cache = font_cache.borrow();
313                    let Some(font) = font_cache.get(identifier) else {
314                        return;
315                    };
316                    self_
317                        .ctx
318                        .glyph_run(font)
319                        .font_size(text_run.pt_size)
320                        .fill_glyphs(text_run.glyphs_and_positions.iter().map(
321                            |glyph_and_position| vello_cpu::Glyph {
322                                id: glyph_and_position.id,
323                                x: glyph_and_position.point.x,
324                                y: glyph_and_position.point.y,
325                            },
326                        ));
327                });
328            }
329        })
330    }
331
332    fn fill_rect(
333        &mut self,
334        rect: &Rect<f32>,
335        style: FillOrStrokeStyle,
336        composition_options: CompositionOptions,
337        transform: Transform2D<f64>,
338    ) {
339        self.ensure_drawing();
340        self.with_composition(composition_options.composition_operation, |self_| {
341            self_.ctx.set_transform(transform.cast().into());
342            self_.ctx.set_paint(paint(style, composition_options.alpha));
343            self_.ctx.fill_rect(&rect.cast().into());
344        })
345    }
346
347    fn get_size(&self) -> Size2D<i32> {
348        self.size().cast()
349    }
350
351    fn pop_clip(&mut self) {
352        if self.clips.pop().is_some() {
353            self.ctx.pop_layer();
354        }
355    }
356
357    fn push_clip(&mut self, path: &Path, fill_rule: FillRule, transform: Transform2D<f64>) {
358        self.ctx.set_transform(transform.cast().into());
359        let mut path = path.clone();
360        path.transform(transform.cast());
361        self.ctx.set_fill_rule(fill_rule.convert());
362        self.ctx.push_clip_layer(&path.0);
363        self.clips.push(path);
364        self.ctx.set_fill_rule(peniko::Fill::NonZero);
365    }
366
367    fn push_clip_rect(&mut self, rect: &Rect<i32>) {
368        let mut path = Path::new();
369        let rect = rect.cast();
370        path.rect(
371            rect.origin.x,
372            rect.origin.y,
373            rect.size.width,
374            rect.size.height,
375        );
376        self.push_clip(&path, FillRule::Nonzero, Transform2D::identity());
377    }
378
379    fn stroke(
380        &mut self,
381        path: &Path,
382        style: FillOrStrokeStyle,
383        line_options: LineOptions,
384        composition_options: CompositionOptions,
385        transform: Transform2D<f64>,
386    ) {
387        self.ensure_drawing();
388        self.with_composition(composition_options.composition_operation, |self_| {
389            self_.ctx.set_transform(transform.cast().into());
390            self_.ctx.set_paint(paint(style, composition_options.alpha));
391            self_.ctx.set_stroke(line_options.convert());
392            self_.ctx.stroke_path(&path.0);
393        })
394    }
395
396    fn stroke_text(
397        &mut self,
398        text_runs: Vec<TextRun>,
399        style: FillOrStrokeStyle,
400        line_options: LineOptions,
401        composition_options: CompositionOptions,
402        transform: Transform2D<f64>,
403    ) {
404        self.ensure_drawing();
405        self.ctx.set_paint(paint(style, composition_options.alpha));
406        self.ctx.set_stroke(line_options.convert());
407        self.ctx.set_transform(transform.cast().into());
408        self.with_composition(composition_options.composition_operation, |self_| {
409            for text_run in text_runs.iter() {
410                SHARED_FONT_CACHE.with(|font_cache| {
411                    let identifier = &text_run.font.identifier;
412                    if !font_cache.borrow().contains_key(identifier) {
413                        let Some(font_data_and_index) = text_run.font.font_data_and_index() else {
414                            return;
415                        };
416                        let font = font_data_and_index.convert();
417                        font_cache.borrow_mut().insert(identifier.clone(), font);
418                    }
419
420                    let font_cache = font_cache.borrow();
421                    let Some(font) = font_cache.get(identifier) else {
422                        return;
423                    };
424                    self_
425                        .ctx
426                        .glyph_run(font)
427                        .font_size(text_run.pt_size)
428                        .stroke_glyphs(text_run.glyphs_and_positions.iter().map(
429                            |glyph_and_position| vello_cpu::Glyph {
430                                id: glyph_and_position.id,
431                                x: glyph_and_position.point.x,
432                                y: glyph_and_position.point.y,
433                            },
434                        ));
435                });
436            }
437        })
438    }
439
440    fn stroke_rect(
441        &mut self,
442        rect: &Rect<f32>,
443        style: FillOrStrokeStyle,
444        line_options: LineOptions,
445        composition_options: CompositionOptions,
446        transform: Transform2D<f64>,
447    ) {
448        self.ensure_drawing();
449        self.with_composition(composition_options.composition_operation, |self_| {
450            self_.ctx.set_transform(transform.cast().into());
451            self_.ctx.set_paint(paint(style, composition_options.alpha));
452            self_.ctx.set_stroke(line_options.convert());
453            self_.ctx.stroke_rect(&rect.cast().into());
454        })
455    }
456
457    fn image_descriptor_and_serializable_data(
458        &mut self,
459    ) -> (ImageDescriptor, SerializableImageData) {
460        let image_desc = ImageDescriptor {
461            format: webrender_api::ImageFormat::RGBA8,
462            size: self.size().cast().cast_unit(),
463            stride: None,
464            offset: 0,
465            flags: ImageDescriptorFlags::empty(),
466        };
467        let data = SerializableImageData::Raw(IpcSharedMemory::from_bytes(self.pixmap()));
468        (image_desc, data)
469    }
470
471    fn snapshot(&mut self) -> pixels::Snapshot {
472        Snapshot::from_vec(
473            self.size().cast(),
474            SnapshotPixelFormat::RGBA,
475            SnapshotAlphaMode::Transparent {
476                premultiplied: true,
477            },
478            self.pixmap().to_vec(),
479        )
480    }
481
482    fn surface(&mut self) -> Self::SourceSurface {
483        self.pixmap(); // sync pixmap
484        Arc::new(vello_cpu::Pixmap::from_parts(
485            self.pixmap.clone().take(),
486            self.pixmap.width(),
487            self.pixmap.height(),
488        ))
489    }
490
491    fn create_source_surface_from_data(&self, data: Snapshot) -> Option<Self::SourceSurface> {
492        Some(snapshot_as_pixmap(data))
493    }
494}
495
496fn snapshot_as_pixmap(mut snapshot: Snapshot) -> Arc<vello_cpu::Pixmap> {
497    let size = snapshot.size().cast();
498    snapshot.transform(
499        SnapshotAlphaMode::Transparent {
500            premultiplied: true,
501        },
502        SnapshotPixelFormat::RGBA,
503    );
504
505    Arc::new(vello_cpu::Pixmap::from_parts(
506        bytemuck::cast_vec(snapshot.into()),
507        size.width,
508        size.height,
509    ))
510}
511
512impl Convert<vello_cpu::PaintType> for FillOrStrokeStyle {
513    fn convert(self) -> vello_cpu::PaintType {
514        use canvas_traits::canvas::FillOrStrokeStyle::*;
515        match self {
516            Color(absolute_color) => vello_cpu::PaintType::Solid(absolute_color.convert()),
517            LinearGradient(style) => {
518                let start = kurbo::Point::new(style.x0, style.y0);
519                let end = kurbo::Point::new(style.x1, style.y1);
520                let mut gradient = peniko::Gradient::new_linear(start, end);
521                gradient.stops = style.stops.convert();
522                vello_cpu::PaintType::Gradient(gradient)
523            },
524            RadialGradient(style) => {
525                let center1 = kurbo::Point::new(style.x0, style.y0);
526                let center2 = kurbo::Point::new(style.x1, style.y1);
527                let mut gradient = peniko::Gradient::new_two_point_radial(
528                    center1,
529                    style.r0 as f32,
530                    center2,
531                    style.r1 as f32,
532                );
533                gradient.stops = style.stops.convert();
534                vello_cpu::PaintType::Gradient(gradient)
535            },
536            Surface(surface_style) => {
537                let pixmap = snapshot_as_pixmap(surface_style.surface_data.to_owned());
538                vello_cpu::PaintType::Image(vello_cpu::Image {
539                    image: vello_cpu::ImageSource::Pixmap(pixmap),
540                    sampler: peniko::ImageSampler {
541                        x_extend: if surface_style.repeat_x {
542                            peniko::Extend::Repeat
543                        } else {
544                            peniko::Extend::Pad
545                        },
546                        y_extend: if surface_style.repeat_y {
547                            peniko::Extend::Repeat
548                        } else {
549                            peniko::Extend::Pad
550                        },
551                        quality: peniko::ImageQuality::Low,
552                        alpha: 1.0,
553                    },
554                })
555            },
556        }
557    }
558}
559
560fn paint(style: FillOrStrokeStyle, alpha: f64) -> vello_cpu::PaintType {
561    assert!((0.0..=1.0).contains(&alpha));
562    let paint = style.convert();
563    if alpha == 1.0 {
564        paint
565    } else {
566        match paint {
567            vello_cpu::PaintType::Solid(alpha_color) => {
568                vello_cpu::PaintType::Solid(alpha_color.multiply_alpha(alpha as f32))
569            },
570            vello_cpu::PaintType::Gradient(gradient) => {
571                vello_cpu::PaintType::Gradient(gradient.multiply_alpha(alpha as f32))
572            },
573            vello_cpu::PaintType::Image(mut image) => {
574                match &mut image.image {
575                    vello_cpu::ImageSource::Pixmap(pixmap) => Arc::get_mut(pixmap)
576                        .expect("pixmap should not be shared with anyone at this point")
577                        .multiply_alpha((alpha * 255.0) as u8),
578                    vello_cpu::ImageSource::OpaqueId(_) => unimplemented!(),
579                };
580                vello_cpu::PaintType::Image(image)
581            },
582        }
583    }
584}