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