1use 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 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}