blob: 375b9c86c1cc86662c7cfcf69e5c4b23fa5f24fa [file] [log] [blame]
// Copyright 2022 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use std::ops::ControlFlow;
use crate::painter::layer_workbench::passes::PassesSharedState;
use crate::painter::layer_workbench::{Context, LayerWorkbenchState, OptimizerTileWriteOp};
use crate::painter::{BlendMode, Color, Fill, Func, LayerProps, Style};
pub fn skip_fully_covered_layers_pass<'w, 'c, P: LayerProps>(
workbench: &'w mut LayerWorkbenchState,
state: &'w mut PassesSharedState,
context: &'c Context<'_, P>,
) -> ControlFlow<OptimizerTileWriteOp> {
#[derive(Debug)]
enum InterestingCover {
Opaque(Color),
Incomplete,
}
let mut first_interesting_cover = None;
// If layers were removed, we cannot assume anything because a visible layer
// might have been removed since last frame.
let mut visible_layers_are_unchanged = !state.layers_were_removed;
for (i, &id) in workbench.ids.iter_masked().rev() {
let props = context.props.get(id);
if !context.props.is_unchanged(id) {
visible_layers_are_unchanged = false;
}
let is_clipped = || {
matches!(props.func, Func::Draw(Style { is_clipped: true, .. }))
&& !state.skip_clipping.contains(&id)
};
if is_clipped() || !workbench.layer_is_full(context, id, props.fill_rule) {
if first_interesting_cover.is_none() {
first_interesting_cover = Some(InterestingCover::Incomplete);
// The loop does not break here in order to try to cull some layers that are
// completely covered.
}
} else if let Func::Draw(Style {
fill: Fill::Solid(color),
blend_mode: BlendMode::Over,
..
}) = props.func
{
if color.a == 1.0 {
if first_interesting_cover.is_none() {
first_interesting_cover = Some(InterestingCover::Opaque(color));
}
workbench.ids.skip_until(i);
break;
}
}
}
let (i, bottom_color) = match first_interesting_cover {
// First opaque layer is skipped when blending.
Some(InterestingCover::Opaque(color)) => {
// All visible layers are unchanged so we can skip drawing altogether.
if visible_layers_are_unchanged {
return ControlFlow::Break(OptimizerTileWriteOp::None);
}
(1, color)
}
// The clear color is used as a virtual first opqaue layer.
None => (0, context.clear_color),
// Visible incomplete cover makes full optimization impossible.
Some(InterestingCover::Incomplete) => return ControlFlow::Continue(()),
};
let color = workbench.ids.iter_masked().skip(i).try_fold(bottom_color, |dst, (_, &id)| {
match context.props.get(id).func {
Func::Draw(Style { fill: Fill::Solid(color), blend_mode, .. }) => {
Some(blend_mode.blend(dst, color))
}
// Fill is not solid.
_ => None,
}
});
match color {
Some(color) => ControlFlow::Break(OptimizerTileWriteOp::Solid(color)),
None => ControlFlow::Continue(()),
}
}