1use pax_runtime_api::{OpacityScope, Paint, ResolvedStroke, StrokeCap, StrokeJoin};
2use piet::{
3 kurbo::{self, Affine, Shape},
4 FixedRadialGradient, LineCap, LineJoin, RenderContext as _, StrokeStyle,
5};
6use std::{
7 cell::RefCell,
8 collections::{HashMap, HashSet},
9 rc::Rc,
10};
11
12use super::layer_surface::{
13 replay_batches_by_directional_priority, surface_intersects_coverage_bounds, LayerSurfaceEntry,
14 LayerSurfaceLayout, LayoutChangeKind, ReplayPriorityEntry, SurfaceReplayCoordinator,
15};
16#[cfg(debug_assertions)]
17use super::layer_surface::{visible_surface_escape, VisibleSurfaceEscape};
18use crate::api;
19
20#[cfg(test)]
21#[path = "piet_compositing_tests.rs"]
22mod compositing_tests;
23
24#[cfg(debug_assertions)]
25fn log_visible_surface_escape(layer: usize, escape: VisibleSurfaceEscape) {
26 log::warn!(
27 "[pax-tile-window-escape] backend=piet layer={} visible_surfaces={} escaped_visible_surfaces={} max_gap_x={:.1} max_gap_y={:.1} previous_bounds={} visible_bounds={}",
28 layer,
29 escape.visible_surfaces,
30 escape.escaped_visible_surfaces,
31 escape.max_gap_x,
32 escape.max_gap_y,
33 format_rect(escape.previous_bounds),
34 format_rect(escape.visible_bounds),
35 );
36}
37
38#[cfg(debug_assertions)]
39fn format_rect(bounds: kurbo::Rect) -> String {
40 format!(
41 "{:.1},{:.1}-{:.1},{:.1}",
42 bounds.x0, bounds.y0, bounds.x1, bounds.y1
43 )
44}
45
46struct ImgData<R: piet::RenderContext> {
47 img: R::Image,
48 size: (usize, usize),
49}
50
51pub trait PietSurface: Sized {
54 type Context: piet::RenderContext;
55
56 fn context(&mut self) -> &mut Self::Context;
57 fn clear(&mut self);
58 fn configure(&mut self, origin: (f32, f32), size: (u32, u32), dpr: [f32; 2]);
59 fn create_group(&self) -> Self;
61 fn composite(&mut self, source: &Self, opacity: f64);
64 fn draw_blend(
66 &mut self,
67 path: &kurbo::BezPath,
68 terms: &[(Paint, f64)],
69 opacity: f64,
70 paint_bounds: kurbo::Rect,
71 );
72 fn draw_image(
73 &mut self,
74 image: &<Self::Context as piet::RenderContext>::Image,
75 rect: kurbo::Rect,
76 opacity: f64,
77 );
78}
79
80type LayerDef<S> = (PietLayerTarget<S>, Box<dyn Fn() -> LayerSurfaceLayout>);
81
82#[derive(Clone, Default)]
83struct DrawState {
84 transform: Affine,
85 clips: Vec<Rc<kurbo::BezPath>>,
88}
89
90enum PietPaint<I> {
91 Fill(kurbo::BezPath, piet::PaintBrush),
92 Blend(kurbo::BezPath, Vec<(Paint, f64)>, f64, kurbo::Rect),
93 ResolvedStroke(kurbo::BezPath, piet::PaintBrush, f64, StrokeStyle),
94 Image(I, kurbo::Rect, f64),
95}
96
97struct PietDraw<I> {
98 state: DrawState,
99 scopes: Vec<OpacityScope>,
100 paint: PietPaint<I>,
101}
102
103struct GroupSurface<S> {
106 surface: S,
107 child: Option<Box<GroupSurface<S>>>,
108}
109
110fn paint_draws<S: PietSurface>(
111 surface: &mut S,
112 scratch: &mut Option<Box<GroupSurface<S>>>,
113 draws: &[PietDraw<<S::Context as piet::RenderContext>::Image>],
114 depth: usize,
115) {
116 let mut index = 0;
117 while index < draws.len() {
118 if let Some(scope) = draws[index].scopes.get(depth) {
119 let end = index
120 + draws[index..]
121 .iter()
122 .take_while(|draw| {
123 draw.scopes
124 .get(depth)
125 .is_some_and(|next| next.node_id == scope.node_id)
126 })
127 .count();
128 let opacity = scope.opacity.clamp(0.0, 1.0) as f64;
129 if opacity == 1.0 {
130 paint_draws(surface, scratch, &draws[index..end], depth + 1);
131 } else if opacity > 0.0 {
132 let group = scratch.get_or_insert_with(|| {
133 Box::new(GroupSurface {
134 surface: surface.create_group(),
135 child: None,
136 })
137 });
138 group.surface.clear();
139 paint_draws(
140 &mut group.surface,
141 &mut group.child,
142 &draws[index..end],
143 depth + 1,
144 );
145 surface.composite(&group.surface, opacity);
146 }
147 index = end;
148 continue;
149 }
150 let draw = &draws[index];
151 let context = surface.context();
152 let _ = context.save();
153 for clip in &draw.state.clips {
154 context.clip(clip.as_ref());
155 }
156 context.transform(draw.state.transform);
157 match &draw.paint {
158 PietPaint::Fill(path, brush) => context.fill(path, brush),
159 PietPaint::Blend(path, terms, opacity, bounds) => {
160 surface.draw_blend(path, terms, *opacity, *bounds)
161 }
162 PietPaint::ResolvedStroke(path, brush, width, style) => {
163 context.stroke_styled(path, brush, *width, style)
164 }
165 PietPaint::Image(image, rect, opacity) => surface.draw_image(image, *rect, *opacity),
166 }
167 let _ = surface.context().restore();
168 index += 1;
169 }
170}
171
172pub struct PietLayerRenderer<S: PietSurface> {
174 key: String,
175 host_signature: String,
176 surface: S,
177 scratch: Option<Box<GroupSurface<S>>>,
178 state: DrawState,
179 saves: Vec<DrawState>,
180 scopes: Vec<OpacityScope>,
181 draws: Vec<PietDraw<<S::Context as piet::RenderContext>::Image>>,
182 origin_x: f32,
183 origin_y: f32,
184 logical_width: f32,
185 logical_height: f32,
186 surface_width: u32,
187 surface_height: u32,
188 dpr: [f32; 2],
189}
190
191impl<S: PietSurface> PietLayerRenderer<S> {
192 pub fn new(
193 key: String,
194 host_signature: String,
195 canvas: S,
196 surface: &LayerSurfaceEntry,
197 ) -> Self {
198 Self {
199 key,
200 host_signature,
201 surface: canvas,
202 scratch: None,
203 state: DrawState::default(),
204 saves: Vec::new(),
205 scopes: Vec::new(),
206 draws: Vec::new(),
207 origin_x: surface.origin_x,
208 origin_y: surface.origin_y,
209 logical_width: surface.surface.logical_width,
210 logical_height: surface.surface.logical_height,
211 surface_width: surface.surface.surface_width,
212 surface_height: surface.surface.surface_height,
213 dpr: surface.surface.dpr,
214 }
215 }
216
217 fn context_mut(&mut self) -> &mut S::Context {
218 self.surface.context()
219 }
220
221 fn clear(&mut self) {
222 self.surface.clear();
223 self.draws.clear();
224 self.state = DrawState::default();
225 self.saves.clear();
226 self.scopes.clear();
227 }
228
229 fn record(&mut self, paint: PietPaint<<S::Context as piet::RenderContext>::Image>) {
230 self.draws.push(PietDraw {
231 state: self.state.clone(),
232 scopes: self.scopes.clone(),
233 paint,
234 });
235 }
236
237 fn flush(&mut self) {
238 paint_draws(&mut self.surface, &mut self.scratch, &self.draws, 0);
239 self.draws.clear();
240 }
241
242 fn intersects_coverage_bounds(&self, bounds: &kurbo::Rect) -> bool {
243 surface_intersects_coverage_bounds(
244 bounds,
245 self.origin_x as f64,
246 self.origin_y as f64,
247 self.logical_width as f64,
248 self.logical_height as f64,
249 )
250 }
251
252 fn coverage_bounds(&self) -> kurbo::Rect {
253 kurbo::Rect::new(
254 self.origin_x as f64,
255 self.origin_y as f64,
256 self.origin_x as f64 + self.logical_width as f64,
257 self.origin_y as f64 + self.logical_height as f64,
258 )
259 }
260
261 fn update_layout(&mut self, surface: &LayerSurfaceEntry) -> LayoutChangeKind {
262 let origin_changed = (self.origin_x - surface.origin_x).abs() > f32::EPSILON
263 || (self.origin_y - surface.origin_y).abs() > f32::EPSILON;
264 let size_changed = self.logical_width != surface.surface.logical_width
265 || self.logical_height != surface.surface.logical_height
266 || self.surface_width != surface.surface.surface_width
267 || self.surface_height != surface.surface.surface_height
268 || self.dpr != surface.surface.dpr;
269
270 self.origin_x = surface.origin_x;
271 self.origin_y = surface.origin_y;
272 self.logical_width = surface.surface.logical_width;
273 self.logical_height = surface.surface.logical_height;
274 self.surface_width = surface.surface.surface_width;
275 self.surface_height = surface.surface.surface_height;
276 self.dpr = surface.surface.dpr;
277
278 if size_changed || origin_changed {
279 self.surface.configure(
280 (self.origin_x, self.origin_y),
281 (self.surface_width, self.surface_height),
282 self.dpr,
283 );
284 self.scratch = None;
285 }
286
287 if size_changed {
288 self.clear();
289 }
290
291 if size_changed {
292 LayoutChangeKind::Resized
293 } else if origin_changed {
294 LayoutChangeKind::OriginOnly
295 } else {
296 LayoutChangeKind::Unchanged
297 }
298 }
299}
300
301pub struct PietLayerTarget<S: PietSurface> {
303 renderers: Vec<PietLayerRenderer<S>>,
304 active: bool,
305}
306
307impl<S: PietSurface> PietLayerTarget<S> {
308 pub fn new(renderers: Vec<PietLayerRenderer<S>>, active: bool) -> Self {
309 Self { renderers, active }
310 }
311
312 fn activate(&mut self) -> bool {
313 let changed = !self.active;
314 self.active = true;
315 changed
316 }
317
318 fn deactivate(&mut self) {
319 self.active = false;
320 }
321}
322
323pub struct PietRenderer<S: PietSurface> {
325 layers: Vec<LayerDef<S>>,
326 image_map: HashMap<String, ImgData<S::Context>>,
327 layer_factory: Box<dyn Fn(usize) -> LayerDef<S>>,
328 ready_layers: Vec<usize>,
329 replay_layers: Vec<usize>,
330 surface_replay: SurfaceReplayCoordinator,
331 active_render_scopes: Vec<Vec<Vec<usize>>>,
332 clean_skipped_canvas_nodes: HashSet<(usize, u32)>,
333}
334
335impl<S: PietSurface> PietRenderer<S> {
336 pub fn new(layer_factory: impl Fn(usize) -> LayerDef<S> + 'static) -> Self {
338 Self {
339 layer_factory: Box::new(layer_factory),
340 layers: Vec::new(),
341 image_map: HashMap::new(),
342 ready_layers: Vec::new(),
343 replay_layers: Vec::new(),
344 surface_replay: SurfaceReplayCoordinator::default(),
345 active_render_scopes: Vec::new(),
346 clean_skipped_canvas_nodes: HashSet::new(),
347 }
348 }
349
350 fn first_context_mut(&mut self) -> Option<&mut S::Context> {
351 self.layers
352 .iter_mut()
353 .find_map(|(target, _)| target.renderers.first_mut())
354 .map(PietLayerRenderer::context_mut)
355 }
356
357 fn with_layer_renderer(&mut self, layer: usize, mut f: impl FnMut(&mut PietLayerRenderer<S>)) {
358 let scoped_indices = self
359 .active_render_scopes
360 .get(layer)
361 .and_then(|scopes| scopes.last())
362 .cloned();
363
364 let Some((target, _)) = self.layers.get_mut(layer) else {
365 return;
366 };
367 if !target.active {
368 return;
369 }
370
371 if let Some(indices) = scoped_indices {
372 for index in indices {
373 if let Some(renderer) = target.renderers.get_mut(index) {
374 f(renderer);
375 }
376 }
377 } else {
378 for renderer in &mut target.renderers {
379 f(renderer);
380 }
381 }
382 }
383
384 fn push_render_scope(&mut self, layer: usize, renderer_indices: Vec<usize>) {
385 if self.active_render_scopes.len() <= layer {
386 self.active_render_scopes.resize_with(layer + 1, Vec::new);
387 }
388 self.active_render_scopes[layer].push(renderer_indices);
389 }
390
391 fn pop_render_scope(&mut self, layer: usize) {
392 if let Some(scopes) = self.active_render_scopes.get_mut(layer) {
393 scopes.pop();
394 }
395 }
396
397 fn take_replay_layer_ids(&mut self) -> Vec<usize> {
398 let mut replay = std::mem::take(&mut self.replay_layers);
399 replay.sort_unstable();
400 replay.dedup();
401 replay
402 }
403
404 fn targeted_or_all_indices(&self, layer: usize, renderer_count: usize) -> Vec<usize> {
405 self.surface_replay
406 .targeted_or_all_indices(layer, renderer_count)
407 }
408
409 fn clear_targeted_replay_scope(&mut self, layer: usize) {
410 self.surface_replay.clear_targeted_replay_scope(layer);
411 }
412
413 fn set_targeted_replay_batches(
414 &mut self,
415 layer: usize,
416 batches: Vec<Vec<usize>>,
417 bounds_by_surface: HashMap<usize, Vec<kurbo::Rect>>,
418 ) {
419 self.surface_replay
420 .set_targeted_replay_batches(layer, batches, bounds_by_surface);
421 }
422
423 fn advance_targeted_replay_queue(&mut self, layer: usize) -> bool {
424 self.surface_replay.advance_targeted_replay_queue(layer)
425 }
426
427 fn targeted_replay_surface_bounds(
428 &self,
429 layer: usize,
430 target: &PietLayerTarget<S>,
431 ) -> Option<Vec<kurbo::Rect>> {
432 self.surface_replay
433 .targeted_replay_surface_bounds(layer, |index| {
434 target
435 .renderers
436 .get(index)
437 .map(PietLayerRenderer::coverage_bounds)
438 })
439 }
440
441 fn replay_region_bounds(&self, layer: usize) -> Option<Vec<kurbo::Rect>> {
442 let Some((target, _)) = self.layers.get(layer) else {
443 return None;
444 };
445 self.targeted_replay_surface_bounds(layer, target)
446 }
447
448 fn refresh_layer_layouts<I>(&mut self, layer_indices: I)
449 where
450 I: IntoIterator<Item = usize>,
451 {
452 for layer_index in layer_indices {
453 let Some((target, layout_provider)) = self.layers.get_mut(layer_index) else {
454 continue;
455 };
456 let layout = (layout_provider)();
457 let layout_matches = layer_layout_matches_target(target, &layout);
458
459 if !layout.active {
460 target.deactivate();
461 self.clear_targeted_replay_scope(layer_index);
462 continue;
463 }
464
465 if !layout_matches {
466 self.clear_targeted_replay_scope(layer_index);
467 let (new_target, new_provider) = (self.layer_factory)(layer_index);
468 let has_surfaces = !new_target.renderers.is_empty();
469 self.layers[layer_index] = (new_target, new_provider);
470 if has_surfaces {
471 self.ready_layers.push(layer_index);
472 self.replay_layers.push(layer_index);
473 }
474 continue;
475 }
476
477 let activated_replay = target.activate() && !target.renderers.is_empty();
478
479 let mut targeted_replay_entries = Vec::new();
480 let mut targeted_replay_bounds = HashMap::new();
481 let mut needs_full_layer_replay = false;
482 #[cfg(debug_assertions)]
483 {
484 let previous_surface_bounds: Vec<_> = target
485 .renderers
486 .iter()
487 .map(PietLayerRenderer::coverage_bounds)
488 .collect();
489 if let Some(escape) =
490 visible_surface_escape(&previous_surface_bounds, &layout.surfaces)
491 {
492 log_visible_surface_escape(layer_index, escape);
493 }
494 }
495 for (index, (surface, renderer)) in layout
496 .surfaces
497 .iter()
498 .zip(target.renderers.iter_mut())
499 .enumerate()
500 {
501 let previous_bounds = renderer.coverage_bounds();
502 let layout_change = renderer.update_layout(surface);
503 let current_bounds = renderer.coverage_bounds();
504 match layout_change {
505 LayoutChangeKind::Unchanged => {}
506 LayoutChangeKind::OriginOnly => {
507 targeted_replay_entries.push(ReplayPriorityEntry::new(
508 index,
509 surface.replay_priority,
510 previous_bounds,
511 current_bounds,
512 ));
513 targeted_replay_bounds.insert(index, vec![previous_bounds, current_bounds]);
514 }
515 LayoutChangeKind::Resized => {
516 needs_full_layer_replay = true;
517 }
518 }
519 }
520 if activated_replay || needs_full_layer_replay {
521 self.clear_targeted_replay_scope(layer_index);
522 self.replay_layers.push(layer_index);
523 } else if !targeted_replay_entries.is_empty() {
524 self.set_targeted_replay_batches(
525 layer_index,
526 replay_batches_by_directional_priority(&targeted_replay_entries),
527 targeted_replay_bounds,
528 );
529 self.replay_layers.push(layer_index);
530 }
531 }
532 }
533}
534
535fn layer_layout_matches_target<S: PietSurface>(
536 target: &PietLayerTarget<S>,
537 layout: &LayerSurfaceLayout,
538) -> bool {
539 layout.surfaces.len() == target.renderers.len()
540 && layout
541 .surfaces
542 .iter()
543 .zip(target.renderers.iter())
544 .all(|(surface, renderer)| {
545 surface.key == renderer.key && surface.host_signature == renderer.host_signature
546 })
547}
548
549#[cfg(test)]
550mod tests {
551 use super::replay_batches_by_directional_priority;
552 use crate::engine::layer_surface::ReplayPriorityEntry;
553 use kurbo::Rect;
554
555 #[test]
556 fn piet_replay_batches_coalesce_visible_surfaces_first() {
557 let batches = replay_batches_by_directional_priority(&[
558 replay_entry(0, 0, 0.0, 100.0),
559 replay_entry(1, 0, 100.0, 200.0),
560 replay_entry(2, 1, 200.0, 300.0),
561 replay_entry(3, 1, -100.0, 0.0),
562 ]);
563
564 assert_eq!(batches[0], vec![0, 1]);
565 assert_eq!(batches[1], vec![2]);
566 assert_eq!(batches[2], vec![3]);
567 }
568
569 #[test]
570 fn piet_replay_batches_keep_warm_only_directional_batches() {
571 let batches = replay_batches_by_directional_priority(&[
572 replay_entry(0, 1, 0.0, 100.0),
573 replay_entry(1, 1, 100.0, 200.0),
574 replay_entry(2, 1, -100.0, 0.0),
575 ]);
576
577 assert_eq!(batches, vec![vec![1], vec![0], vec![2]]);
578 }
579
580 fn replay_entry(
581 index: usize,
582 priority: i32,
583 previous_y: f64,
584 current_y: f64,
585 ) -> ReplayPriorityEntry {
586 ReplayPriorityEntry::new(
587 index,
588 priority,
589 Rect::new(0.0, previous_y, 100.0, previous_y + 100.0),
590 Rect::new(0.0, current_y, 100.0, current_y + 100.0),
591 )
592 }
593}
594
595impl<S: PietSurface> api::RenderContext for PietRenderer<S> {
596 fn fill_with_opacity(
597 &mut self,
598 layer: usize,
599 path: kurbo::BezPath,
600 fill: &Paint,
601 opacity: f64,
602 ) {
603 if let Paint::Blend(terms) = fill {
604 self.with_layer_renderer(layer, |renderer| {
605 renderer.record(PietPaint::Blend(
606 path.clone(),
607 terms.clone(),
608 opacity,
609 path.bounding_box(),
610 ))
611 });
612 } else if let Some(brush) =
613 fill_to_piet_brush(&fill.with_alpha_factor(opacity), path.bounding_box())
614 {
615 self.with_layer_renderer(layer, |renderer| {
616 renderer.record(PietPaint::Fill(path.clone(), brush.clone()))
617 });
618 }
619 }
620
621 fn stroke_with_opacity(
622 &mut self,
623 layer: usize,
624 path: kurbo::BezPath,
625 stroke: &ResolvedStroke,
626 opacity: f64,
627 ) {
628 self.stroke_with_draw_range_and_material_and_opacity(
629 layer,
630 path,
631 stroke,
632 &stroke.material,
633 opacity,
634 0.0,
635 1.0,
636 );
637 }
638
639 fn stroke_with_draw_range_and_material_and_opacity(
640 &mut self,
641 layer: usize,
642 path: kurbo::BezPath,
643 stroke: &ResolvedStroke,
644 _material: &api::ResolvedMaterial,
645 opacity: f64,
646 draw_start: f64,
647 draw_end: f64,
648 ) {
649 if draw_start >= draw_end || stroke.width_pixels() <= f64::EPSILON {
650 return;
651 }
652 let bounds = path.bounding_box();
653 let path = api::drawing::path_trim::trim_bez_path(&path, draw_start, draw_end);
654 let paint = stroke.paint.clone();
655 if let Paint::Blend(terms) = paint {
656 if let Some(outline) = api::drawing::stroke_utils::stroked_outline_path(&path, stroke) {
657 self.with_layer_renderer(layer, |renderer| {
658 renderer.record(PietPaint::Blend(
659 outline.clone(),
660 terms.clone(),
661 opacity,
662 bounds,
663 ))
664 });
665 }
666 } else if let Some(brush) = fill_to_piet_brush(&paint.with_alpha_factor(opacity), bounds) {
667 self.with_layer_renderer(layer, |renderer| {
668 renderer.record(PietPaint::ResolvedStroke(
669 path.clone(),
670 brush.clone(),
671 stroke.width_pixels(),
672 stroke_to_piet_style(stroke),
673 ))
674 });
675 }
676 }
677
678 fn save(&mut self, layer: usize) {
679 self.with_layer_renderer(layer, |renderer| {
680 renderer.saves.push(renderer.state.clone())
681 });
682 }
683
684 fn transform(&mut self, layer: usize, affine: Affine) {
685 self.with_layer_renderer(layer, |renderer| renderer.state.transform *= affine);
686 }
687
688 fn clip(&mut self, layer: usize, path: kurbo::BezPath) {
689 self.with_layer_renderer(layer, |renderer| {
690 renderer
691 .state
692 .clips
693 .push(Rc::new(renderer.state.transform * &path))
694 });
695 }
696
697 fn restore(&mut self, layer: usize) {
698 self.with_layer_renderer(layer, |renderer| {
699 if let Some(state) = renderer.saves.pop() {
700 renderer.state = state;
701 }
702 });
703 }
704
705 fn load_image(&mut self, path: &str, buf: &[u8], width: usize, height: usize) {
706 let Some(render_context) = self.first_context_mut() else {
707 return;
708 };
709 let img = render_context
710 .make_image(width, height, buf, piet::ImageFormat::RgbaSeparate)
711 .expect("image creation successful");
712 self.image_map.insert(
713 path.to_owned(),
714 ImgData {
715 img,
716 size: (width, height),
717 },
718 );
719 }
720
721 fn get_image_size(&mut self, image_path: &str) -> Option<(usize, usize)> {
722 self.image_map.get(image_path).map(|img| img.size)
723 }
724
725 fn draw_image_with_opacity(
726 &mut self,
727 layer: usize,
728 image_path: &str,
729 rect: kurbo::Rect,
730 opacity: f64,
731 ) {
732 let Some(data) = self.image_map.get(image_path) else {
733 return;
734 };
735 let image = data.img.clone();
736 self.with_layer_renderer(layer, |renderer| {
737 renderer.record(PietPaint::Image(
738 image.clone(),
739 rect,
740 opacity.clamp(0.0, 1.0),
741 ));
742 });
743 }
744
745 fn layers(&self) -> usize {
746 self.layers.len()
747 }
748
749 fn resize_layers_to(&mut self, layer_count: usize, dirty_canvases: Rc<RefCell<Vec<bool>>>) {
750 let current_len = self.layers.len();
751 match layer_count.cmp(¤t_len) {
752 std::cmp::Ordering::Less => {
753 self.layers.truncate(layer_count);
754 self.active_render_scopes.truncate(layer_count);
755 self.surface_replay.truncate_layers(layer_count);
756 }
757 std::cmp::Ordering::Equal => return,
758 std::cmp::Ordering::Greater => {
759 for i in current_len..layer_count {
760 let (target, provider) = (self.layer_factory)(i);
761 let has_surfaces = target.active && !target.renderers.is_empty();
762 self.layers.push((target, provider));
763 if let Some(dirty_bit) = dirty_canvases.borrow_mut().get_mut(i) {
764 *dirty_bit = true;
765 }
766 if has_surfaces {
767 self.ready_layers.push(i);
768 }
769 }
770 }
771 }
772 }
773
774 fn image_loaded(&self, path: &str) -> bool {
775 self.image_map.contains_key(path)
776 }
777
778 fn clear(&mut self, layer: usize) {
779 let scoped_indices = self
780 .active_render_scopes
781 .get(layer)
782 .and_then(|scopes| scopes.last())
783 .cloned()
784 .or_else(|| {
785 self.layers
786 .get(layer)
787 .map(|(target, _)| self.targeted_or_all_indices(layer, target.renderers.len()))
788 });
789 let Some((target, _)) = self.layers.get_mut(layer) else {
790 return;
791 };
792 if !target.active {
793 return;
794 }
795 if let Some(indices) = scoped_indices {
796 for index in indices {
797 if let Some(renderer) = target.renderers.get_mut(index) {
798 renderer.clear();
799 }
800 }
801 } else {
802 for renderer in &mut target.renderers {
803 renderer.clear();
804 }
805 }
806 }
807
808 fn supports_subtree_opacity(&self) -> bool {
809 true
810 }
811
812 fn set_node_opacity_scopes(&mut self, layer: usize, _node_id: u32, scopes: &[OpacityScope]) {
813 self.with_layer_renderer(layer, |renderer| renderer.scopes = scopes.to_vec());
814 }
815
816 fn flush(&mut self, layer: usize, _dirty_canvases: Rc<RefCell<Vec<bool>>>) {
817 self.with_layer_renderer(layer, |renderer| renderer.flush());
818 if self.advance_targeted_replay_queue(layer) {
819 self.replay_layers.push(layer);
820 }
821 }
822
823 fn resize(&mut self, _width: usize, _height: usize) {
824 let layer_count = self.layers.len();
825 self.refresh_layer_layouts(0..layer_count);
826 }
827
828 fn refresh_layers(&mut self, layers: &[usize]) {
829 self.refresh_layer_layouts(layers.iter().copied());
830 }
831
832 fn clear_targeted_replay(&mut self, layer: usize) {
833 self.clear_targeted_replay_scope(layer);
834 }
835
836 fn take_ready_canvas_layers(&mut self) -> Vec<usize> {
837 let mut ready = std::mem::take(&mut self.ready_layers);
838 ready.sort_unstable();
839 ready.dedup();
840 ready
841 }
842
843 fn take_replay_canvas_layer_updates(
844 &mut self,
845 ) -> Vec<pax_runtime_api::ReplayCanvasLayerUpdate> {
846 let replay_layers = self.take_replay_layer_ids();
847 replay_layers
848 .into_iter()
849 .map(|layer| pax_runtime_api::ReplayCanvasLayerUpdate {
850 layer,
851 regions: self.replay_region_bounds(layer),
852 })
853 .collect()
854 }
855
856 fn retains_canvas_nodes(&self) -> bool {
857 false
858 }
859
860 fn begin_node(&mut self, layer: usize, _node_id: u32, _z_index: i32, _light_mask: u32) -> bool {
861 let renderer_count = {
862 let Some((target, _)) = self.layers.get(layer) else {
863 return false;
864 };
865 if !target.active {
866 return false;
867 }
868 target.renderers.len()
869 };
870 let selected = self.targeted_or_all_indices(layer, renderer_count);
871 let began = !selected.is_empty();
872 if began {
873 self.push_render_scope(layer, selected);
874 }
875 began
876 }
877
878 fn begin_node_with_bounds(
879 &mut self,
880 layer: usize,
881 node_id: u32,
882 _z_index: i32,
883 coverage_bounds: kurbo::Rect,
884 _light_mask: u32,
885 ) -> bool {
886 let renderer_count = {
887 let Some((target, _)) = self.layers.get(layer) else {
888 return false;
889 };
890 if !target.active {
891 return false;
892 }
893 target.renderers.len()
894 };
895 let candidate_indices = self.targeted_or_all_indices(layer, renderer_count);
896 let Some((target, _)) = self.layers.get(layer) else {
897 return false;
898 };
899 let selected: Vec<_> = target
900 .renderers
901 .iter()
902 .enumerate()
903 .filter(|(index, _)| candidate_indices.contains(index))
904 .filter_map(|(index, renderer)| {
905 renderer
906 .intersects_coverage_bounds(&coverage_bounds)
907 .then_some(index)
908 })
909 .collect();
910 let began = !selected.is_empty();
911 if began {
912 self.push_render_scope(layer, selected);
913 } else {
914 self.clean_skipped_canvas_nodes.insert((layer, node_id));
915 }
916 began
917 }
918
919 fn take_clean_skipped_node(&mut self, layer: usize, node_id: u32) -> bool {
920 self.clean_skipped_canvas_nodes.remove(&(layer, node_id))
921 }
922
923 fn end_node(&mut self, layer: usize, _node_id: u32) -> bool {
924 self.pop_render_scope(layer);
925 true
926 }
927
928 fn remove_node(&mut self, _layer: usize, _node_id: u32) -> bool {
929 true
930 }
931}
932
933pub fn fill_to_piet_brush(fill: &Paint, rect: kurbo::Rect) -> Option<piet::PaintBrush> {
936 let rect = api::drawing::paint_bounds(rect)?;
937 Some(match fill {
938 Paint::MeshGradient(_) => {
939 static WARNING: std::sync::Once = std::sync::Once::new();
940 WARNING.call_once(|| {
941 log::warn!("Mesh-gradient paint requires WGPU; Piet paints no mesh field")
942 });
943 return None;
944 }
945 Paint::Blend(_) => return None,
946 Paint::Solid(color) => color.to_piet_color().into(),
947 Paint::LinearGradient(linear) => {
948 let bounds = (rect.width(), rect.height());
949 let point = |value: (api::Size, api::Size)| {
950 kurbo::Point::new(
951 rect.x0 + value.0.evaluate(bounds, api::Axis::X),
952 rect.y0 + value.1.evaluate(bounds, api::Axis::Y),
953 )
954 };
955 let start = point(linear.start);
956 let end = point(linear.end);
957 let length = start.distance(end);
958 if !length.is_finite() || length <= f64::EPSILON {
959 return Some(piet::Color::TRANSPARENT.into());
960 }
961 piet::FixedLinearGradient {
962 start,
963 end,
964 stops: linear
965 .stops
966 .iter()
967 .map(|stop| piet::GradientStop {
968 pos: (stop.position.evaluate((length, 0.0), api::Axis::X) / length) as f32,
969 color: stop.color.to_piet_color(),
970 })
971 .collect(),
972 }
973 .into()
974 }
975 Paint::RadialGradient(radial) => {
976 let Some(g) = radial.resolve_geometry(rect) else {
977 return Some(piet::Color::TRANSPARENT.into());
978 };
979 if radial.stops.is_empty() {
980 return Some(piet::Color::TRANSPARENT.into());
981 }
982 let stops = radial
983 .stops
984 .iter()
985 .map(|s| piet::GradientStop {
986 pos: (s
987 .position
988 .evaluate((g.radius, 0.0), pax_runtime_api::Axis::X)
989 / g.radius) as f32,
990 color: s.color.to_piet_color(),
991 })
992 .collect();
993 FixedRadialGradient {
994 center: g.center,
995 origin_offset: g.focal_point - g.center,
996 radius: g.radius,
997 stops,
998 }
999 .into()
1000 }
1001 })
1002}
1003
1004fn stroke_to_piet_style(stroke: &ResolvedStroke) -> StrokeStyle {
1005 let mut style = StrokeStyle::new();
1006 style.set_line_cap(match stroke.cap {
1007 StrokeCap::Butt => LineCap::Butt,
1008 StrokeCap::Round => LineCap::Round,
1009 StrokeCap::Square => LineCap::Square,
1010 });
1011 style.set_line_join(match stroke.join {
1012 StrokeJoin::Miter => LineJoin::Miter { limit: 4.0 },
1013 StrokeJoin::Round => LineJoin::Round,
1014 StrokeJoin::Bevel => LineJoin::Bevel,
1015 });
1016 style
1017}
1018
1019#[cfg(test)]
1020mod radial_tests {
1021 use super::*;
1022 use pax_runtime_api::{Color, GradientStop, RadialGradient, Size};
1023
1024 #[test]
1025 fn linear_brush_keeps_origin_and_pixel_stops_on_collapsed_axes() {
1026 let paint = Paint::linearGradient(
1027 (Size::Percent(0.into()), Size::Percent(50.into())),
1028 (Size::Percent(100.into()), Size::Percent(50.into())),
1029 vec![
1030 GradientStop::get(Color::RED, Size::Pixels(25.into())),
1031 GradientStop::get(Color::BLUE, Size::Percent(100.into())),
1032 ],
1033 );
1034 let Some(piet::PaintBrush::Fixed(piet::FixedGradient::Linear(g))) =
1035 fill_to_piet_brush(&paint, kurbo::Rect::new(40.0, 60.0, 140.0, 60.0))
1036 else {
1037 panic!("linear brush")
1038 };
1039 assert_eq!(g.start, kurbo::Point::new(40.0, 60.0));
1040 assert_eq!(g.end, kurbo::Point::new(140.0, 60.0));
1041 assert_eq!(g.stops[0].pos, 0.25);
1042 }
1043
1044 #[test]
1045 fn radial_brush_uses_local_bounds_radius_and_focal_point() {
1046 let mut radial = RadialGradient {
1047 start: (Size::Percent(75.into()), Size::Percent(25.into())),
1048 end: (Size::Percent(50.into()), Size::Percent(50.into())),
1049 radius: 50.0,
1050 stops: vec![
1051 GradientStop::get(Color::BLACK, Size::Pixels(25.into())),
1052 GradientStop::get(Color::WHITE, Size::Percent(100.into())),
1053 ],
1054 };
1055 let rect = kurbo::Rect::new(10.0, 20.0, 210.0, 120.0);
1056 let Some(piet::PaintBrush::Fixed(piet::FixedGradient::Radial(g))) =
1057 fill_to_piet_brush(&Paint::RadialGradient(radial.clone()), rect)
1058 else {
1059 panic!("radial brush")
1060 };
1061 assert_eq!(g.center, kurbo::Point::new(110.0, 70.0));
1062 assert_eq!(g.origin_offset, kurbo::Vec2::new(50.0, -25.0));
1063 assert_eq!(g.radius, 50.0);
1064 assert_eq!(
1065 g.stops.iter().map(|s| s.pos).collect::<Vec<_>>(),
1066 vec![0.5, 1.0]
1067 );
1068 for radius in [0.0, -1.0, f64::NAN, f64::INFINITY] {
1069 radial.radius = radius;
1070 assert!(radial.resolve_geometry(rect).is_none());
1071 assert!(
1072 matches!(fill_to_piet_brush(&Paint::RadialGradient(radial.clone()), rect),
1073 Some(piet::PaintBrush::Color(c)) if c == piet::Color::TRANSPARENT)
1074 );
1075 }
1076 }
1077}