blob: 6c9e528ef15d79d0b9f7181ee48e9da53060bcba [file] [log] [blame] [edit]
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "impeller/entity/save_layer_utils.h"
namespace impeller {
namespace {
bool SizeDifferenceUnderThreshold(Size a, Size b, Scalar threshold) {
return (std::abs(a.width - b.width) / b.width) < threshold &&
(std::abs(a.height - b.height) / b.height) < threshold;
}
} // namespace
std::optional<Rect> ComputeSaveLayerCoverage(
const Rect& content_coverage,
const Matrix& effect_transform,
const Rect& coverage_limit,
const std::shared_ptr<FilterContents>& image_filter,
bool flood_output_coverage,
bool flood_input_coverage) {
Rect coverage = content_coverage;
// There are three conditions that should cause input coverage to flood, the
// first is the presence of a backdrop filter on the saveLayer. The second is
// the presence of a color filter that effects transparent black on the
// saveLayer. The last is the presence of unbounded content within the
// saveLayer (such as a drawPaint, bdf, et cetera). Note that currently
// only the presence of a backdrop filter impacts this flag, while color
// filters are not yet handled
// (https://github.com/flutter/flutter/issues/154035) and unbounded coverage
// is handled in the display list dispatcher.
//
// Backdrop filters apply before the saveLayer is restored. The presence of
// a backdrop filter causes the content coverage of the saveLayer to be
// unbounded.
//
// If there is a color filter that needs to flood its output. The color filter
// is applied before any image filters, so this floods input coverage and not
// the output coverage. Technically, we only need to flood the output of the
// color filter and could allocate a render target sized just to the content,
// but we don't currenty have the means to do so. Flooding the coverage is a
// non-optimal but technically correct way to render this.
//
// If the saveLayer contains unbounded content, then at this point the
// dl_dispatcher will have set content coverage to Rect::MakeMaximum().
if (flood_input_coverage) {
coverage = Rect::MakeMaximum();
}
// The content coverage must be scaled by any image filters present on the
// saveLayer paint. For example, if a saveLayer has a coverage limit of
// 100x100, but it has a Matrix image filter that scales by one half, the
// actual coverage limit is 200x200.
if (image_filter) {
// Transform the input coverage into the global coordinate space before
// computing the bounds limit intersection. This is the "worst case"
// coverage value before we intersect with the content coverage below.
std::optional<Rect> source_coverage_limit =
image_filter->GetSourceCoverage(effect_transform, coverage_limit);
if (!source_coverage_limit.has_value()) {
// No intersection with parent coverage limit.
return std::nullopt;
}
// The image filter may change the coverage limit required to flood
// the parent layer. Returning the source coverage limit so that we
// can guarantee the render target is larger enough.
//
// See note below on flood_output_coverage.
if (flood_output_coverage || coverage.IsMaximum()) {
return source_coverage_limit;
}
// Trimming the content coverage by the coverage limit can reduce memory
// bandwith. But in cases where there are animated matrix filters, such as
// in the framework's zoom transition, the changing scale values continually
// change the source_coverage_limit. Intersecting the source_coverage_limit
// with the coverage may result in slightly different texture sizes each
// frame of the animation. This leads to non-optimal allocation patterns as
// differently sized textures cannot be reused. Hence the following
// herustic: If the coverage is within a semi-arbitrary percentage of the
// intersected coverage, then just use the transformed coverage. In other
// cases, use the intersection.
auto transformed_coverage = coverage.TransformBounds(effect_transform);
auto intersected_coverage =
transformed_coverage.Intersection(source_coverage_limit.value());
if (intersected_coverage.has_value() &&
SizeDifferenceUnderThreshold(transformed_coverage.GetSize(),
intersected_coverage->GetSize(), 0.2)) {
// Returning the transformed coverage is always correct, it just may
// be larger than the clip area or onscreen texture.
return transformed_coverage;
}
return intersected_coverage;
}
// If the input coverage is maximum, just return the coverage limit that
// is already in the global coordinate space.
//
// If flood_output_coverage is true, then the restore is applied with a
// destructive blend mode that requires flooding to the coverage limit.
// Technically we could only allocated a render target as big as the input
// coverage and then use a decal sampling mode to perform the flood. Returning
// the coverage limit is a correct but non optimal means of ensuring correct
// rendering.
if (flood_output_coverage || coverage.IsMaximum()) {
return coverage_limit;
}
// Transform the input coverage into the global coordinate space before
// computing the bounds limit intersection.
return coverage.TransformBounds(effect_transform)
.Intersection(coverage_limit);
}
} // namespace impeller