1use std::cell::Cell;
6use std::mem;
7use std::ops::{ControlFlow, RangeInclusive};
8
9use rustc_hash::FxHashMap;
10
11use crate::painter::layer_workbench::passes::PassesSharedState;
12use crate::painter::{
13 CachedTile, Channel, Color, Cover, CoverCarry, FillRule, Func, LayerProps, Props, Style,
14};
15use crate::rasterizer::{self, PixelSegment};
16use crate::{TILE_HEIGHT, TILE_WIDTH};
17
18mod passes;
19
20pub(crate) trait LayerPainter {
21 fn clear_cells(&mut self);
22 fn acc_segment(&mut self, segment: PixelSegment<TILE_WIDTH, TILE_HEIGHT>);
23 fn acc_cover(&mut self, cover: Cover);
24 fn clear(&mut self, color: Color);
25 fn paint_layer(
26 &mut self,
27 tile_x: usize,
28 tile_y: usize,
29 layer_id: u32,
30 props: &Props,
31 apply_clip: bool,
32 ) -> Cover;
33}
34
35#[derive(Clone, Copy, Debug)]
36#[repr(transparent)]
37pub struct Index(usize);
38
39#[derive(Debug)]
40struct MaskedCell<T> {
41 val: T,
42 mask: Cell<bool>,
43}
44
45#[derive(Debug, Default)]
46pub struct MaskedVec<T> {
47 vals: Vec<MaskedCell<T>>,
48 skipped: Cell<usize>,
49}
50
51impl<T> MaskedVec<T> {
52 pub fn len(&self) -> usize {
53 self.vals.len()
54 }
55
56 pub fn iter(&self) -> impl Iterator<Item = &T> {
57 self.vals.iter().map(|cell| &cell.val)
58 }
59
60 pub fn iter_with_masks(&self) -> impl Iterator<Item = (&T, bool)> {
61 self.vals
62 .iter()
63 .enumerate()
64 .map(move |(i, cell)| (&cell.val, i >= self.skipped.get() && cell.mask.get()))
65 }
66
67 pub fn iter_masked(&self) -> impl DoubleEndedIterator<Item = (Index, &T)> {
68 self.vals
69 .iter()
70 .enumerate()
71 .skip(self.skipped.get())
72 .filter_map(|(i, cell)| cell.mask.get().then_some((Index(i), &cell.val)))
73 }
74
75 pub fn clear(&mut self) {
76 self.vals.clear();
77 self.skipped.set(0);
78 }
79
80 pub fn set_mask(&self, i: Index, mask: bool) {
81 self.vals[i.0].mask.set(mask);
82 }
83
84 pub fn skip_until(&self, i: Index) {
85 self.skipped.set(i.0);
86 }
87}
88
89impl<T: Copy + Ord + PartialEq> MaskedVec<T> {
90 pub fn sort_and_dedup(&mut self) {
91 self.vals.sort_unstable_by_key(|cell| cell.val);
92 self.vals.dedup_by_key(|cell| cell.val);
93 }
94}
95
96impl<A> Extend<A> for MaskedVec<A> {
97 fn extend<T: IntoIterator<Item = A>>(&mut self, iter: T) {
98 self.vals.extend(iter.into_iter().map(|val| MaskedCell { val, mask: Cell::new(true) }));
99 }
100}
101
102#[derive(Debug, Eq, PartialEq)]
103pub enum OptimizerTileWriteOp {
104 None,
105 Solid(Color),
106}
107
108#[derive(Debug, Eq, PartialEq)]
109pub enum TileWriteOp {
110 None,
111 Solid([u8; 4]),
112 ColorBuffer,
113}
114
115pub struct Context<'c, P: LayerProps> {
116 pub tile_x: usize,
117 pub tile_y: usize,
118 pub segments: &'c [PixelSegment<TILE_WIDTH, TILE_HEIGHT>],
119 pub props: &'c P,
120 pub cached_clear_color: Option<Color>,
121 pub channels: [Channel; 4],
122 pub cached_tile: Option<&'c CachedTile>,
123 pub clear_color: Color,
124}
125
126#[derive(Debug, Default)]
127pub struct LayerWorkbenchState {
128 pub ids: MaskedVec<u32>,
129 pub segment_ranges: FxHashMap<u32, RangeInclusive<usize>>,
130 pub queue_indices: FxHashMap<u32, usize>,
131 pub queue: Vec<CoverCarry>,
132 next_queue: Vec<CoverCarry>,
133}
134
135impl LayerWorkbenchState {
136 fn segments<'c, P: LayerProps>(
137 &self,
138 context: &'c Context<'_, P>,
139 id: u32,
140 ) -> Option<&'c [PixelSegment<TILE_WIDTH, TILE_HEIGHT>]> {
141 self.segment_ranges.get(&id).map(|range| &context.segments[range.clone()])
142 }
143
144 fn cover(&self, id: u32) -> Option<&Cover> {
145 self.queue_indices.get(&id).map(|&i| &self.queue[i].cover)
146 }
147
148 pub(crate) fn layer_is_full<'c, P: LayerProps>(
149 &self,
150 context: &'c Context<'_, P>,
151 id: u32,
152 fill_rule: FillRule,
153 ) -> bool {
154 self.segments(context, id).is_none()
155 && self.cover(id).map(|cover| cover.is_full(fill_rule)).unwrap_or_default()
156 }
157}
158
159#[derive(Debug, Default)]
160pub(crate) struct LayerWorkbench {
161 state: LayerWorkbenchState,
162 passes_shared_state: PassesSharedState,
163}
164
165impl LayerWorkbench {
166 pub fn new() -> Self {
167 Self::default()
168 }
169
170 pub fn init(&mut self, cover_carries: impl IntoIterator<Item = CoverCarry>) {
171 self.state.queue.clear();
172 self.state.queue.extend(cover_carries);
173 }
174
175 fn next_tile(&mut self) {
176 self.state.ids.clear();
177 self.state.segment_ranges.clear();
178 self.state.queue_indices.clear();
179
180 mem::swap(&mut self.state.queue, &mut self.state.next_queue);
181
182 self.state.next_queue.clear();
183
184 self.passes_shared_state.reset();
185 }
186
187 fn cover_carry<'c, P: LayerProps>(
188 &self,
189 context: &'c Context<'_, P>,
190 id: u32,
191 ) -> Option<CoverCarry> {
192 let mut acc_cover = Cover::default();
193
194 if let Some(segments) = self.state.segments(context, id) {
195 for segment in segments {
196 acc_cover.as_slice_mut()[segment.local_y() as usize] += segment.cover();
197 }
198 }
199
200 if let Some(cover) = self.state.cover(id) {
201 cover.add_cover_to(&mut acc_cover.covers);
202 }
203
204 (!acc_cover.is_empty(context.props.get(id).fill_rule))
205 .then_some(CoverCarry { cover: acc_cover, layer_id: id })
206 }
207
208 fn optimization_passes<'c, P: LayerProps>(
209 &mut self,
210 context: &'c Context<'_, P>,
211 ) -> ControlFlow<OptimizerTileWriteOp> {
212 let state = &mut self.state;
213 let passes_shared_state = &mut self.passes_shared_state;
214
215 passes::tile_unchanged_pass(state, passes_shared_state, context)?;
216 passes::skip_trivial_clips_pass(state, passes_shared_state, context);
217 passes::skip_fully_covered_layers_pass(state, passes_shared_state, context)?;
218
219 ControlFlow::Continue(())
220 }
221
222 fn populate_layers<'c, P: LayerProps>(&mut self, context: &'c Context<'_, P>) {
223 let mut start = 0;
224 while let Some(id) = context.segments.get(start).map(|s| s.layer_id()) {
225 let diff =
226 rasterizer::search_last_by_key(&context.segments[start..], id, |s| s.layer_id())
227 .unwrap();
228
229 self.state.segment_ranges.insert(id, start..=start + diff);
230
231 start += diff + 1;
232 }
233
234 self.state.queue_indices.extend(
235 self.state.queue.iter().enumerate().map(|(i, cover_carry)| (cover_carry.layer_id, i)),
236 );
237
238 self.state.ids.extend(
239 self.state
240 .segment_ranges
241 .keys()
242 .copied()
243 .chain(self.state.queue_indices.keys().copied()),
244 );
245
246 self.state.ids.sort_and_dedup();
247 }
248
249 pub fn drive_tile_painting<'c, P: LayerProps>(
250 &mut self,
251 painter: &mut impl LayerPainter,
252 context: &'c Context<'_, P>,
253 ) -> TileWriteOp {
254 self.populate_layers(context);
255
256 if let ControlFlow::Break(tile_op) =
257 CachedTile::convert_optimizer_op(self.optimization_passes(context), context)
258 {
259 for &id in self.state.ids.iter() {
260 if let Some(cover_carry) = self.cover_carry(context, id) {
261 self.state.next_queue.push(cover_carry);
262 }
263 }
264
265 self.next_tile();
266
267 return tile_op;
268 }
269
270 painter.clear(context.clear_color);
271
272 for (&id, mask) in self.state.ids.iter_with_masks() {
273 if mask {
274 painter.clear_cells();
275
276 if let Some(segments) = self.state.segments(context, id) {
277 for &segment in segments {
278 painter.acc_segment(segment);
279 }
280 }
281
282 if let Some(&cover) = self.state.cover(id) {
283 painter.acc_cover(cover);
284 }
285
286 let props = context.props.get(id);
287 let mut apply_clip = false;
288
289 if let Func::Draw(Style { is_clipped, .. }) = props.func {
290 apply_clip =
291 is_clipped && !self.passes_shared_state.skip_clipping.contains(&id);
292 }
293
294 let cover =
295 painter.paint_layer(context.tile_x, context.tile_y, id, &props, apply_clip);
296
297 if !cover.is_empty(props.fill_rule) {
298 self.state.next_queue.push(CoverCarry { cover, layer_id: id });
299 }
300 } else if let Some(cover_carry) = self.cover_carry(context, id) {
301 self.state.next_queue.push(cover_carry);
302 }
303 }
304
305 self.next_tile();
306
307 TileWriteOp::ColorBuffer
308 }
309}
310
311#[cfg(test)]
312mod tests {
313 use super::*;
314
315 use std::borrow::Cow;
316
317 use crate::painter::{BlendMode, Fill, RGBA};
318 use crate::simd::{i8x16, Simd};
319 use crate::PIXEL_WIDTH;
320
321 const WHITEF: Color = Color { r: 1.0, g: 1.0, b: 1.0, a: 1.0 };
322 const BLACKF: Color = Color { r: 0.0, g: 0.0, b: 0.0, a: 0.0 };
323 const REDF: Color = Color { r: 1.0, g: 0.0, b: 0.0, a: 1.0 };
324
325 const RED: [u8; 4] = [255, 0, 0, 255];
326 const WHITE: [u8; 4] = [255, 255, 255, 255];
327
328 impl<T: PartialEq, const N: usize> PartialEq<[T; N]> for MaskedVec<T> {
329 fn eq(&self, other: &[T; N]) -> bool {
330 self.iter_masked().map(|(_, val)| val).eq(other.iter())
331 }
332 }
333
334 struct UnimplementedPainter;
335
336 impl LayerPainter for UnimplementedPainter {
337 fn clear_cells(&mut self) {
338 unimplemented!();
339 }
340
341 fn acc_segment(&mut self, _segment: PixelSegment<TILE_WIDTH, TILE_HEIGHT>) {
342 unimplemented!();
343 }
344
345 fn acc_cover(&mut self, _cover: Cover) {
346 unimplemented!();
347 }
348
349 fn clear(&mut self, _color: Color) {
350 unimplemented!();
351 }
352
353 fn paint_layer(
354 &mut self,
355 _tile_x: usize,
356 _tile_y: usize,
357 _layer_id: u32,
358 _props: &Props,
359 _apply_clip: bool,
360 ) -> Cover {
361 unimplemented!()
362 }
363 }
364
365 #[test]
366 fn masked_vec() {
367 let mut v = MaskedVec::default();
368
369 v.extend([1, 2, 3, 4, 5, 6, 7, 8, 9]);
370
371 for (i, &val) in v.iter_masked() {
372 if let 2 | 3 | 4 | 5 = val {
373 v.set_mask(i, false);
374 }
375
376 if val == 3 {
377 v.set_mask(i, true);
378 }
379 }
380
381 assert_eq!(v, [1, 3, 6, 7, 8, 9]);
382
383 for (i, &val) in v.iter_masked() {
384 if let 3 | 7 = val {
385 v.set_mask(i, false);
386 }
387 }
388
389 assert_eq!(v, [1, 6, 8, 9]);
390
391 for (i, &val) in v.iter_masked() {
392 if val == 8 {
393 v.skip_until(i);
394 }
395 }
396
397 assert_eq!(v, [8, 9]);
398 }
399
400 enum CoverType {
401 Partial,
402 Full,
403 }
404
405 fn cover(layer_id: u32, cover_type: CoverType) -> CoverCarry {
406 let cover = match cover_type {
407 CoverType::Partial => Cover { covers: [i8x16::splat(1); TILE_HEIGHT / i8x16::LANES] },
408 CoverType::Full => {
409 Cover { covers: [i8x16::splat(PIXEL_WIDTH as i8); TILE_HEIGHT / i8x16::LANES] }
410 }
411 };
412
413 CoverCarry { cover, layer_id }
414 }
415
416 fn segment(layer_id: u32) -> PixelSegment<TILE_WIDTH, TILE_HEIGHT> {
417 PixelSegment::new(layer_id, 0, 0, 0, 0, 0, 0)
418 }
419
420 #[test]
421 fn populate_layers() {
422 let mut workbench = LayerWorkbench::default();
423
424 struct UnimplementedProps;
425
426 impl LayerProps for UnimplementedProps {
427 fn get(&self, _layer_id: u32) -> Cow<'_, Props> {
428 unimplemented!()
429 }
430
431 fn is_unchanged(&self, _layer_id: u32) -> bool {
432 unimplemented!()
433 }
434 }
435
436 workbench.init([
437 cover(0, CoverType::Partial),
438 cover(3, CoverType::Partial),
439 cover(4, CoverType::Partial),
440 ]);
441
442 let context = Context {
443 tile_x: 0,
444 tile_y: 0,
445 segments: &[
446 segment(0),
447 segment(1),
448 segment(1),
449 segment(2),
450 segment(5),
451 segment(5),
452 segment(5),
453 ],
454 props: &UnimplementedProps,
455 cached_clear_color: Some(BLACKF),
456 cached_tile: None,
457 channels: RGBA,
458 clear_color: BLACKF,
459 };
460
461 workbench.populate_layers(&context);
462
463 let segment_ranges = workbench.state.segment_ranges;
464 let queue_indices = workbench.state.queue_indices;
465
466 assert_eq!(workbench.state.ids, [0, 1, 2, 3, 4, 5]);
467
468 assert_eq!(segment_ranges.len(), 4);
469 assert_eq!(segment_ranges.get(&0).cloned(), Some(0..=0));
470 assert_eq!(segment_ranges.get(&1).cloned(), Some(1..=2));
471 assert_eq!(segment_ranges.get(&2).cloned(), Some(3..=3));
472 assert_eq!(segment_ranges.get(&3).cloned(), None);
473 assert_eq!(segment_ranges.get(&4).cloned(), None);
474 assert_eq!(segment_ranges.get(&5).cloned(), Some(4..=6));
475
476 assert_eq!(queue_indices.len(), 3);
477 assert_eq!(queue_indices.get(&0).copied(), Some(0));
478 assert_eq!(queue_indices.get(&1).copied(), None);
479 assert_eq!(queue_indices.get(&2).copied(), None);
480 assert_eq!(queue_indices.get(&3).copied(), Some(1));
481 assert_eq!(queue_indices.get(&4).copied(), Some(2));
482 assert_eq!(queue_indices.get(&5).copied(), None);
483 }
484
485 #[test]
486 fn skip_unchanged() {
487 let mut workbench = LayerWorkbench::default();
488
489 struct TestProps;
490
491 impl LayerProps for TestProps {
492 fn get(&self, _layer_id: u32) -> Cow<'_, Props> {
493 unimplemented!()
494 }
495
496 fn is_unchanged(&self, layer_id: u32) -> bool {
497 layer_id < 5
498 }
499 }
500
501 let cached_tiles = CachedTile::default();
502 cached_tiles.update_layer_count(Some(4));
503
504 let context = Context {
505 tile_x: 0,
506 tile_y: 0,
507 segments: &[segment(0), segment(1), segment(2), segment(3), segment(4)],
508 props: &TestProps,
509 cached_clear_color: Some(BLACKF),
510 cached_tile: Some(&cached_tiles),
511 channels: RGBA,
512 clear_color: BLACKF,
513 };
514
515 workbench.populate_layers(&context);
516
517 assert_eq!(
519 passes::tile_unchanged_pass(
520 &mut workbench.state,
521 &mut workbench.passes_shared_state,
522 &context
523 ),
524 ControlFlow::Continue(()),
525 );
526 assert_eq!(cached_tiles.layer_count(), Some(5));
527
528 let context = Context {
529 tile_x: 0,
530 tile_y: 0,
531 segments: &[segment(0), segment(1), segment(2), segment(3), segment(4)],
532 props: &TestProps,
533 cached_clear_color: Some(BLACKF),
534 cached_tile: Some(&cached_tiles),
535 channels: RGBA,
536 clear_color: BLACKF,
537 };
538
539 assert_eq!(
541 passes::tile_unchanged_pass(
542 &mut workbench.state,
543 &mut workbench.passes_shared_state,
544 &context
545 ),
546 ControlFlow::Break(OptimizerTileWriteOp::None),
547 );
548 assert_eq!(cached_tiles.layer_count(), Some(5));
549
550 let context = Context {
551 tile_x: 0,
552 tile_y: 0,
553 segments: &[segment(1), segment(2), segment(3), segment(4), segment(5)],
554 props: &TestProps,
555 cached_clear_color: Some(BLACKF),
556 cached_tile: Some(&cached_tiles),
557 channels: RGBA,
558 clear_color: BLACKF,
559 };
560
561 workbench.next_tile();
562 workbench.populate_layers(&context);
563
564 assert_eq!(
566 passes::tile_unchanged_pass(
567 &mut workbench.state,
568 &mut workbench.passes_shared_state,
569 &context
570 ),
571 ControlFlow::Continue(()),
572 );
573 assert_eq!(cached_tiles.layer_count(), Some(5));
574
575 let context = Context {
576 tile_x: 0,
577 tile_y: 0,
578 segments: &[segment(0), segment(1), segment(2), segment(3), segment(4)],
579 props: &TestProps,
580 cached_clear_color: Some(BLACKF),
581 cached_tile: Some(&cached_tiles),
582 channels: RGBA,
583 clear_color: WHITEF,
584 };
585
586 workbench.next_tile();
587 workbench.populate_layers(&context);
588
589 assert_eq!(
591 passes::tile_unchanged_pass(
592 &mut workbench.state,
593 &mut workbench.passes_shared_state,
594 &context
595 ),
596 ControlFlow::Continue(()),
597 );
598 assert_eq!(cached_tiles.layer_count(), Some(5));
599 }
600
601 #[test]
602 fn skip_full_clip() {
603 let mut workbench = LayerWorkbench::default();
604
605 struct TestProps;
606
607 impl LayerProps for TestProps {
608 fn get(&self, layer_id: u32) -> Cow<'_, Props> {
609 Cow::Owned(match layer_id {
610 1 | 3 => Props { func: Func::Clip(1), ..Default::default() },
611 _ => Props {
612 func: Func::Draw(Style { is_clipped: layer_id == 2, ..Default::default() }),
613 ..Default::default()
614 },
615 })
616 }
617
618 fn is_unchanged(&self, _layer_id: u32) -> bool {
619 unimplemented!()
620 }
621 }
622
623 workbench.init([
624 cover(0, CoverType::Partial),
625 cover(1, CoverType::Full),
626 cover(2, CoverType::Partial),
627 cover(3, CoverType::Full),
628 ]);
629
630 let context = Context {
631 tile_x: 0,
632 tile_y: 0,
633 segments: &[],
634 props: &TestProps,
635 cached_clear_color: Some(BLACKF),
636 cached_tile: None,
637 channels: RGBA,
638 clear_color: BLACKF,
639 };
640
641 workbench.populate_layers(&context);
642
643 passes::skip_trivial_clips_pass(
644 &mut workbench.state,
645 &mut workbench.passes_shared_state,
646 &context,
647 );
648
649 let skip_clipping = workbench.passes_shared_state.skip_clipping;
650
651 assert_eq!(workbench.state.ids, [0, 2]);
652 assert!(!skip_clipping.contains(&0));
653 assert!(skip_clipping.contains(&2));
654 }
655
656 #[test]
657 fn skip_layer_outside_of_clip() {
658 let mut workbench = LayerWorkbench::default();
659
660 struct TestProps;
661
662 impl LayerProps for TestProps {
663 fn get(&self, _layer_id: u32) -> Cow<'_, Props> {
664 Cow::Owned(Props {
665 func: Func::Draw(Style { is_clipped: true, ..Default::default() }),
666 ..Default::default()
667 })
668 }
669
670 fn is_unchanged(&self, _layer_id: u32) -> bool {
671 unimplemented!()
672 }
673 }
674
675 workbench.init([cover(0, CoverType::Partial), cover(1, CoverType::Partial)]);
676
677 let context = Context {
678 tile_x: 0,
679 tile_y: 0,
680 segments: &[],
681 props: &TestProps,
682 cached_clear_color: Some(BLACKF),
683 cached_tile: None,
684 channels: RGBA,
685 clear_color: BLACKF,
686 };
687
688 workbench.populate_layers(&context);
689
690 passes::skip_trivial_clips_pass(
691 &mut workbench.state,
692 &mut workbench.passes_shared_state,
693 &context,
694 );
695
696 assert_eq!(workbench.state.ids, []);
697 }
698
699 #[test]
700 fn skip_without_layer_usage() {
701 let mut workbench = LayerWorkbench::default();
702
703 struct TestProps;
704
705 impl LayerProps for TestProps {
706 fn get(&self, layer_id: u32) -> Cow<'_, Props> {
707 Cow::Owned(match layer_id {
708 1 | 4 => Props { func: Func::Clip(1), ..Default::default() },
709 _ => Props::default(),
710 })
711 }
712
713 fn is_unchanged(&self, _layer_id: u32) -> bool {
714 unimplemented!()
715 }
716 }
717
718 workbench.init([
719 cover(0, CoverType::Partial),
720 cover(1, CoverType::Partial),
721 cover(3, CoverType::Partial),
722 cover(4, CoverType::Partial),
723 ]);
724
725 let context = Context {
726 tile_x: 0,
727 tile_y: 0,
728 segments: &[],
729 props: &TestProps,
730 cached_clear_color: Some(BLACKF),
731 cached_tile: None,
732 channels: RGBA,
733 clear_color: BLACKF,
734 };
735
736 workbench.populate_layers(&context);
737
738 passes::skip_trivial_clips_pass(
739 &mut workbench.state,
740 &mut workbench.passes_shared_state,
741 &context,
742 );
743
744 assert_eq!(workbench.state.ids, [0, 3]);
745 }
746
747 #[test]
748 fn skip_everything_below_opaque() {
749 let mut workbench = LayerWorkbench::default();
750
751 struct TestProps;
752
753 impl LayerProps for TestProps {
754 fn get(&self, _layer_id: u32) -> Cow<'_, Props> {
755 Cow::Owned(Props::default())
756 }
757
758 fn is_unchanged(&self, _layer_id: u32) -> bool {
759 false
760 }
761 }
762
763 workbench.init([
764 cover(0, CoverType::Partial),
765 cover(1, CoverType::Partial),
766 cover(2, CoverType::Full),
767 ]);
768
769 let context = Context {
770 tile_x: 0,
771 tile_y: 0,
772 segments: &[segment(3)],
773 props: &TestProps,
774 cached_clear_color: Some(BLACKF),
775 cached_tile: None,
776 channels: RGBA,
777 clear_color: BLACKF,
778 };
779
780 workbench.populate_layers(&context);
781
782 assert_eq!(
783 passes::skip_fully_covered_layers_pass(
784 &mut workbench.state,
785 &mut workbench.passes_shared_state,
786 &context,
787 ),
788 ControlFlow::Continue(()),
789 );
790
791 assert_eq!(workbench.state.ids, [2, 3]);
792 }
793
794 #[test]
795 fn blend_top_full_layers() {
796 let mut workbench = LayerWorkbench::default();
797
798 struct TestProps;
799
800 impl LayerProps for TestProps {
801 fn get(&self, layer_id: u32) -> Cow<'_, Props> {
802 Cow::Owned(Props {
803 func: Func::Draw(Style {
804 fill: Fill::Solid(Color { r: 0.5, g: 0.5, b: 0.5, a: 0.5 }),
805 blend_mode: match layer_id {
806 0 => BlendMode::Over,
807 1 => BlendMode::Multiply,
808 _ => unimplemented!(),
809 },
810 ..Default::default()
811 }),
812 ..Default::default()
813 })
814 }
815
816 fn is_unchanged(&self, _layer_id: u32) -> bool {
817 false
818 }
819 }
820
821 workbench.init([cover(0, CoverType::Full), cover(1, CoverType::Full)]);
822
823 let context = Context {
824 tile_x: 0,
825 tile_y: 0,
826 segments: &[],
827 props: &TestProps,
828 cached_clear_color: Some(BLACKF),
829 cached_tile: None,
830 channels: RGBA,
831 clear_color: BLACKF,
832 };
833
834 workbench.populate_layers(&context);
835
836 assert_eq!(
837 passes::skip_fully_covered_layers_pass(
838 &mut workbench.state,
839 &mut workbench.passes_shared_state,
840 &context,
841 ),
842 ControlFlow::Break(OptimizerTileWriteOp::Solid(Color {
843 r: 0.28125,
844 g: 0.28125,
845 b: 0.28125,
846 a: 0.75
847 })),
848 );
849 }
850
851 #[test]
852 fn blend_top_full_layers_with_clear_color() {
853 let mut workbench = LayerWorkbench::default();
854
855 struct TestProps;
856
857 impl LayerProps for TestProps {
858 fn get(&self, _layer_id: u32) -> Cow<'_, Props> {
859 Cow::Owned(Props {
860 func: Func::Draw(Style {
861 fill: Fill::Solid(Color { r: 0.5, g: 0.5, b: 0.5, a: 0.5 }),
862 blend_mode: BlendMode::Multiply,
863 ..Default::default()
864 }),
865 ..Default::default()
866 })
867 }
868
869 fn is_unchanged(&self, _layer_id: u32) -> bool {
870 false
871 }
872 }
873
874 workbench.init([cover(0, CoverType::Full), cover(1, CoverType::Full)]);
875
876 let context = Context {
877 tile_x: 0,
878 tile_y: 0,
879 segments: &[],
880 props: &TestProps,
881 cached_clear_color: Some(WHITEF),
882 cached_tile: None,
883 channels: RGBA,
884 clear_color: WHITEF,
885 };
886
887 workbench.populate_layers(&context);
888
889 assert_eq!(
890 passes::skip_fully_covered_layers_pass(
891 &mut workbench.state,
892 &mut workbench.passes_shared_state,
893 &context,
894 ),
895 ControlFlow::Break(OptimizerTileWriteOp::Solid(Color {
896 r: 0.5625,
897 g: 0.5625,
898 b: 0.5625,
899 a: 1.0
900 })),
901 );
902 }
903
904 #[test]
905 fn skip_fully_covered_layers_clip() {
906 let mut workbench = LayerWorkbench::default();
907
908 struct TestProps;
909
910 impl LayerProps for TestProps {
911 fn get(&self, layer_id: u32) -> Cow<'_, Props> {
912 Cow::Owned(Props {
913 func: match layer_id {
914 0 => Func::Clip(1),
915 1 => Func::Draw(Style {
916 blend_mode: BlendMode::Multiply,
917 ..Default::default()
918 }),
919 _ => unimplemented!(),
920 },
921 ..Default::default()
922 })
923 }
924
925 fn is_unchanged(&self, _layer_id: u32) -> bool {
926 false
927 }
928 }
929
930 workbench.init([cover(0, CoverType::Partial), cover(1, CoverType::Full)]);
931
932 let context = Context {
933 tile_x: 0,
934 tile_y: 0,
935 segments: &[],
936 props: &TestProps,
937 cached_clear_color: Some(WHITEF),
938 cached_tile: None,
939 channels: RGBA,
940 clear_color: WHITEF,
941 };
942
943 workbench.populate_layers(&context);
944
945 assert_eq!(
946 passes::skip_fully_covered_layers_pass(
947 &mut workbench.state,
948 &mut workbench.passes_shared_state,
949 &context,
950 ),
951 ControlFlow::Continue(()),
952 );
953 }
954
955 #[test]
956 fn skip_clip_then_blend() {
957 let mut workbench = LayerWorkbench::default();
958
959 struct TestProps;
960
961 impl LayerProps for TestProps {
962 fn get(&self, layer_id: u32) -> Cow<'_, Props> {
963 Cow::Owned(Props {
964 func: match layer_id {
965 0 => Func::Clip(1),
966 1 => Func::Draw(Style {
967 fill: Fill::Solid(Color { r: 0.5, g: 0.5, b: 0.5, a: 0.5 }),
968 blend_mode: BlendMode::Multiply,
969 ..Default::default()
970 }),
971 _ => unimplemented!(),
972 },
973 ..Default::default()
974 })
975 }
976
977 fn is_unchanged(&self, _layer_id: u32) -> bool {
978 false
979 }
980 }
981
982 workbench.init([cover(0, CoverType::Partial), cover(1, CoverType::Full)]);
983
984 let context = Context {
985 tile_x: 0,
986 tile_y: 0,
987 segments: &[],
988 props: &TestProps,
989 cached_clear_color: Some(WHITEF),
990 cached_tile: None,
991 channels: RGBA,
992 clear_color: WHITEF,
993 };
994
995 assert_eq!(
996 workbench.drive_tile_painting(&mut UnimplementedPainter, &context),
997 TileWriteOp::Solid([224, 224, 224, 255]),
998 );
999 }
1000
1001 #[test]
1002 fn skip_visible_is_unchanged() {
1003 let mut workbench = LayerWorkbench::default();
1004
1005 struct TestProps;
1006
1007 impl LayerProps for TestProps {
1008 fn get(&self, layer_id: u32) -> Cow<'_, Props> {
1009 if layer_id == 2 {
1010 return Cow::Owned(Props {
1011 func: Func::Draw(Style { fill: Fill::Solid(REDF), ..Default::default() }),
1012 ..Default::default()
1013 });
1014 }
1015
1016 Cow::Owned(Props::default())
1017 }
1018
1019 fn is_unchanged(&self, layer_id: u32) -> bool {
1020 layer_id != 0
1021 }
1022 }
1023
1024 workbench.init([
1025 cover(0, CoverType::Partial),
1026 cover(1, CoverType::Partial),
1027 cover(2, CoverType::Full),
1028 ]);
1029
1030 let cached_tiles = CachedTile::default();
1031 cached_tiles.update_layer_count(Some(3));
1032
1033 let context = Context {
1034 tile_x: 0,
1035 tile_y: 0,
1036 segments: &[],
1037 props: &TestProps,
1038 cached_clear_color: Some(BLACKF),
1039 cached_tile: Some(&cached_tiles),
1040 channels: RGBA,
1041 clear_color: BLACKF,
1042 };
1043
1044 workbench.populate_layers(&context);
1045
1046 assert_eq!(
1048 passes::tile_unchanged_pass(
1049 &mut workbench.state,
1050 &mut workbench.passes_shared_state,
1051 &context
1052 ),
1053 ControlFlow::Continue(()),
1054 );
1055 assert_eq!(
1057 passes::skip_fully_covered_layers_pass(
1058 &mut workbench.state,
1059 &mut workbench.passes_shared_state,
1060 &context,
1061 ),
1062 ControlFlow::Break(OptimizerTileWriteOp::None),
1063 );
1064
1065 cached_tiles.update_layer_count(Some(2));
1066
1067 let context = Context {
1068 tile_x: 0,
1069 tile_y: 0,
1070 segments: &[],
1071 props: &TestProps,
1072 cached_clear_color: Some(BLACKF),
1073 cached_tile: Some(&cached_tiles),
1074 channels: RGBA,
1075 clear_color: BLACKF,
1076 };
1077
1078 workbench.populate_layers(&context);
1079
1080 assert_eq!(
1082 passes::tile_unchanged_pass(
1083 &mut workbench.state,
1084 &mut workbench.passes_shared_state,
1085 &context
1086 ),
1087 ControlFlow::Continue(()),
1088 );
1089 assert_eq!(
1091 passes::skip_fully_covered_layers_pass(
1092 &mut workbench.state,
1093 &mut workbench.passes_shared_state,
1094 &context,
1095 ),
1096 ControlFlow::Break(OptimizerTileWriteOp::None),
1097 );
1098
1099 cached_tiles.update_layer_count(Some(4));
1100
1101 let context = Context {
1102 tile_x: 0,
1103 tile_y: 0,
1104 segments: &[],
1105 props: &TestProps,
1106 cached_clear_color: Some(BLACKF),
1107 cached_tile: Some(&cached_tiles),
1108 channels: RGBA,
1109 clear_color: BLACKF,
1110 };
1111
1112 workbench.populate_layers(&context);
1113
1114 assert_eq!(
1116 passes::tile_unchanged_pass(
1117 &mut workbench.state,
1118 &mut workbench.passes_shared_state,
1119 &context
1120 ),
1121 ControlFlow::Continue(()),
1122 );
1123 assert_eq!(
1126 passes::skip_fully_covered_layers_pass(
1127 &mut workbench.state,
1128 &mut workbench.passes_shared_state,
1129 &context,
1130 ),
1131 ControlFlow::Break(OptimizerTileWriteOp::Solid(REDF)),
1132 );
1133 }
1134
1135 #[test]
1136 fn skip_solid_color_is_unchanged() {
1137 let mut workbench = LayerWorkbench::default();
1138
1139 struct TestProps;
1140
1141 impl LayerProps for TestProps {
1142 fn get(&self, _layer_id: u32) -> Cow<'_, Props> {
1143 Cow::Owned(Props {
1144 func: Func::Draw(Style { fill: Fill::Solid(REDF), ..Default::default() }),
1145 ..Default::default()
1146 })
1147 }
1148
1149 fn is_unchanged(&self, _layer_id: u32) -> bool {
1150 false
1151 }
1152 }
1153
1154 workbench.init([cover(0, CoverType::Full)]);
1155
1156 let context = Context {
1157 tile_x: 0,
1158 tile_y: 0,
1159 segments: &[],
1160 props: &TestProps,
1161 cached_clear_color: Some(BLACKF),
1162 cached_tile: None,
1163 channels: RGBA,
1164 clear_color: BLACKF,
1165 };
1166
1167 workbench.populate_layers(&context);
1168
1169 assert_eq!(
1171 workbench.drive_tile_painting(&mut UnimplementedPainter, &context),
1172 TileWriteOp::Solid(RED),
1173 );
1174
1175 let cached_tiles = CachedTile::default();
1176 cached_tiles.update_layer_count(Some(0));
1177 cached_tiles.update_solid_color(Some(WHITE));
1178
1179 let context = Context {
1180 tile_x: 0,
1181 tile_y: 0,
1182 segments: &[],
1183 props: &TestProps,
1184 cached_clear_color: Some(BLACKF),
1185 cached_tile: Some(&cached_tiles),
1186 channels: RGBA,
1187 clear_color: BLACKF,
1188 };
1189
1190 workbench.populate_layers(&context);
1191
1192 assert_eq!(
1194 workbench.drive_tile_painting(&mut UnimplementedPainter, &context),
1195 TileWriteOp::Solid(RED),
1196 );
1197
1198 cached_tiles.update_layer_count(Some(0));
1199 cached_tiles.update_solid_color(Some(RED));
1200
1201 let context = Context {
1202 tile_x: 0,
1203 tile_y: 0,
1204 segments: &[],
1205 props: &TestProps,
1206 cached_clear_color: Some(BLACKF),
1207 cached_tile: Some(&cached_tiles),
1208 channels: RGBA,
1209 clear_color: BLACKF,
1210 };
1211
1212 workbench.populate_layers(&context);
1213
1214 assert_eq!(
1216 workbench.drive_tile_painting(&mut UnimplementedPainter, &context),
1217 TileWriteOp::None,
1218 );
1219 }
1220}