surpass/painter/layer_workbench/passes/
skip_fully_covered_layers.rs

1// Copyright 2022 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::ops::ControlFlow;
6
7use crate::painter::layer_workbench::passes::PassesSharedState;
8use crate::painter::layer_workbench::{Context, LayerWorkbenchState, OptimizerTileWriteOp};
9use crate::painter::{BlendMode, Color, Fill, Func, LayerProps, Style};
10
11pub fn skip_fully_covered_layers_pass<'w, 'c, P: LayerProps>(
12    workbench: &'w mut LayerWorkbenchState,
13    state: &'w mut PassesSharedState,
14    context: &'c Context<'_, P>,
15) -> ControlFlow<OptimizerTileWriteOp> {
16    #[derive(Debug)]
17    enum InterestingCover {
18        Opaque(Color),
19        Incomplete,
20    }
21
22    let mut first_interesting_cover = None;
23    // If layers were removed, we cannot assume anything because a visible layer
24    // might have been removed since last frame.
25    let mut visible_layers_are_unchanged = !state.layers_were_removed;
26    for (i, &id) in workbench.ids.iter_masked().rev() {
27        let props = context.props.get(id);
28
29        if !context.props.is_unchanged(id) {
30            visible_layers_are_unchanged = false;
31        }
32
33        let is_clipped = || {
34            matches!(props.func, Func::Draw(Style { is_clipped: true, .. }))
35                && !state.skip_clipping.contains(&id)
36        };
37
38        if is_clipped() || !workbench.layer_is_full(context, id, props.fill_rule) {
39            if first_interesting_cover.is_none() {
40                first_interesting_cover = Some(InterestingCover::Incomplete);
41                // The loop does not break here in order to try to cull some layers that are
42                // completely covered.
43            }
44        } else if let Func::Draw(Style {
45            fill: Fill::Solid(color),
46            blend_mode: BlendMode::Over,
47            ..
48        }) = props.func
49        {
50            if color.a == 1.0 {
51                if first_interesting_cover.is_none() {
52                    first_interesting_cover = Some(InterestingCover::Opaque(color));
53                }
54
55                workbench.ids.skip_until(i);
56
57                break;
58            }
59        }
60    }
61
62    let (i, bottom_color) = match first_interesting_cover {
63        // First opaque layer is skipped when blending.
64        Some(InterestingCover::Opaque(color)) => {
65            // All visible layers are unchanged so we can skip drawing altogether.
66            if visible_layers_are_unchanged {
67                return ControlFlow::Break(OptimizerTileWriteOp::None);
68            }
69
70            (1, color)
71        }
72        // The clear color is used as a virtual first opqaue layer.
73        None => (0, context.clear_color),
74        // Visible incomplete cover makes full optimization impossible.
75        Some(InterestingCover::Incomplete) => return ControlFlow::Continue(()),
76    };
77
78    let color = workbench.ids.iter_masked().skip(i).try_fold(bottom_color, |dst, (_, &id)| {
79        match context.props.get(id).func {
80            Func::Draw(Style { fill: Fill::Solid(color), blend_mode, .. }) => {
81                Some(blend_mode.blend(dst, color))
82            }
83            // Fill is not solid.
84            _ => None,
85        }
86    });
87
88    match color {
89        Some(color) => ControlFlow::Break(OptimizerTileWriteOp::Solid(color)),
90        None => ControlFlow::Continue(()),
91    }
92}