surpass/painter/layer_workbench/
mod.rs

1// Copyright 2021 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use 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        // Optimization should fail because the number of layers changed.
518        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        // Skip should occur because the previous pass updated the number of layers.
540        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        // Optimization should fail because at least one layer changed.
565        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        // Optimization should fail because the clear color changed.
590        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        // Tile has changed because layer 0 changed.
1047        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        // However, we can still skip drawing because everything visible is unchanged.
1056        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        // Tile has changed because layer 0 changed and number of layers has changed.
1081        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        // We can still skip the tile because any newly added layer is covered by an opaque layer.
1090        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        // Tile has changed because layer 0 changed and number of layers has changed.
1115        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        // This time we cannot skip because there might have been a visible layer
1124        // last frame that is now removed.
1125        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        // We can't skip drawing because we don't have any cached tile.
1170        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        // We can't skip drawing because the tile solid color (RED) is different from the previous one (WHITE).
1193        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        // We can skip drawing because the tile solid color is unchanged.
1215        assert_eq!(
1216            workbench.drive_tile_painting(&mut UnimplementedPainter, &context),
1217            TileWriteOp::None,
1218        );
1219    }
1220}