Skip to main content

servo_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 crossbeam_channel::{Sender, select, unbounded};
9use euclid::default::{Rect, Size2D, Transform2D};
10use log::warn;
11use paint_api::CrossProcessPaintApi;
12use pixels::Snapshot;
13use rustc_hash::FxHashMap;
14use servo_base::generic_channel::GenericSender;
15use servo_base::{Epoch, generic_channel};
16use servo_canvas_traits::ConstellationCanvasMsg;
17use servo_canvas_traits::canvas::*;
18use webrender_api::ImageKey;
19
20use crate::canvas_data::*;
21
22pub struct CanvasPaintThread {
23    canvases: FxHashMap<CanvasId, Canvas>,
24    next_canvas_id: CanvasId,
25    paint_api: CrossProcessPaintApi,
26}
27
28impl CanvasPaintThread {
29    fn new(paint_api: CrossProcessPaintApi) -> CanvasPaintThread {
30        CanvasPaintThread {
31            canvases: FxHashMap::default(),
32            next_canvas_id: CanvasId(0),
33            paint_api,
34        }
35    }
36
37    /// Creates a new `CanvasPaintThread` and returns an `IpcSender` to
38    /// communicate with it.
39    pub fn start(
40        paint_api: CrossProcessPaintApi,
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                    paint_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.paint_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
293enum Canvas {
294    #[cfg(feature = "vello")]
295    Vello(CanvasData<crate::vello_backend::VelloDrawTarget>),
296    VelloCPU(CanvasData<crate::vello_cpu_backend::VelloCPUDrawTarget>),
297}
298
299impl Canvas {
300    fn new(size: Size2D<u64>, paint_api: CrossProcessPaintApi) -> Option<Self> {
301        match servo_config::pref!(dom_canvas_backend)
302            .to_lowercase()
303            .as_str()
304        {
305            #[cfg(feature = "vello")]
306            "vello" => Some(Self::Vello(CanvasData::new(size, paint_api))),
307            _ => Some(Self::VelloCPU(CanvasData::new(size, paint_api))),
308        }
309    }
310
311    fn set_image_key(&mut self, image_key: ImageKey) {
312        match self {
313            #[cfg(feature = "vello")]
314            Canvas::Vello(canvas_data) => canvas_data.set_image_key(image_key),
315            Canvas::VelloCPU(canvas_data) => canvas_data.set_image_key(image_key),
316        }
317    }
318
319    fn pop_clips(&mut self, clips: usize) {
320        match self {
321            #[cfg(feature = "vello")]
322            Canvas::Vello(canvas_data) => canvas_data.pop_clips(clips),
323            Canvas::VelloCPU(canvas_data) => canvas_data.pop_clips(clips),
324        }
325    }
326
327    fn stroke_text(
328        &mut self,
329        text_bounds: Rect<f64>,
330        text_runs: Vec<TextRun>,
331        fill_or_stroke_style: FillOrStrokeStyle,
332        line_options: LineOptions,
333        shadow_options: ShadowOptions,
334        composition_options: CompositionOptions,
335        transform: Transform2D<f64>,
336    ) {
337        match self {
338            #[cfg(feature = "vello")]
339            Canvas::Vello(canvas_data) => canvas_data.stroke_text(
340                text_bounds,
341                text_runs,
342                fill_or_stroke_style,
343                line_options,
344                shadow_options,
345                composition_options,
346                transform,
347            ),
348            Canvas::VelloCPU(canvas_data) => canvas_data.stroke_text(
349                text_bounds,
350                text_runs,
351                fill_or_stroke_style,
352                line_options,
353                shadow_options,
354                composition_options,
355                transform,
356            ),
357        }
358    }
359
360    fn fill_text(
361        &mut self,
362        text_bounds: Rect<f64>,
363        text_runs: Vec<TextRun>,
364        fill_or_stroke_style: FillOrStrokeStyle,
365        shadow_options: ShadowOptions,
366        composition_options: CompositionOptions,
367        transform: Transform2D<f64>,
368    ) {
369        match self {
370            #[cfg(feature = "vello")]
371            Canvas::Vello(canvas_data) => canvas_data.fill_text(
372                text_bounds,
373                text_runs,
374                fill_or_stroke_style,
375                shadow_options,
376                composition_options,
377                transform,
378            ),
379            Canvas::VelloCPU(canvas_data) => canvas_data.fill_text(
380                text_bounds,
381                text_runs,
382                fill_or_stroke_style,
383                shadow_options,
384                composition_options,
385                transform,
386            ),
387        }
388    }
389
390    fn fill_rect(
391        &mut self,
392        rect: &Rect<f32>,
393        style: FillOrStrokeStyle,
394        shadow_options: ShadowOptions,
395        composition_options: CompositionOptions,
396        transform: Transform2D<f64>,
397    ) {
398        match self {
399            #[cfg(feature = "vello")]
400            Canvas::Vello(canvas_data) => {
401                canvas_data.fill_rect(rect, style, shadow_options, composition_options, transform)
402            },
403            Canvas::VelloCPU(canvas_data) => {
404                canvas_data.fill_rect(rect, style, shadow_options, composition_options, transform)
405            },
406        }
407    }
408
409    fn stroke_rect(
410        &mut self,
411        rect: &Rect<f32>,
412        style: FillOrStrokeStyle,
413        line_options: LineOptions,
414        shadow_options: ShadowOptions,
415        composition_options: CompositionOptions,
416        transform: Transform2D<f64>,
417    ) {
418        match self {
419            #[cfg(feature = "vello")]
420            Canvas::Vello(canvas_data) => canvas_data.stroke_rect(
421                rect,
422                style,
423                line_options,
424                shadow_options,
425                composition_options,
426                transform,
427            ),
428            Canvas::VelloCPU(canvas_data) => canvas_data.stroke_rect(
429                rect,
430                style,
431                line_options,
432                shadow_options,
433                composition_options,
434                transform,
435            ),
436        }
437    }
438
439    fn fill_path(
440        &mut self,
441        path: &Path,
442        fill_rule: FillRule,
443        style: FillOrStrokeStyle,
444        shadow_options: ShadowOptions,
445        composition_options: CompositionOptions,
446        transform: Transform2D<f64>,
447    ) {
448        match self {
449            #[cfg(feature = "vello")]
450            Canvas::Vello(canvas_data) => canvas_data.fill_path(
451                path,
452                fill_rule,
453                style,
454                shadow_options,
455                composition_options,
456                transform,
457            ),
458            Canvas::VelloCPU(canvas_data) => canvas_data.fill_path(
459                path,
460                fill_rule,
461                style,
462                shadow_options,
463                composition_options,
464                transform,
465            ),
466        }
467    }
468
469    fn stroke_path(
470        &mut self,
471        path: &Path,
472        style: FillOrStrokeStyle,
473        line_options: LineOptions,
474        shadow_options: ShadowOptions,
475        composition_options: CompositionOptions,
476        transform: Transform2D<f64>,
477    ) {
478        match self {
479            #[cfg(feature = "vello")]
480            Canvas::Vello(canvas_data) => canvas_data.stroke_path(
481                path,
482                style,
483                line_options,
484                shadow_options,
485                composition_options,
486                transform,
487            ),
488            Canvas::VelloCPU(canvas_data) => canvas_data.stroke_path(
489                path,
490                style,
491                line_options,
492                shadow_options,
493                composition_options,
494                transform,
495            ),
496        }
497    }
498
499    fn clear_rect(&mut self, rect: &Rect<f32>, transform: Transform2D<f64>) {
500        match self {
501            #[cfg(feature = "vello")]
502            Canvas::Vello(canvas_data) => canvas_data.clear_rect(rect, transform),
503            Canvas::VelloCPU(canvas_data) => canvas_data.clear_rect(rect, transform),
504        }
505    }
506
507    #[expect(clippy::too_many_arguments)]
508    fn draw_image(
509        &mut self,
510        snapshot: Snapshot,
511        dest_rect: Rect<f64>,
512        source_rect: Rect<f64>,
513        smoothing_enabled: bool,
514        shadow_options: ShadowOptions,
515        composition_options: CompositionOptions,
516        transform: Transform2D<f64>,
517    ) {
518        match self {
519            #[cfg(feature = "vello")]
520            Canvas::Vello(canvas_data) => canvas_data.draw_image(
521                snapshot,
522                dest_rect,
523                source_rect,
524                smoothing_enabled,
525                shadow_options,
526                composition_options,
527                transform,
528            ),
529            Canvas::VelloCPU(canvas_data) => canvas_data.draw_image(
530                snapshot,
531                dest_rect,
532                source_rect,
533                smoothing_enabled,
534                shadow_options,
535                composition_options,
536                transform,
537            ),
538        }
539    }
540
541    fn read_pixels(&mut self, read_rect: Option<Rect<u32>>) -> Snapshot {
542        match self {
543            #[cfg(feature = "vello")]
544            Canvas::Vello(canvas_data) => canvas_data.read_pixels(read_rect),
545            Canvas::VelloCPU(canvas_data) => canvas_data.read_pixels(read_rect),
546        }
547    }
548
549    fn clip_path(&mut self, path: &Path, fill_rule: FillRule, transform: Transform2D<f64>) {
550        match self {
551            #[cfg(feature = "vello")]
552            Canvas::Vello(canvas_data) => canvas_data.clip_path(path, fill_rule, transform),
553            Canvas::VelloCPU(canvas_data) => canvas_data.clip_path(path, fill_rule, transform),
554        }
555    }
556
557    fn put_image_data(&mut self, snapshot: Snapshot, rect: Rect<u32>) {
558        match self {
559            #[cfg(feature = "vello")]
560            Canvas::Vello(canvas_data) => canvas_data.put_image_data(snapshot, rect),
561            Canvas::VelloCPU(canvas_data) => canvas_data.put_image_data(snapshot, rect),
562        }
563    }
564
565    fn update_image_rendering(&mut self, canvas_epoch: Option<Epoch>) {
566        match self {
567            #[cfg(feature = "vello")]
568            Canvas::Vello(canvas_data) => canvas_data.update_image_rendering(canvas_epoch),
569            Canvas::VelloCPU(canvas_data) => canvas_data.update_image_rendering(canvas_epoch),
570        }
571    }
572
573    fn recreate(&mut self, size: Option<Size2D<u64>>) {
574        match self {
575            #[cfg(feature = "vello")]
576            Canvas::Vello(canvas_data) => canvas_data.recreate(size),
577            Canvas::VelloCPU(canvas_data) => canvas_data.recreate(size),
578        }
579    }
580}