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