1use std::fmt;
13use std::sync::Arc;
14
15use crate::composition::Surface;
16use crate::geometry::{
17 Clip, Color, Matrix4, Offset, RRect, Rect, rrect_radii_elliptical, transform3,
18};
19use crate::painting::{
20 Backdrop, BlendMode, Canvas, ClipOp, FillRule, ImageFilter, Paint, Path, Picture, valo,
21};
22
23#[derive(Debug, PartialEq)]
27pub struct Scene {
28 pub picture: Arc<Picture>,
29 pub surfaces: Vec<SurfacePlacement>,
30}
31
32#[derive(Clone)]
35pub struct SurfacePlacement {
36 pub surface: Arc<dyn Surface>,
37 pub rect: Rect,
40 pub scale: f64,
46 pub clip: Rect,
50}
51
52impl PartialEq for SurfacePlacement {
55 fn eq(&self, other: &SurfacePlacement) -> bool {
56 Arc::ptr_eq(&self.surface, &other.surface)
57 && self.rect == other.rect
58 && self.scale == other.scale
59 && self.clip == other.clip
60 }
61}
62
63impl fmt::Debug for SurfacePlacement {
64 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
65 f.debug_struct("SurfacePlacement")
66 .field("surface", &Arc::as_ptr(&self.surface).cast::<()>())
67 .field("rect", &self.rect)
68 .field("scale", &self.scale)
69 .field("clip", &self.clip)
70 .finish()
71 }
72}
73
74pub type EngineLayer = Arc<Picture>;
78
79pub struct SceneBuilder {
88 canvas: Canvas,
90 transform: Matrix4,
92 clip: Option<Rect>,
95 enclosing: Vec<EnclosingScope>,
98 surfaces: Vec<SurfacePlacement>,
100}
101
102struct EnclosingScope {
104 canvas: Canvas,
105 transform: Matrix4,
106 clip: Option<Rect>,
107}
108
109impl fmt::Debug for SceneBuilder {
110 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
111 f.debug_struct("SceneBuilder")
112 .field("open_scopes", &self.enclosing.len())
113 .finish_non_exhaustive()
114 }
115}
116
117impl Default for SceneBuilder {
118 fn default() -> SceneBuilder {
119 SceneBuilder::new()
120 }
121}
122
123impl SceneBuilder {
124 pub fn new() -> SceneBuilder {
126 SceneBuilder {
127 canvas: Canvas::new(),
128 transform: Matrix4::IDENTITY,
129 clip: None,
130 enclosing: Vec::new(),
131 surfaces: Vec::new(),
132 }
133 }
134
135 pub fn push_transform(&mut self, matrix4: &Matrix4) {
139 self.open(|canvas| {
140 canvas.save();
141 canvas.concat(matrix4);
142 });
143 self.transformed_by(matrix4);
144 }
145
146 pub fn push_offset(&mut self, dx: f64, dy: f64) {
151 self.open(|canvas| {
152 canvas.save();
153 canvas.translate(dx as f32, dy as f32);
154 });
155 self.transformed_by(&Matrix4::translation(dx as f32, dy as f32));
156 }
157
158 pub fn push_clip_rect(&mut self, rect: Rect, clip_behavior: Clip) {
162 self.open(|canvas| {
163 open_clip(canvas, clip_behavior, rect.into());
164 canvas.clip_rect(rect, ClipOp::Intersect);
165 });
166 self.clipped_to(rect);
167 }
168
169 pub fn push_clip_rrect(&mut self, rrect: RRect, clip_behavior: Clip) {
173 self.open(|canvas| {
174 open_clip(canvas, clip_behavior, rrect.outer_rect().into());
175 canvas.clip_rrect_radii_elliptical(
176 rrect.outer_rect(),
177 rrect_radii_elliptical(rrect),
178 ClipOp::Intersect,
179 );
180 });
181 self.clipped_to(rrect.outer_rect());
182 }
183
184 pub fn push_clip_path(&mut self, path: &Arc<Path>, clip_behavior: Clip) {
188 self.open(|canvas| {
189 open_clip(canvas, clip_behavior, path.bounds());
190 canvas.clip_path(path, FillRule::NonZero, ClipOp::Intersect);
191 });
192 self.clipped_to(bounds_of(path));
193 }
194
195 pub fn push_opacity(&mut self, alpha: i32, offset: Offset) {
201 self.open(|canvas| {
202 let paint = Paint {
203 color: Color::from_argb(alpha, 0, 0, 0).into(),
204 ..Paint::default()
205 };
206 canvas.save_layer(None, &paint);
207 canvas.translate(offset.dx() as f32, offset.dy() as f32);
208 });
209 self.transformed_by(&Matrix4::translation(
210 offset.dx() as f32,
211 offset.dy() as f32,
212 ));
213 }
214
215 pub fn push_backdrop_filter(
224 &mut self,
225 filter: &ImageFilter,
226 blend_mode: BlendMode,
227 backdrop_id: Option<u64>,
228 ) {
229 let backdrop = valo_backdrop(filter, backdrop_id);
230 let paint = Paint {
231 blend_mode,
232 ..Paint::default()
233 };
234 self.open(|canvas| canvas.save_layer_backdrop(None, &paint, backdrop));
235 }
236
237 pub fn pop(&mut self) -> EngineLayer {
248 let enclosing = self
249 .enclosing
250 .pop()
251 .expect("SceneBuilder::pop without a push");
252 self.canvas.restore();
253 let scope = std::mem::replace(&mut self.canvas, enclosing.canvas);
254 self.transform = enclosing.transform;
255 self.clip = enclosing.clip;
256 let layer = Arc::new(scope.build());
257 self.canvas.draw_display_list(&layer);
258 layer
259 }
260
261 pub fn add_surface(&mut self, surface: Arc<dyn Surface>, rect: Rect) {
266 let rect = self.in_view_coordinates(rect);
267 let clip = match self.clip {
268 Some(clip) => visible_part_of(rect, clip),
269 None => rect,
270 };
271 self.surfaces.push(SurfacePlacement {
272 surface,
273 rect,
274 scale: horizontal_scale_of(self.transform),
275 clip,
276 });
277 }
278
279 pub fn add_retained(&mut self, retained_layer: &EngineLayer) {
284 self.canvas.draw_display_list(retained_layer);
285 }
286
287 pub fn add_picture(
292 &mut self,
293 offset: Offset,
294 picture: &Arc<Picture>,
295 is_complex_hint: bool,
296 will_change_hint: bool,
297 ) {
298 let _ = (is_complex_hint, will_change_hint);
299 self.canvas.save();
300 self.canvas
301 .translate(offset.dx() as f32, offset.dy() as f32);
302 self.canvas.draw_display_list(picture);
303 self.canvas.restore();
304 }
305
306 pub fn build(self) -> Scene {
312 debug_assert!(
313 self.enclosing.is_empty(),
314 "SceneBuilder::build with an open push"
315 );
316 Scene {
317 picture: Arc::new(self.canvas.build()),
318 surfaces: self.surfaces,
319 }
320 }
321
322 fn open(&mut self, push: impl FnOnce(&mut Canvas)) {
325 let enclosing = std::mem::replace(&mut self.canvas, Canvas::new());
326 self.enclosing.push(EnclosingScope {
327 canvas: enclosing,
328 transform: self.transform,
329 clip: self.clip,
330 });
331 push(&mut self.canvas);
332 }
333
334 fn transformed_by(&mut self, matrix: &Matrix4) {
337 self.transform = self.transform.then(matrix);
338 }
339
340 fn clipped_to(&mut self, bounds: Rect) {
343 let bounds = self.in_view_coordinates(bounds);
344 self.clip = Some(match self.clip {
345 Some(clip) => visible_part_of(bounds, clip),
346 None => bounds,
347 });
348 }
349
350 fn in_view_coordinates(&self, rect: Rect) -> Rect {
353 let corners = [
354 Offset::new(rect.left, rect.top),
355 Offset::new(rect.right, rect.top),
356 Offset::new(rect.left, rect.bottom),
357 Offset::new(rect.right, rect.bottom),
358 ]
359 .map(|corner| transform3(self.transform, corner));
360 let xs = corners.iter().map(|corner| corner.dx());
361 let ys = corners.iter().map(|corner| corner.dy());
362 Rect::from_ltrb(
363 xs.clone().fold(f64::INFINITY, f64::min),
364 ys.clone().fold(f64::INFINITY, f64::min),
365 xs.fold(f64::NEG_INFINITY, f64::max),
366 ys.fold(f64::NEG_INFINITY, f64::max),
367 )
368 }
369}
370
371fn open_clip(canvas: &mut Canvas, clip_behavior: Clip, bounds: valo::Rect) {
374 match clip_behavior {
375 Clip::None | Clip::HardEdge | Clip::AntiAlias => canvas.save(),
376 Clip::AntiAliasWithSaveLayer => canvas.save_layer(Some(bounds), &Paint::default()),
377 }
378}
379
380fn bounds_of(path: &Path) -> Rect {
382 let bounds = path.bounds();
383 Rect::from_ltwh(
384 f64::from(bounds.x),
385 f64::from(bounds.y),
386 f64::from(bounds.width),
387 f64::from(bounds.height),
388 )
389}
390
391fn visible_part_of(rect: Rect, clip: Rect) -> Rect {
394 let overlap = rect.intersect(clip);
395 Rect::from_ltrb(
396 overlap.left,
397 overlap.top,
398 overlap.right.max(overlap.left),
399 overlap.bottom.max(overlap.top),
400 )
401}
402
403fn horizontal_scale_of(transform: Matrix4) -> f64 {
405 let m = transform.to_flutter_array();
406 f64::from(m[0]).hypot(f64::from(m[1]))
407}
408
409fn valo_backdrop(filter: &ImageFilter, backdrop_id: Option<u64>) -> Backdrop {
412 let backdrop = Backdrop::new(filter.to_valo());
413 match backdrop_id {
414 Some(id) => backdrop.shared(id),
415 None => backdrop,
416 }
417}
418
419#[cfg(test)]
420mod tests {
421 use super::*;
422 use crate::painting::{ColorFilter, flattened_ops};
423
424 #[test]
425 fn pushes_and_pops_balance() {
426 let mut builder = SceneBuilder::new();
427 builder.push_offset(1.0, 2.0);
428 builder.push_opacity(128, Offset::ZERO);
429 builder.pop();
430 builder.pop();
431 let _scene = builder.build();
432 }
433
434 #[test]
435 fn a_popped_scope_is_the_engine_layer_embedded_in_its_parent() {
436 let mut canvas = Canvas::new();
437 canvas.draw_rect(Rect::from_ltwh(0.0, 0.0, 10.0, 10.0), &Paint::default());
438 let picture = Arc::new(canvas.build());
439 let mut builder = SceneBuilder::new();
440 builder.push_offset(1.0, 2.0);
441 builder.add_picture(Offset::ZERO, &picture, false, false);
442 let layer = builder.pop();
443 builder.add_retained(&layer);
444 let scene = builder.build();
445 let embedded = scene
446 .picture
447 .ops()
448 .iter()
449 .filter(|op| matches!(op, valo::Op::DrawDisplayList { list, .. } if Arc::ptr_eq(list, &layer)))
450 .count();
451 assert_eq!(
452 embedded,
453 2,
454 "once from the pop, once retained: {:?}",
455 scene.picture.ops()
456 );
457 }
458
459 struct IdleSurface;
462
463 impl Surface for IdleSurface {
464 fn present(&self, _buffer: crate::Buffer, _released: Box<dyn FnOnce() + Send + Sync>) {}
465 }
466
467 fn idle_surface() -> Arc<dyn Surface> {
468 Arc::new(IdleSurface)
469 }
470
471 #[test]
472 fn a_surface_is_placed_under_the_open_transforms() {
473 let (inner, outer) = (idle_surface(), idle_surface());
474 let mut builder = SceneBuilder::new();
475 builder.push_transform(&Matrix4::scale(2.0, 2.0));
476 builder.push_offset(1.0, 1.0);
477 builder.add_surface(Arc::clone(&inner), Rect::from_ltwh(0.0, 0.0, 1.0, 1.0));
478 builder.pop();
479 builder.pop();
480 builder.add_surface(Arc::clone(&outer), Rect::from_ltwh(10.0, 20.0, 5.0, 5.0));
482 let scene = builder.build();
483 assert_eq!(
484 scene.surfaces,
485 [
486 unclipped(inner, Rect::from_ltrb(2.0, 2.0, 4.0, 4.0), 2.0),
487 unclipped(outer, Rect::from_ltwh(10.0, 20.0, 5.0, 5.0), 1.0),
488 ]
489 );
490 }
491
492 #[test]
496 fn a_surface_carries_the_scale_of_the_transforms_above_it() {
497 let surface = idle_surface();
498 let mut builder = SceneBuilder::new();
499 builder.push_transform(&Matrix4::scale(2.0, 2.0));
500 builder.push_offset(10.0, 20.0);
501 builder.push_transform(&Matrix4::scale(0.5, 0.5));
502 builder.add_surface(
503 Arc::clone(&surface),
504 Rect::from_ltwh(0.0, 0.0, 390.0, 844.0),
505 );
506 builder.pop();
507 builder.pop();
508 builder.pop();
509 let placement = &builder.build().surfaces[0];
510 assert_eq!(placement.scale, 1.0);
511 assert_eq!(placement.rect, Rect::from_ltwh(20.0, 40.0, 390.0, 844.0));
512 }
513
514 #[test]
515 fn a_surface_shows_only_what_the_clips_above_it_leave() {
516 let (clipped, beside) = (idle_surface(), idle_surface());
517 let mut builder = SceneBuilder::new();
518 builder.push_transform(&Matrix4::scale(2.0, 2.0));
519 builder.push_clip_rect(Rect::from_ltwh(0.0, 0.0, 100.0, 50.0), Clip::HardEdge);
520 builder.push_offset(10.0, 10.0);
521 let path = {
522 let mut path = crate::painting::PathBuilder::new();
523 path.rrect(Rect::from_ltwh(0.0, 0.0, 200.0, 20.0).into(), 4.0);
524 path.build()
525 };
526 builder.push_clip_path(&path, Clip::AntiAlias);
527 builder.add_surface(
528 Arc::clone(&clipped),
529 Rect::from_ltwh(-5.0, -5.0, 300.0, 300.0),
530 );
531 builder.pop();
532 builder.pop();
533 builder.add_surface(Arc::clone(&beside), Rect::from_ltwh(0.0, 0.0, 300.0, 300.0));
534 builder.pop();
535 builder.pop();
536 let scene = builder.build();
537 assert_eq!(
540 scene.surfaces[0].clip,
541 Rect::from_ltrb(20.0, 20.0, 200.0, 60.0)
542 );
543 assert_eq!(
544 scene.surfaces[0].rect,
545 Rect::from_ltwh(10.0, 10.0, 600.0, 600.0)
546 );
547 assert_eq!(
548 scene.surfaces[1].clip,
549 Rect::from_ltwh(0.0, 0.0, 200.0, 100.0),
550 "the rectangle's clip alone, once the path's scope is closed"
551 );
552 }
553
554 #[test]
555 fn a_surface_outside_its_clip_shows_nothing() {
556 let surface = idle_surface();
557 let mut builder = SceneBuilder::new();
558 builder.push_clip_rect(Rect::from_ltwh(0.0, 0.0, 100.0, 100.0), Clip::HardEdge);
559 builder.add_surface(surface, Rect::from_ltwh(200.0, 0.0, 50.0, 50.0));
560 builder.pop();
561 let clip = builder.build().surfaces[0].clip;
562 assert!(clip.is_empty(), "{clip:?}");
563 }
564
565 #[test]
566 fn a_placement_is_its_surface_its_rectangle_its_scale_and_what_shows() {
567 let surface = idle_surface();
568 let rect = Rect::from_ltwh(0.0, 0.0, 4.0, 4.0);
569 let placement = unclipped(Arc::clone(&surface), rect, 1.0);
570 assert_eq!(placement.clone(), placement);
571 let elsewhere = unclipped(
572 Arc::clone(&surface),
573 Rect::from_ltwh(1.0, 0.0, 4.0, 4.0),
574 1.0,
575 );
576 assert_ne!(elsewhere, placement);
577 let smaller = unclipped(Arc::clone(&surface), rect, 0.5);
578 assert_ne!(smaller, placement);
579 let cut = SurfacePlacement {
580 clip: Rect::from_ltwh(0.0, 0.0, 2.0, 4.0),
581 ..placement.clone()
582 };
583 assert_ne!(cut, placement);
584 let another_surface = unclipped(idle_surface(), rect, 1.0);
585 assert_ne!(
586 another_surface, placement,
587 "the same rectangle is not the same surface"
588 );
589 }
590
591 fn unclipped(surface: Arc<dyn Surface>, rect: Rect, scale: f64) -> SurfacePlacement {
593 SurfacePlacement {
594 surface,
595 rect,
596 scale,
597 clip: rect,
598 }
599 }
600
601 #[test]
602 fn a_colour_filter_composed_over_a_blur_reaches_the_backdrop_in_its_order() {
603 let saturation = ColorFilter::saturation(1.8);
604 let filter =
605 ImageFilter::compose(ImageFilter::Color(saturation), ImageFilter::blur(4.0, 4.0));
606 let backdrop = recorded_backdrop(&filter, Some(7));
607 assert_eq!(
608 backdrop.filter,
609 Some(valo::ImageFilter::compose(
610 valo::ImageFilter::Color(saturation.to_valo()),
611 valo::ImageFilter::blur(4.0, 4.0),
612 ))
613 );
614 assert_eq!(backdrop.key, Some(7), "the backdrop is still shared");
615 }
616
617 #[test]
618 fn an_uneven_blur_reaches_the_backdrop_with_both_sigmas() {
619 let backdrop = recorded_backdrop(&ImageFilter::blur(2.0, 6.0), None);
620 assert_eq!(backdrop.filter, Some(valo::ImageFilter::blur(2.0, 6.0)));
621 assert_eq!(backdrop.key, None);
622 }
623
624 #[test]
625 fn a_bounded_blur_reaches_the_backdrop_with_its_bounds_and_leaves_the_layer_unconfined() {
626 let glass = Rect::from_ltwh(8.0, 8.0, 16.0, 16.0);
627 let bounded = recorded_backdrop(&ImageFilter::blur(2.0, 3.0).bounds(glass), None);
628 assert_eq!(
629 bounded.filter,
630 Some(valo::ImageFilter::bounded_blur(2.0, 3.0, glass.into()))
631 );
632 let unbounded = recorded_backdrop(&ImageFilter::blur(2.0, 3.0), None);
633 assert_eq!(
634 bounded.bounds, unbounded.bounds,
635 "the bounds limit what the blur reads, not the layer"
636 );
637 }
638
639 struct RecordedBackdrop {
641 filter: Option<valo::ImageFilter>,
642 key: Option<u64>,
644 bounds: valo::Bounds,
646 }
647
648 fn recorded_backdrop(filter: &ImageFilter, backdrop_id: Option<u64>) -> RecordedBackdrop {
649 let mut builder = SceneBuilder::new();
650 builder.push_backdrop_filter(filter, BlendMode::SrcOver, backdrop_id);
651 builder.pop();
652 flattened_ops(&builder.build().picture)
653 .into_iter()
654 .find_map(|op| match op {
655 valo::Op::SaveLayer {
656 backdrop,
657 scope_bounds,
658 ..
659 } => Some(RecordedBackdrop {
660 key: backdrop.as_ref().and_then(|backdrop| backdrop.shared_key),
661 filter: backdrop.map(|backdrop| backdrop.filter),
662 bounds: scope_bounds,
663 }),
664 _ => None,
665 })
666 .expect("a backdrop filter opens a layer")
667 }
668
669 #[cfg(feature = "test-support")]
672 mod drawn {
673 use super::*;
674 use crate::test_support::drawn_pixels;
675
676 const SIZE: u32 = 32;
677
678 #[test]
679 fn a_colour_filter_composed_over_a_blur_recolours_the_backdrop() {
680 let swap_red_and_green = ColorFilter::matrix([
681 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, ]);
686 let glass = Rect::from_ltwh(8.0, 8.0, 16.0, 16.0);
687 let filter = ImageFilter::compose(
688 ImageFilter::Color(swap_red_and_green),
689 ImageFilter::blur(2.0, 2.0).bounds(glass),
690 );
691 let mut builder = SceneBuilder::new();
692 builder.add_picture(Offset::ZERO, &filled(Color::new(0xFFFF0000)), false, false);
693 builder.push_clip_rect(glass, Clip::HardEdge);
694 builder.push_backdrop_filter(&filter, BlendMode::SrcOver, None);
695 builder.pop();
696 builder.pop();
697 let pixels = drawn_pixels(&builder.build().picture, [SIZE, SIZE]);
698 assert_near(
699 pixel_at(&pixels, 16, 16),
700 [0, 255, 0, 255],
701 "inside the glass",
702 );
703 assert_near(
704 pixel_at(&pixels, 2, 2),
705 [255, 0, 0, 255],
706 "outside the glass",
707 );
708 }
709
710 fn filled(color: Color) -> Arc<Picture> {
712 let mut canvas = Canvas::new();
713 let paint = Paint {
714 color: color.into(),
715 ..Paint::default()
716 };
717 canvas.draw_rect(Rect::from_ltwh(0.0, 0.0, SIZE.into(), SIZE.into()), &paint);
718 Arc::new(canvas.build())
719 }
720
721 fn pixel_at(pixels: &[u8], x: usize, y: usize) -> [u8; 4] {
722 let start = (y * SIZE as usize + x) * 4;
723 pixels[start..start + 4]
724 .try_into()
725 .expect("four bytes a pixel")
726 }
727
728 fn assert_near(actual: [u8; 4], expected: [u8; 4], place: &str) {
730 let near = actual
731 .iter()
732 .zip(expected)
733 .all(|(actual, expected)| actual.abs_diff(expected) <= 2);
734 assert!(near, "{place}: {actual:?}, expected {expected:?}");
735 }
736 }
737
738 #[test]
739 fn a_scene_without_surfaces_places_none() {
740 let scene = SceneBuilder::new().build();
741 assert!(scene.surfaces.is_empty());
742 assert!(scene.picture.ops().is_empty());
743 }
744
745 #[test]
746 #[should_panic(expected = "open push")]
747 fn build_with_an_open_push_panics() {
748 let mut builder = SceneBuilder::new();
749 builder.push_offset(0.0, 0.0);
750 let _scene = builder.build();
751 }
752
753 #[test]
754 #[should_panic(expected = "without a push")]
755 fn pop_without_a_push_panics() {
756 SceneBuilder::new().pop();
757 }
758}