Skip to main content

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