canvas/
canvas_paint_thread.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::borrow::ToOwned;
6use std::{f32, thread};
7
8use base::generic_channel::GenericSender;
9use base::{Epoch, generic_channel};
10use canvas_traits::ConstellationCanvasMsg;
11use canvas_traits::canvas::*;
12use compositing_traits::CrossProcessCompositorApi;
13use crossbeam_channel::{Sender, select, unbounded};
14use euclid::default::{Rect, Size2D, Transform2D};
15use log::warn;
16use pixels::Snapshot;
17use rustc_hash::FxHashMap;
18use webrender_api::ImageKey;
19
20use crate::canvas_data::*;
21
22pub struct CanvasPaintThread {
23    canvases: FxHashMap<CanvasId, Canvas>,
24    next_canvas_id: CanvasId,
25    compositor_api: CrossProcessCompositorApi,
26}
27
28impl CanvasPaintThread {
29    fn new(compositor_api: CrossProcessCompositorApi) -> CanvasPaintThread {
30        CanvasPaintThread {
31            canvases: FxHashMap::default(),
32            next_canvas_id: CanvasId(0),
33            compositor_api: compositor_api.clone(),
34        }
35    }
36
37    /// Creates a new `CanvasPaintThread` and returns an `IpcSender` to
38    /// communicate with it.
39    pub fn start(
40        compositor_api: CrossProcessCompositorApi,
41    ) -> (Sender<ConstellationCanvasMsg>, GenericSender<CanvasMsg>) {
42        let (ipc_sender, ipc_receiver) = generic_channel::channel::<CanvasMsg>().unwrap();
43        let msg_receiver = ipc_receiver.route_preserving_errors();
44        let (create_sender, create_receiver) = unbounded();
45        thread::Builder::new()
46            .name("Canvas".to_owned())
47            .spawn(move || {
48                let mut canvas_paint_thread = CanvasPaintThread::new(
49                    compositor_api);
50                loop {
51                    select! {
52                        recv(msg_receiver) -> msg => {
53                            match msg {
54                                Ok(Ok(CanvasMsg::Canvas2d(message, canvas_id))) => {
55                                    canvas_paint_thread.process_canvas_2d_message(message, canvas_id);
56                                },
57                                Ok(Ok(CanvasMsg::Close(canvas_id))) => {
58                                    canvas_paint_thread.canvases.remove(&canvas_id);
59                                },
60                                Ok(Ok(CanvasMsg::Recreate(size, canvas_id))) => {
61                                    canvas_paint_thread.canvas(canvas_id).recreate(size);
62                                },
63                                Ok(Err(e)) => {
64                                    warn!("CanvasPaintThread message deserialization error: {e:?}");
65                                }
66                                Err(_disconnected) => {
67                                    warn!("CanvasMsg receiver disconnected");
68                                    break;
69                                },
70                            }
71                        }
72                        recv(create_receiver) -> msg => {
73                            match msg {
74                                Ok(ConstellationCanvasMsg::Create { sender: creator, size }) => {
75                                    creator.send(canvas_paint_thread.create_canvas(size)).unwrap();
76                                },
77                                Ok(ConstellationCanvasMsg::Exit(exit_sender)) => {
78                                    let _ = exit_sender.send(());
79                                    break;
80                                },
81                                Err(e) => {
82                                    warn!("Error on CanvasPaintThread receive ({})", e);
83                                    break;
84                                },
85                            }
86                        }
87                    }
88                }
89            })
90            .expect("Thread spawning failed");
91
92        (create_sender, ipc_sender)
93    }
94
95    #[servo_tracing::instrument(skip_all)]
96    pub fn create_canvas(&mut self, size: Size2D<u64>) -> Option<CanvasId> {
97        let canvas_id = self.next_canvas_id;
98        self.next_canvas_id.0 += 1;
99
100        let canvas = Canvas::new(size, self.compositor_api.clone())?;
101        self.canvases.insert(canvas_id, canvas);
102
103        Some(canvas_id)
104    }
105
106    #[servo_tracing::instrument(
107        skip_all,
108        fields(message = message.to_string())
109    )]
110    fn process_canvas_2d_message(&mut self, message: Canvas2dMsg, canvas_id: CanvasId) {
111        match message {
112            Canvas2dMsg::SetImageKey(image_key) => {
113                self.canvas(canvas_id).set_image_key(image_key);
114            },
115            Canvas2dMsg::FillText(
116                text_bounds,
117                text_runs,
118                fill_or_stroke_style,
119                shadow_options,
120                composition_options,
121                transform,
122            ) => {
123                self.canvas(canvas_id).fill_text(
124                    text_bounds,
125                    text_runs,
126                    fill_or_stroke_style,
127                    shadow_options,
128                    composition_options,
129                    transform,
130                );
131            },
132            Canvas2dMsg::StrokeText(
133                text_bounds,
134                text_runs,
135                fill_or_stroke_style,
136                line_options,
137                shadow_options,
138                composition_options,
139                transform,
140            ) => {
141                self.canvas(canvas_id).stroke_text(
142                    text_bounds,
143                    text_runs,
144                    fill_or_stroke_style,
145                    line_options,
146                    shadow_options,
147                    composition_options,
148                    transform,
149                );
150            },
151            Canvas2dMsg::FillRect(rect, style, shadow_options, composition_options, transform) => {
152                self.canvas(canvas_id).fill_rect(
153                    &rect,
154                    style,
155                    shadow_options,
156                    composition_options,
157                    transform,
158                );
159            },
160            Canvas2dMsg::StrokeRect(
161                rect,
162                style,
163                line_options,
164                shadow_options,
165                composition_options,
166                transform,
167            ) => {
168                self.canvas(canvas_id).stroke_rect(
169                    &rect,
170                    style,
171                    line_options,
172                    shadow_options,
173                    composition_options,
174                    transform,
175                );
176            },
177            Canvas2dMsg::ClearRect(ref rect, transform) => {
178                self.canvas(canvas_id).clear_rect(rect, transform)
179            },
180            Canvas2dMsg::FillPath(
181                style,
182                path,
183                fill_rule,
184                shadow_options,
185                composition_options,
186                transform,
187            ) => {
188                self.canvas(canvas_id).fill_path(
189                    &path,
190                    fill_rule,
191                    style,
192                    shadow_options,
193                    composition_options,
194                    transform,
195                );
196            },
197            Canvas2dMsg::StrokePath(
198                path,
199                style,
200                line_options,
201                shadow_options,
202                composition_options,
203                transform,
204            ) => {
205                self.canvas(canvas_id).stroke_path(
206                    &path,
207                    style,
208                    line_options,
209                    shadow_options,
210                    composition_options,
211                    transform,
212                );
213            },
214            Canvas2dMsg::ClipPath(path, fill_rule, transform) => {
215                self.canvas(canvas_id)
216                    .clip_path(&path, fill_rule, transform);
217            },
218            Canvas2dMsg::DrawImage(
219                snapshot,
220                dest_rect,
221                source_rect,
222                smoothing_enabled,
223                shadow_options,
224                composition_options,
225                transform,
226            ) => self.canvas(canvas_id).draw_image(
227                snapshot.to_owned(),
228                dest_rect,
229                source_rect,
230                smoothing_enabled,
231                shadow_options,
232                composition_options,
233                transform,
234            ),
235            Canvas2dMsg::DrawEmptyImage(
236                image_size,
237                dest_rect,
238                source_rect,
239                shadow_options,
240                composition_options,
241                transform,
242            ) => self.canvas(canvas_id).draw_image(
243                Snapshot::cleared(image_size),
244                dest_rect,
245                source_rect,
246                false,
247                shadow_options,
248                composition_options,
249                transform,
250            ),
251            Canvas2dMsg::DrawImageInOther(
252                other_canvas_id,
253                dest_rect,
254                source_rect,
255                smoothing,
256                shadow_options,
257                composition_options,
258                transform,
259            ) => {
260                let snapshot = self
261                    .canvas(canvas_id)
262                    .read_pixels(Some(source_rect.to_u32()));
263                self.canvas(other_canvas_id).draw_image(
264                    snapshot,
265                    dest_rect,
266                    source_rect,
267                    smoothing,
268                    shadow_options,
269                    composition_options,
270                    transform,
271                );
272            },
273            Canvas2dMsg::GetImageData(dest_rect, sender) => {
274                let snapshot = self.canvas(canvas_id).read_pixels(dest_rect);
275                sender.send(snapshot.to_shared()).unwrap();
276            },
277            Canvas2dMsg::PutImageData(rect, snapshot) => {
278                self.canvas(canvas_id)
279                    .put_image_data(snapshot.to_owned(), rect);
280            },
281            Canvas2dMsg::UpdateImage(canvas_epoch) => {
282                self.canvas(canvas_id).update_image_rendering(canvas_epoch);
283            },
284            Canvas2dMsg::PopClips(clips) => self.canvas(canvas_id).pop_clips(clips),
285        }
286    }
287
288    fn canvas(&mut self, canvas_id: CanvasId) -> &mut Canvas {
289        self.canvases.get_mut(&canvas_id).expect("Bogus canvas id")
290    }
291}
292
293#[allow(clippy::large_enum_variant)]
294enum Canvas {
295    #[cfg(feature = "vello")]
296    Vello(CanvasData<crate::vello_backend::VelloDrawTarget>),
297    #[cfg(feature = "vello_cpu")]
298    VelloCPU(CanvasData<crate::vello_cpu_backend::VelloCPUDrawTarget>),
299}
300
301impl Canvas {
302    fn new(size: Size2D<u64>, compositor_api: CrossProcessCompositorApi) -> Option<Self> {
303        match servo_config::pref!(dom_canvas_backend)
304            .to_lowercase()
305            .as_str()
306        {
307            #[cfg(feature = "vello_cpu")]
308            "" | "auto" | "vello_cpu" => {
309                Some(Self::VelloCPU(CanvasData::new(size, compositor_api)))
310            },
311            #[cfg(feature = "vello")]
312            "" | "auto" | "vello" => Some(Self::Vello(CanvasData::new(size, compositor_api))),
313            s => {
314                warn!("Unknown 2D canvas backend: `{s}`");
315                None
316            },
317        }
318    }
319
320    fn set_image_key(&mut self, image_key: ImageKey) {
321        match self {
322            #[cfg(feature = "vello")]
323            Canvas::Vello(canvas_data) => canvas_data.set_image_key(image_key),
324            #[cfg(feature = "vello_cpu")]
325            Canvas::VelloCPU(canvas_data) => canvas_data.set_image_key(image_key),
326        }
327    }
328
329    fn pop_clips(&mut self, clips: usize) {
330        match self {
331            #[cfg(feature = "vello")]
332            Canvas::Vello(canvas_data) => canvas_data.pop_clips(clips),
333            #[cfg(feature = "vello_cpu")]
334            Canvas::VelloCPU(canvas_data) => canvas_data.pop_clips(clips),
335        }
336    }
337
338    fn stroke_text(
339        &mut self,
340        text_bounds: Rect<f64>,
341        text_runs: Vec<TextRun>,
342        fill_or_stroke_style: FillOrStrokeStyle,
343        line_options: LineOptions,
344        shadow_options: ShadowOptions,
345        composition_options: CompositionOptions,
346        transform: Transform2D<f64>,
347    ) {
348        match self {
349            #[cfg(feature = "vello")]
350            Canvas::Vello(canvas_data) => canvas_data.stroke_text(
351                text_bounds,
352                text_runs,
353                fill_or_stroke_style,
354                line_options,
355                shadow_options,
356                composition_options,
357                transform,
358            ),
359            #[cfg(feature = "vello_cpu")]
360            Canvas::VelloCPU(canvas_data) => canvas_data.stroke_text(
361                text_bounds,
362                text_runs,
363                fill_or_stroke_style,
364                line_options,
365                shadow_options,
366                composition_options,
367                transform,
368            ),
369        }
370    }
371
372    fn fill_text(
373        &mut self,
374        text_bounds: Rect<f64>,
375        text_runs: Vec<TextRun>,
376        fill_or_stroke_style: FillOrStrokeStyle,
377        shadow_options: ShadowOptions,
378        composition_options: CompositionOptions,
379        transform: Transform2D<f64>,
380    ) {
381        match self {
382            #[cfg(feature = "vello")]
383            Canvas::Vello(canvas_data) => canvas_data.fill_text(
384                text_bounds,
385                text_runs,
386                fill_or_stroke_style,
387                shadow_options,
388                composition_options,
389                transform,
390            ),
391            #[cfg(feature = "vello_cpu")]
392            Canvas::VelloCPU(canvas_data) => canvas_data.fill_text(
393                text_bounds,
394                text_runs,
395                fill_or_stroke_style,
396                shadow_options,
397                composition_options,
398                transform,
399            ),
400        }
401    }
402
403    fn fill_rect(
404        &mut self,
405        rect: &Rect<f32>,
406        style: FillOrStrokeStyle,
407        shadow_options: ShadowOptions,
408        composition_options: CompositionOptions,
409        transform: Transform2D<f64>,
410    ) {
411        match self {
412            #[cfg(feature = "vello")]
413            Canvas::Vello(canvas_data) => {
414                canvas_data.fill_rect(rect, style, shadow_options, composition_options, transform)
415            },
416            #[cfg(feature = "vello_cpu")]
417            Canvas::VelloCPU(canvas_data) => {
418                canvas_data.fill_rect(rect, style, shadow_options, composition_options, transform)
419            },
420        }
421    }
422
423    fn stroke_rect(
424        &mut self,
425        rect: &Rect<f32>,
426        style: FillOrStrokeStyle,
427        line_options: LineOptions,
428        shadow_options: ShadowOptions,
429        composition_options: CompositionOptions,
430        transform: Transform2D<f64>,
431    ) {
432        match self {
433            #[cfg(feature = "vello")]
434            Canvas::Vello(canvas_data) => canvas_data.stroke_rect(
435                rect,
436                style,
437                line_options,
438                shadow_options,
439                composition_options,
440                transform,
441            ),
442            #[cfg(feature = "vello_cpu")]
443            Canvas::VelloCPU(canvas_data) => canvas_data.stroke_rect(
444                rect,
445                style,
446                line_options,
447                shadow_options,
448                composition_options,
449                transform,
450            ),
451        }
452    }
453
454    fn fill_path(
455        &mut self,
456        path: &Path,
457        fill_rule: FillRule,
458        style: FillOrStrokeStyle,
459        shadow_options: ShadowOptions,
460        composition_options: CompositionOptions,
461        transform: Transform2D<f64>,
462    ) {
463        match self {
464            #[cfg(feature = "vello")]
465            Canvas::Vello(canvas_data) => canvas_data.fill_path(
466                path,
467                fill_rule,
468                style,
469                shadow_options,
470                composition_options,
471                transform,
472            ),
473            #[cfg(feature = "vello_cpu")]
474            Canvas::VelloCPU(canvas_data) => canvas_data.fill_path(
475                path,
476                fill_rule,
477                style,
478                shadow_options,
479                composition_options,
480                transform,
481            ),
482        }
483    }
484
485    fn stroke_path(
486        &mut self,
487        path: &Path,
488        style: FillOrStrokeStyle,
489        line_options: LineOptions,
490        shadow_options: ShadowOptions,
491        composition_options: CompositionOptions,
492        transform: Transform2D<f64>,
493    ) {
494        match self {
495            #[cfg(feature = "vello")]
496            Canvas::Vello(canvas_data) => canvas_data.stroke_path(
497                path,
498                style,
499                line_options,
500                shadow_options,
501                composition_options,
502                transform,
503            ),
504            #[cfg(feature = "vello_cpu")]
505            Canvas::VelloCPU(canvas_data) => canvas_data.stroke_path(
506                path,
507                style,
508                line_options,
509                shadow_options,
510                composition_options,
511                transform,
512            ),
513        }
514    }
515
516    fn clear_rect(&mut self, rect: &Rect<f32>, transform: Transform2D<f64>) {
517        match self {
518            #[cfg(feature = "vello")]
519            Canvas::Vello(canvas_data) => canvas_data.clear_rect(rect, transform),
520            #[cfg(feature = "vello_cpu")]
521            Canvas::VelloCPU(canvas_data) => canvas_data.clear_rect(rect, transform),
522        }
523    }
524
525    #[allow(clippy::too_many_arguments)]
526    fn draw_image(
527        &mut self,
528        snapshot: Snapshot,
529        dest_rect: Rect<f64>,
530        source_rect: Rect<f64>,
531        smoothing_enabled: bool,
532        shadow_options: ShadowOptions,
533        composition_options: CompositionOptions,
534        transform: Transform2D<f64>,
535    ) {
536        match self {
537            #[cfg(feature = "vello")]
538            Canvas::Vello(canvas_data) => canvas_data.draw_image(
539                snapshot,
540                dest_rect,
541                source_rect,
542                smoothing_enabled,
543                shadow_options,
544                composition_options,
545                transform,
546            ),
547            #[cfg(feature = "vello_cpu")]
548            Canvas::VelloCPU(canvas_data) => canvas_data.draw_image(
549                snapshot,
550                dest_rect,
551                source_rect,
552                smoothing_enabled,
553                shadow_options,
554                composition_options,
555                transform,
556            ),
557        }
558    }
559
560    fn read_pixels(&mut self, read_rect: Option<Rect<u32>>) -> Snapshot {
561        match self {
562            #[cfg(feature = "vello")]
563            Canvas::Vello(canvas_data) => canvas_data.read_pixels(read_rect),
564            #[cfg(feature = "vello_cpu")]
565            Canvas::VelloCPU(canvas_data) => canvas_data.read_pixels(read_rect),
566        }
567    }
568
569    fn clip_path(&mut self, path: &Path, fill_rule: FillRule, transform: Transform2D<f64>) {
570        match self {
571            #[cfg(feature = "vello")]
572            Canvas::Vello(canvas_data) => canvas_data.clip_path(path, fill_rule, transform),
573            #[cfg(feature = "vello_cpu")]
574            Canvas::VelloCPU(canvas_data) => canvas_data.clip_path(path, fill_rule, transform),
575        }
576    }
577
578    fn put_image_data(&mut self, snapshot: Snapshot, rect: Rect<u32>) {
579        match self {
580            #[cfg(feature = "vello")]
581            Canvas::Vello(canvas_data) => canvas_data.put_image_data(snapshot, rect),
582            #[cfg(feature = "vello_cpu")]
583            Canvas::VelloCPU(canvas_data) => canvas_data.put_image_data(snapshot, rect),
584        }
585    }
586
587    fn update_image_rendering(&mut self, canvas_epoch: Option<Epoch>) {
588        match self {
589            #[cfg(feature = "vello")]
590            Canvas::Vello(canvas_data) => canvas_data.update_image_rendering(canvas_epoch),
591            #[cfg(feature = "vello_cpu")]
592            Canvas::VelloCPU(canvas_data) => canvas_data.update_image_rendering(canvas_epoch),
593        }
594    }
595
596    fn recreate(&mut self, size: Option<Size2D<u64>>) {
597        match self {
598            #[cfg(feature = "vello")]
599            Canvas::Vello(canvas_data) => canvas_data.recreate(size),
600            #[cfg(feature = "vello_cpu")]
601            Canvas::VelloCPU(canvas_data) => canvas_data.recreate(size),
602        }
603    }
604}