[Impeller] separate algorithm for computing render target size. (flutter/engine#54604)
Separate out the math for computing a saveLayer's allocated texture size based on input bounds, paint, bdf, coverage limit, et cetera.
This commit is contained in:
parent
4994d9c4d6
commit
060d89a46f
@ -173,6 +173,7 @@
|
|||||||
../../../flutter/impeller/entity/entity_unittests.cc
|
../../../flutter/impeller/entity/entity_unittests.cc
|
||||||
../../../flutter/impeller/entity/geometry/geometry_unittests.cc
|
../../../flutter/impeller/entity/geometry/geometry_unittests.cc
|
||||||
../../../flutter/impeller/entity/render_target_cache_unittests.cc
|
../../../flutter/impeller/entity/render_target_cache_unittests.cc
|
||||||
|
../../../flutter/impeller/entity/save_layer_utils_unittests.cc
|
||||||
../../../flutter/impeller/fixtures
|
../../../flutter/impeller/fixtures
|
||||||
../../../flutter/impeller/geometry/README.md
|
../../../flutter/impeller/geometry/README.md
|
||||||
../../../flutter/impeller/geometry/geometry_unittests.cc
|
../../../flutter/impeller/geometry/geometry_unittests.cc
|
||||||
|
@ -42871,6 +42871,8 @@ ORIGIN: ../../../flutter/impeller/entity/inline_pass_context.cc + ../../../flutt
|
|||||||
ORIGIN: ../../../flutter/impeller/entity/inline_pass_context.h + ../../../flutter/LICENSE
|
ORIGIN: ../../../flutter/impeller/entity/inline_pass_context.h + ../../../flutter/LICENSE
|
||||||
ORIGIN: ../../../flutter/impeller/entity/render_target_cache.cc + ../../../flutter/LICENSE
|
ORIGIN: ../../../flutter/impeller/entity/render_target_cache.cc + ../../../flutter/LICENSE
|
||||||
ORIGIN: ../../../flutter/impeller/entity/render_target_cache.h + ../../../flutter/LICENSE
|
ORIGIN: ../../../flutter/impeller/entity/render_target_cache.h + ../../../flutter/LICENSE
|
||||||
|
ORIGIN: ../../../flutter/impeller/entity/save_layer_utils.cc + ../../../flutter/LICENSE
|
||||||
|
ORIGIN: ../../../flutter/impeller/entity/save_layer_utils.h + ../../../flutter/LICENSE
|
||||||
ORIGIN: ../../../flutter/impeller/entity/shaders/blending/advanced_blend.frag + ../../../flutter/LICENSE
|
ORIGIN: ../../../flutter/impeller/entity/shaders/blending/advanced_blend.frag + ../../../flutter/LICENSE
|
||||||
ORIGIN: ../../../flutter/impeller/entity/shaders/blending/advanced_blend.vert + ../../../flutter/LICENSE
|
ORIGIN: ../../../flutter/impeller/entity/shaders/blending/advanced_blend.vert + ../../../flutter/LICENSE
|
||||||
ORIGIN: ../../../flutter/impeller/entity/shaders/blending/blend_select.glsl + ../../../flutter/LICENSE
|
ORIGIN: ../../../flutter/impeller/entity/shaders/blending/blend_select.glsl + ../../../flutter/LICENSE
|
||||||
@ -45761,6 +45763,8 @@ FILE: ../../../flutter/impeller/entity/inline_pass_context.cc
|
|||||||
FILE: ../../../flutter/impeller/entity/inline_pass_context.h
|
FILE: ../../../flutter/impeller/entity/inline_pass_context.h
|
||||||
FILE: ../../../flutter/impeller/entity/render_target_cache.cc
|
FILE: ../../../flutter/impeller/entity/render_target_cache.cc
|
||||||
FILE: ../../../flutter/impeller/entity/render_target_cache.h
|
FILE: ../../../flutter/impeller/entity/render_target_cache.h
|
||||||
|
FILE: ../../../flutter/impeller/entity/save_layer_utils.cc
|
||||||
|
FILE: ../../../flutter/impeller/entity/save_layer_utils.h
|
||||||
FILE: ../../../flutter/impeller/entity/shaders/blending/advanced_blend.frag
|
FILE: ../../../flutter/impeller/entity/shaders/blending/advanced_blend.frag
|
||||||
FILE: ../../../flutter/impeller/entity/shaders/blending/advanced_blend.vert
|
FILE: ../../../flutter/impeller/entity/shaders/blending/advanced_blend.vert
|
||||||
FILE: ../../../flutter/impeller/entity/shaders/blending/blend_select.glsl
|
FILE: ../../../flutter/impeller/entity/shaders/blending/blend_select.glsl
|
||||||
|
@ -127,12 +127,13 @@ TEST_P(AiksTest, ColorWheel) {
|
|||||||
|
|
||||||
draw_color_wheel(canvas);
|
draw_color_wheel(canvas);
|
||||||
auto color_wheel_picture = canvas.EndRecordingAsPicture();
|
auto color_wheel_picture = canvas.EndRecordingAsPicture();
|
||||||
auto snapshot = color_wheel_picture.Snapshot(renderer);
|
auto image = color_wheel_picture.ToImage(
|
||||||
if (!snapshot.has_value() || !snapshot->texture) {
|
renderer, ISize{GetWindowSize().width, GetWindowSize().height});
|
||||||
|
if (!image) {
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
color_wheel_image = snapshot->texture;
|
color_wheel_image = image;
|
||||||
color_wheel_transform = snapshot->transform;
|
color_wheel_transform = Matrix();
|
||||||
}
|
}
|
||||||
|
|
||||||
Canvas canvas;
|
Canvas canvas;
|
||||||
|
@ -814,8 +814,7 @@ void Canvas::SaveLayer(const Paint& paint,
|
|||||||
const std::shared_ptr<ImageFilter>& backdrop_filter,
|
const std::shared_ptr<ImageFilter>& backdrop_filter,
|
||||||
ContentBoundsPromise bounds_promise,
|
ContentBoundsPromise bounds_promise,
|
||||||
uint32_t total_content_depth,
|
uint32_t total_content_depth,
|
||||||
bool can_distribute_opacity,
|
bool can_distribute_opacity) {
|
||||||
bool bounds_from_caller) {
|
|
||||||
if (can_distribute_opacity && !backdrop_filter &&
|
if (can_distribute_opacity && !backdrop_filter &&
|
||||||
Paint::CanApplyOpacityPeephole(paint) &&
|
Paint::CanApplyOpacityPeephole(paint) &&
|
||||||
bounds_promise != ContentBoundsPromise::kMayClipContents) {
|
bounds_promise != ContentBoundsPromise::kMayClipContents) {
|
||||||
@ -837,7 +836,7 @@ void Canvas::SaveLayer(const Paint& paint,
|
|||||||
|
|
||||||
auto& new_layer_pass = GetCurrentPass();
|
auto& new_layer_pass = GetCurrentPass();
|
||||||
if (bounds) {
|
if (bounds) {
|
||||||
new_layer_pass.SetBoundsLimit(bounds, bounds_promise);
|
new_layer_pass.SetBoundsLimit(bounds);
|
||||||
}
|
}
|
||||||
|
|
||||||
// When applying a save layer, absorb any pending distributed opacity.
|
// When applying a save layer, absorb any pending distributed opacity.
|
||||||
|
@ -76,8 +76,7 @@ class Canvas {
|
|||||||
const std::shared_ptr<ImageFilter>& backdrop_filter = nullptr,
|
const std::shared_ptr<ImageFilter>& backdrop_filter = nullptr,
|
||||||
ContentBoundsPromise bounds_promise = ContentBoundsPromise::kUnknown,
|
ContentBoundsPromise bounds_promise = ContentBoundsPromise::kUnknown,
|
||||||
uint32_t total_content_depth = kMaxDepth,
|
uint32_t total_content_depth = kMaxDepth,
|
||||||
bool can_distribute_opacity = false,
|
bool can_distribute_opacity = false);
|
||||||
bool bounds_from_caller = false);
|
|
||||||
|
|
||||||
virtual bool Restore();
|
virtual bool Restore();
|
||||||
|
|
||||||
|
@ -12,10 +12,12 @@
|
|||||||
#include "impeller/core/allocator.h"
|
#include "impeller/core/allocator.h"
|
||||||
#include "impeller/core/formats.h"
|
#include "impeller/core/formats.h"
|
||||||
#include "impeller/entity/contents/clip_contents.h"
|
#include "impeller/entity/contents/clip_contents.h"
|
||||||
|
#include "impeller/entity/contents/filters/filter_contents.h"
|
||||||
#include "impeller/entity/contents/framebuffer_blend_contents.h"
|
#include "impeller/entity/contents/framebuffer_blend_contents.h"
|
||||||
#include "impeller/entity/contents/text_contents.h"
|
#include "impeller/entity/contents/text_contents.h"
|
||||||
#include "impeller/entity/entity.h"
|
#include "impeller/entity/entity.h"
|
||||||
#include "impeller/entity/entity_pass_clip_stack.h"
|
#include "impeller/entity/entity_pass_clip_stack.h"
|
||||||
|
#include "impeller/entity/save_layer_utils.h"
|
||||||
#include "impeller/geometry/color.h"
|
#include "impeller/geometry/color.h"
|
||||||
#include "impeller/renderer/render_target.h"
|
#include "impeller/renderer/render_target.h"
|
||||||
|
|
||||||
@ -314,15 +316,9 @@ void ExperimentalCanvas::SaveLayer(
|
|||||||
const std::shared_ptr<ImageFilter>& backdrop_filter,
|
const std::shared_ptr<ImageFilter>& backdrop_filter,
|
||||||
ContentBoundsPromise bounds_promise,
|
ContentBoundsPromise bounds_promise,
|
||||||
uint32_t total_content_depth,
|
uint32_t total_content_depth,
|
||||||
bool can_distribute_opacity,
|
bool can_distribute_opacity) {
|
||||||
bool bounds_from_caller) {
|
|
||||||
TRACE_EVENT0("flutter", "Canvas::saveLayer");
|
TRACE_EVENT0("flutter", "Canvas::saveLayer");
|
||||||
|
|
||||||
if (bounds.has_value() && bounds->IsEmpty()) {
|
|
||||||
Save(total_content_depth);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!clip_coverage_stack_.HasCoverage()) {
|
if (!clip_coverage_stack_.HasCoverage()) {
|
||||||
// The current clip is empty. This means the pass texture won't be
|
// The current clip is empty. This means the pass texture won't be
|
||||||
// visible, so skip it.
|
// visible, so skip it.
|
||||||
@ -346,13 +342,6 @@ void ExperimentalCanvas::SaveLayer(
|
|||||||
->GetSize()))
|
->GetSize()))
|
||||||
.Intersection(current_clip_coverage);
|
.Intersection(current_clip_coverage);
|
||||||
|
|
||||||
if (!maybe_coverage_limit.has_value()) {
|
|
||||||
Save(total_content_depth);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
maybe_coverage_limit = maybe_coverage_limit->Intersection(
|
|
||||||
Rect::MakeSize(render_target_.GetRenderTargetSize()));
|
|
||||||
|
|
||||||
if (!maybe_coverage_limit.has_value() || maybe_coverage_limit->IsEmpty()) {
|
if (!maybe_coverage_limit.has_value() || maybe_coverage_limit->IsEmpty()) {
|
||||||
Save(total_content_depth);
|
Save(total_content_depth);
|
||||||
return;
|
return;
|
||||||
@ -367,12 +356,33 @@ void ExperimentalCanvas::SaveLayer(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<FilterContents> filter_contents;
|
||||||
|
if (paint.image_filter) {
|
||||||
|
filter_contents = paint.image_filter->GetFilterContents();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<Rect> maybe_subpass_coverage = ComputeSaveLayerCoverage(
|
||||||
|
bounds.value_or(Rect::MakeMaximum()),
|
||||||
|
transform_stack_.back().transform, //
|
||||||
|
coverage_limit, //
|
||||||
|
filter_contents, //
|
||||||
|
/*flood_output_coverage=*/
|
||||||
|
Entity::IsBlendModeDestructive(paint.blend_mode), //
|
||||||
|
/*flood_input_coverage=*/!!backdrop_filter //
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!maybe_subpass_coverage.has_value() ||
|
||||||
|
maybe_subpass_coverage->IsEmpty()) {
|
||||||
|
Save(total_content_depth);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto subpass_coverage = maybe_subpass_coverage.value();
|
||||||
|
|
||||||
// Backdrop filter state, ignored if there is no BDF.
|
// Backdrop filter state, ignored if there is no BDF.
|
||||||
std::shared_ptr<FilterContents> backdrop_filter_contents;
|
std::shared_ptr<FilterContents> backdrop_filter_contents;
|
||||||
Point local_position = {0, 0};
|
Point local_position = {0, 0};
|
||||||
if (backdrop_filter) {
|
if (backdrop_filter) {
|
||||||
local_position =
|
local_position = subpass_coverage.GetOrigin() - GetGlobalPassPosition();
|
||||||
current_clip_coverage.GetOrigin() - GetGlobalPassPosition();
|
|
||||||
EntityPass::BackdropFilterProc backdrop_filter_proc =
|
EntityPass::BackdropFilterProc backdrop_filter_proc =
|
||||||
[backdrop_filter = backdrop_filter->Clone()](
|
[backdrop_filter = backdrop_filter->Clone()](
|
||||||
const FilterInput::Ref& input, const Matrix& effect_transform,
|
const FilterInput::Ref& input, const Matrix& effect_transform,
|
||||||
@ -409,21 +419,6 @@ void ExperimentalCanvas::SaveLayer(
|
|||||||
paint_copy.color.alpha *= transform_stack_.back().distributed_opacity;
|
paint_copy.color.alpha *= transform_stack_.back().distributed_opacity;
|
||||||
transform_stack_.back().distributed_opacity = 1.0;
|
transform_stack_.back().distributed_opacity = 1.0;
|
||||||
|
|
||||||
// Backdrop Filter must expand bounds to at least the clip stack, otherwise
|
|
||||||
// the coverage of the parent render pass.
|
|
||||||
Rect subpass_coverage;
|
|
||||||
if (backdrop_filter_contents ||
|
|
||||||
Entity::IsBlendModeDestructive(paint.blend_mode) || !bounds.has_value()) {
|
|
||||||
subpass_coverage = coverage_limit;
|
|
||||||
// TODO(jonahwilliams): if we have tight bounds we should be able to reduce
|
|
||||||
// this size here. if (bounds.has_value() && bounds_from_caller) {
|
|
||||||
// subpass_coverage =
|
|
||||||
// coverage_limit.Intersection(bounds.value()).value_or(bounds.value());
|
|
||||||
// }
|
|
||||||
} else {
|
|
||||||
subpass_coverage = bounds->TransformBounds(GetCurrentTransform());
|
|
||||||
}
|
|
||||||
|
|
||||||
render_passes_.push_back(LazyRenderingConfig(
|
render_passes_.push_back(LazyRenderingConfig(
|
||||||
renderer_, //
|
renderer_, //
|
||||||
CreateRenderTarget(renderer_, //
|
CreateRenderTarget(renderer_, //
|
||||||
|
@ -71,8 +71,7 @@ class ExperimentalCanvas : public Canvas {
|
|||||||
const std::shared_ptr<ImageFilter>& backdrop_filter,
|
const std::shared_ptr<ImageFilter>& backdrop_filter,
|
||||||
ContentBoundsPromise bounds_promise,
|
ContentBoundsPromise bounds_promise,
|
||||||
uint32_t total_content_depth,
|
uint32_t total_content_depth,
|
||||||
bool can_distribute_opacity,
|
bool can_distribute_opacity) override;
|
||||||
bool bounds_from_caller) override;
|
|
||||||
|
|
||||||
bool Restore() override;
|
bool Restore() override;
|
||||||
|
|
||||||
|
@ -8,26 +8,10 @@
|
|||||||
#include <optional>
|
#include <optional>
|
||||||
|
|
||||||
#include "impeller/base/validation.h"
|
#include "impeller/base/validation.h"
|
||||||
#include "impeller/entity/entity.h"
|
|
||||||
#include "impeller/renderer/render_target.h"
|
#include "impeller/renderer/render_target.h"
|
||||||
#include "impeller/renderer/snapshot.h"
|
|
||||||
|
|
||||||
namespace impeller {
|
namespace impeller {
|
||||||
|
|
||||||
std::optional<Snapshot> Picture::Snapshot(AiksContext& context) {
|
|
||||||
auto coverage = pass->GetElementsCoverage(std::nullopt);
|
|
||||||
if (!coverage.has_value() || coverage->IsEmpty()) {
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto translate = Matrix::MakeTranslation(-coverage->GetOrigin());
|
|
||||||
auto texture =
|
|
||||||
RenderToTexture(context, ISize(coverage->GetSize()), translate);
|
|
||||||
return impeller::Snapshot{
|
|
||||||
.texture = std::move(texture),
|
|
||||||
.transform = Matrix::MakeTranslation(coverage->GetOrigin())};
|
|
||||||
}
|
|
||||||
|
|
||||||
std::shared_ptr<Texture> Picture::ToImage(AiksContext& context,
|
std::shared_ptr<Texture> Picture::ToImage(AiksContext& context,
|
||||||
ISize size) const {
|
ISize size) const {
|
||||||
if (size.IsEmpty()) {
|
if (size.IsEmpty()) {
|
||||||
|
@ -16,8 +16,6 @@ namespace impeller {
|
|||||||
struct Picture {
|
struct Picture {
|
||||||
std::unique_ptr<EntityPass> pass;
|
std::unique_ptr<EntityPass> pass;
|
||||||
|
|
||||||
std::optional<Snapshot> Snapshot(AiksContext& context);
|
|
||||||
|
|
||||||
std::shared_ptr<Texture> ToImage(AiksContext& context, ISize size) const;
|
std::shared_ptr<Texture> ToImage(AiksContext& context, ISize size) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
@ -723,22 +723,18 @@ void DlDispatcherBase::saveLayer(const SkRect& bounds,
|
|||||||
? ContentBoundsPromise::kMayClipContents
|
? ContentBoundsPromise::kMayClipContents
|
||||||
: ContentBoundsPromise::kContainsContents;
|
: ContentBoundsPromise::kContainsContents;
|
||||||
std::optional<Rect> impeller_bounds;
|
std::optional<Rect> impeller_bounds;
|
||||||
|
// If the content is unbounded but has developer specified bounds, we take
|
||||||
|
// the original bounds so that we clip the content as expected.
|
||||||
if (!options.content_is_unbounded() || options.bounds_from_caller()) {
|
if (!options.content_is_unbounded() || options.bounds_from_caller()) {
|
||||||
impeller_bounds = skia_conversions::ToRect(bounds);
|
impeller_bounds = skia_conversions::ToRect(bounds);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Empty bounds on a save layer that contains a BDF or destructive blend
|
GetCanvas().SaveLayer(
|
||||||
// should be treated as unbounded. All other empty bounds can be skipped.
|
paint, impeller_bounds, ToImageFilter(backdrop), promise,
|
||||||
if (impeller_bounds.has_value() && impeller_bounds->IsEmpty() &&
|
total_content_depth,
|
||||||
(backdrop != nullptr ||
|
// Unbounded content can still have user specified bounds that require a
|
||||||
Entity::IsBlendModeDestructive(paint.blend_mode))) {
|
// saveLayer to be created to perform the clip.
|
||||||
impeller_bounds = std::nullopt;
|
options.can_distribute_opacity() && !options.content_is_unbounded());
|
||||||
}
|
|
||||||
|
|
||||||
GetCanvas().SaveLayer(paint, impeller_bounds, ToImageFilter(backdrop),
|
|
||||||
promise, total_content_depth,
|
|
||||||
options.can_distribute_opacity(),
|
|
||||||
options.bounds_from_caller());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// |flutter::DlOpReceiver|
|
// |flutter::DlOpReceiver|
|
||||||
|
@ -206,6 +206,8 @@ impeller_component("entity") {
|
|||||||
"inline_pass_context.h",
|
"inline_pass_context.h",
|
||||||
"render_target_cache.cc",
|
"render_target_cache.cc",
|
||||||
"render_target_cache.h",
|
"render_target_cache.h",
|
||||||
|
"save_layer_utils.cc",
|
||||||
|
"save_layer_utils.h",
|
||||||
]
|
]
|
||||||
|
|
||||||
public_deps = [
|
public_deps = [
|
||||||
@ -260,6 +262,7 @@ impeller_component("entity_unittests") {
|
|||||||
"entity_unittests.cc",
|
"entity_unittests.cc",
|
||||||
"geometry/geometry_unittests.cc",
|
"geometry/geometry_unittests.cc",
|
||||||
"render_target_cache_unittests.cc",
|
"render_target_cache_unittests.cc",
|
||||||
|
"save_layer_utils_unittests.cc",
|
||||||
]
|
]
|
||||||
|
|
||||||
deps = [
|
deps = [
|
||||||
|
@ -24,6 +24,7 @@
|
|||||||
#include "impeller/entity/entity.h"
|
#include "impeller/entity/entity.h"
|
||||||
#include "impeller/entity/entity_pass_clip_stack.h"
|
#include "impeller/entity/entity_pass_clip_stack.h"
|
||||||
#include "impeller/entity/inline_pass_context.h"
|
#include "impeller/entity/inline_pass_context.h"
|
||||||
|
#include "impeller/entity/save_layer_utils.h"
|
||||||
#include "impeller/geometry/color.h"
|
#include "impeller/geometry/color.h"
|
||||||
#include "impeller/geometry/rect.h"
|
#include "impeller/geometry/rect.h"
|
||||||
#include "impeller/geometry/size.h"
|
#include "impeller/geometry/size.h"
|
||||||
@ -60,48 +61,14 @@ void EntityPass::SetDelegate(std::shared_ptr<EntityPassDelegate> delegate) {
|
|||||||
delegate_ = std::move(delegate);
|
delegate_ = std::move(delegate);
|
||||||
}
|
}
|
||||||
|
|
||||||
void EntityPass::SetBoundsLimit(std::optional<Rect> bounds_limit,
|
void EntityPass::SetBoundsLimit(std::optional<Rect> bounds_limit) {
|
||||||
ContentBoundsPromise bounds_promise) {
|
|
||||||
bounds_limit_ = bounds_limit;
|
bounds_limit_ = bounds_limit;
|
||||||
bounds_promise_ = bounds_limit.has_value() ? bounds_promise
|
|
||||||
: ContentBoundsPromise::kUnknown;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<Rect> EntityPass::GetBoundsLimit() const {
|
std::optional<Rect> EntityPass::GetBoundsLimit() const {
|
||||||
return bounds_limit_;
|
return bounds_limit_;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool EntityPass::GetBoundsLimitMightClipContent() const {
|
|
||||||
switch (bounds_promise_) {
|
|
||||||
case ContentBoundsPromise::kUnknown:
|
|
||||||
// If the promise is unknown due to not having a bounds limit,
|
|
||||||
// then no clipping will occur. But if we have a bounds limit
|
|
||||||
// and it is unkown, then we can make no promises about whether
|
|
||||||
// it causes clipping of the entity pass contents and we
|
|
||||||
// conservatively return true.
|
|
||||||
return bounds_limit_.has_value();
|
|
||||||
case ContentBoundsPromise::kContainsContents:
|
|
||||||
FML_DCHECK(bounds_limit_.has_value());
|
|
||||||
return false;
|
|
||||||
case ContentBoundsPromise::kMayClipContents:
|
|
||||||
FML_DCHECK(bounds_limit_.has_value());
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
FML_UNREACHABLE();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool EntityPass::GetBoundsLimitIsSnug() const {
|
|
||||||
switch (bounds_promise_) {
|
|
||||||
case ContentBoundsPromise::kUnknown:
|
|
||||||
return false;
|
|
||||||
case ContentBoundsPromise::kContainsContents:
|
|
||||||
case ContentBoundsPromise::kMayClipContents:
|
|
||||||
FML_DCHECK(bounds_limit_.has_value());
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
FML_UNREACHABLE();
|
|
||||||
}
|
|
||||||
|
|
||||||
void EntityPass::AddEntity(Entity entity) {
|
void EntityPass::AddEntity(Entity entity) {
|
||||||
if (entity.GetBlendMode() == BlendMode::kSourceOver &&
|
if (entity.GetBlendMode() == BlendMode::kSourceOver &&
|
||||||
entity.GetContents()->IsOpaque(entity.GetTransform())) {
|
entity.GetContents()->IsOpaque(entity.GetTransform())) {
|
||||||
@ -160,115 +127,6 @@ size_t EntityPass::GetSubpassesDepth() const {
|
|||||||
return max_subpass_depth + 1u;
|
return max_subpass_depth + 1u;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<Rect> EntityPass::GetElementsCoverage(
|
|
||||||
std::optional<Rect> coverage_limit) const {
|
|
||||||
std::optional<Rect> accumulated_coverage;
|
|
||||||
for (const auto& element : elements_) {
|
|
||||||
std::optional<Rect> element_coverage;
|
|
||||||
|
|
||||||
if (auto entity = std::get_if<Entity>(&element)) {
|
|
||||||
element_coverage = entity->GetCoverage();
|
|
||||||
|
|
||||||
// When the coverage limit is std::nullopt, that means there is no limit,
|
|
||||||
// as opposed to empty coverage.
|
|
||||||
if (element_coverage.has_value() && coverage_limit.has_value()) {
|
|
||||||
const auto* filter = entity->GetContents()->AsFilter();
|
|
||||||
if (!filter || filter->IsTranslationOnly()) {
|
|
||||||
element_coverage =
|
|
||||||
element_coverage->Intersection(coverage_limit.value());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (auto subpass_ptr =
|
|
||||||
std::get_if<std::unique_ptr<EntityPass>>(&element)) {
|
|
||||||
auto& subpass = *subpass_ptr->get();
|
|
||||||
|
|
||||||
std::optional<Rect> unfiltered_coverage =
|
|
||||||
GetSubpassCoverage(subpass, std::nullopt);
|
|
||||||
|
|
||||||
// If the current pass elements have any coverage so far and there's a
|
|
||||||
// backdrop filter, then incorporate the backdrop filter in the
|
|
||||||
// pre-filtered coverage of the subpass.
|
|
||||||
if (accumulated_coverage.has_value() && subpass.backdrop_filter_proc_) {
|
|
||||||
std::shared_ptr<FilterContents> backdrop_filter =
|
|
||||||
subpass.backdrop_filter_proc_(
|
|
||||||
FilterInput::Make(accumulated_coverage.value()),
|
|
||||||
subpass.transform_,
|
|
||||||
Entity::RenderingMode::kSubpassAppendSnapshotTransform);
|
|
||||||
if (backdrop_filter) {
|
|
||||||
auto backdrop_coverage = backdrop_filter->GetCoverage({});
|
|
||||||
unfiltered_coverage =
|
|
||||||
Rect::Union(unfiltered_coverage, backdrop_coverage);
|
|
||||||
} else {
|
|
||||||
VALIDATION_LOG << "The EntityPass backdrop filter proc didn't return "
|
|
||||||
"a valid filter.";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!unfiltered_coverage.has_value()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Additionally, subpass textures may be passed through filters, which may
|
|
||||||
// modify the coverage.
|
|
||||||
//
|
|
||||||
// Note that we currently only assume that ImageFilters (such as blurs and
|
|
||||||
// matrix transforms) may modify coverage, although it's technically
|
|
||||||
// possible ColorFilters to affect coverage as well. For example: A
|
|
||||||
// ColorMatrixFilter could output a completely transparent result, and
|
|
||||||
// we could potentially detect this case as zero coverage in the future.
|
|
||||||
std::shared_ptr<FilterContents> image_filter =
|
|
||||||
subpass.delegate_->WithImageFilter(*unfiltered_coverage,
|
|
||||||
subpass.transform_);
|
|
||||||
if (image_filter) {
|
|
||||||
Entity subpass_entity;
|
|
||||||
subpass_entity.SetTransform(subpass.transform_);
|
|
||||||
element_coverage = image_filter->GetCoverage(subpass_entity);
|
|
||||||
} else {
|
|
||||||
element_coverage = unfiltered_coverage;
|
|
||||||
}
|
|
||||||
|
|
||||||
element_coverage = Rect::Intersection(element_coverage, coverage_limit);
|
|
||||||
} else {
|
|
||||||
FML_UNREACHABLE();
|
|
||||||
}
|
|
||||||
|
|
||||||
accumulated_coverage = Rect::Union(accumulated_coverage, element_coverage);
|
|
||||||
}
|
|
||||||
return accumulated_coverage;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::optional<Rect> EntityPass::GetSubpassCoverage(
|
|
||||||
const EntityPass& subpass,
|
|
||||||
std::optional<Rect> coverage_limit) const {
|
|
||||||
if (subpass.bounds_limit_.has_value() && subpass.GetBoundsLimitIsSnug()) {
|
|
||||||
return subpass.bounds_limit_->TransformBounds(subpass.transform_);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::shared_ptr<FilterContents> image_filter =
|
|
||||||
subpass.delegate_->WithImageFilter(Rect(), subpass.transform_);
|
|
||||||
|
|
||||||
// If the subpass has an image filter, then its coverage space may deviate
|
|
||||||
// from the parent pass and make intersecting with the pass coverage limit
|
|
||||||
// unsafe.
|
|
||||||
if (image_filter && coverage_limit.has_value()) {
|
|
||||||
coverage_limit = image_filter->GetSourceCoverage(subpass.transform_,
|
|
||||||
coverage_limit.value());
|
|
||||||
}
|
|
||||||
|
|
||||||
auto entities_coverage = subpass.GetElementsCoverage(coverage_limit);
|
|
||||||
// The entities don't cover anything. There is nothing to do.
|
|
||||||
if (!entities_coverage.has_value()) {
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!subpass.bounds_limit_.has_value()) {
|
|
||||||
return entities_coverage;
|
|
||||||
}
|
|
||||||
auto user_bounds_coverage =
|
|
||||||
subpass.bounds_limit_->TransformBounds(subpass.transform_);
|
|
||||||
return entities_coverage->Intersection(user_bounds_coverage);
|
|
||||||
}
|
|
||||||
|
|
||||||
EntityPass* EntityPass::GetSuperpass() const {
|
EntityPass* EntityPass::GetSuperpass() const {
|
||||||
return superpass_;
|
return superpass_;
|
||||||
}
|
}
|
||||||
@ -641,10 +499,18 @@ EntityPass::EntityResult EntityPass::GetEntityForElement(
|
|||||||
return EntityPass::EntityResult::Skip();
|
return EntityPass::EntityResult::Skip();
|
||||||
}
|
}
|
||||||
|
|
||||||
auto subpass_coverage =
|
std::shared_ptr<FilterContents> image_filter =
|
||||||
(subpass->flood_clip_ || subpass_backdrop_filter_contents)
|
subpass->delegate_->WithImageFilter(Rect(), subpass->transform_);
|
||||||
? coverage_limit
|
|
||||||
: GetSubpassCoverage(*subpass, coverage_limit);
|
auto subpass_coverage = ComputeSaveLayerCoverage(
|
||||||
|
subpass->bounds_limit_.value_or(Rect::MakeMaximum()), //
|
||||||
|
subpass->transform_, //
|
||||||
|
coverage_limit.value(), //
|
||||||
|
image_filter, //
|
||||||
|
/*flood_output_coverage=*/subpass->flood_clip_, //
|
||||||
|
/*flood_input_coverage=*/!!subpass_backdrop_filter_contents //
|
||||||
|
);
|
||||||
|
|
||||||
if (!subpass_coverage.has_value()) {
|
if (!subpass_coverage.has_value()) {
|
||||||
return EntityPass::EntityResult::Skip();
|
return EntityPass::EntityResult::Skip();
|
||||||
}
|
}
|
||||||
|
@ -67,33 +67,13 @@ class EntityPass {
|
|||||||
|
|
||||||
void SetDelegate(std::shared_ptr<EntityPassDelegate> delgate);
|
void SetDelegate(std::shared_ptr<EntityPassDelegate> delgate);
|
||||||
|
|
||||||
/// @brief Set the bounds limit, which is provided by the user when creating
|
/// @brief Set the computed content bounds, or std::nullopt if the contents
|
||||||
/// a SaveLayer. This is a hint that allows the user to communicate
|
/// are unbounded.
|
||||||
/// that it's OK to not render content outside of the bounds.
|
void SetBoundsLimit(std::optional<Rect> content_bounds);
|
||||||
///
|
|
||||||
/// For consistency with Skia, we effectively treat this like a
|
|
||||||
/// rectangle clip by forcing the subpass texture size to never exceed
|
|
||||||
/// it.
|
|
||||||
///
|
|
||||||
/// The entity pass will assume that these bounds cause a clipping
|
|
||||||
/// effect on the layer unless this call is followed up with a
|
|
||||||
/// call to |SetBoundsClipsContent()| specifying otherwise.
|
|
||||||
void SetBoundsLimit(
|
|
||||||
std::optional<Rect> bounds_limit,
|
|
||||||
ContentBoundsPromise bounds_promise = ContentBoundsPromise::kUnknown);
|
|
||||||
|
|
||||||
/// @brief Get the bounds limit, which is provided by the user when creating
|
/// @brief Get the bounds limit.
|
||||||
/// a SaveLayer.
|
|
||||||
std::optional<Rect> GetBoundsLimit() const;
|
std::optional<Rect> GetBoundsLimit() const;
|
||||||
|
|
||||||
/// @brief Indicates if the bounds limit set using |SetBoundsLimit()|
|
|
||||||
/// might clip the contents of the pass.
|
|
||||||
bool GetBoundsLimitMightClipContent() const;
|
|
||||||
|
|
||||||
/// @brief Indicates if the bounds limit set using |SetBoundsLimit()|
|
|
||||||
/// is a reasonably tight estimate of the bounds of the contents.
|
|
||||||
bool GetBoundsLimitIsSnug() const;
|
|
||||||
|
|
||||||
size_t GetSubpassesDepth() const;
|
size_t GetSubpassesDepth() const;
|
||||||
|
|
||||||
/// @brief Add an entity to the current entity pass.
|
/// @brief Add an entity to the current entity pass.
|
||||||
@ -181,30 +161,6 @@ class EntityPass {
|
|||||||
required_mip_count_ = mip_count;
|
required_mip_count_ = mip_count;
|
||||||
}
|
}
|
||||||
|
|
||||||
//----------------------------------------------------------------------------
|
|
||||||
/// @brief Computes the coverage of a given subpass. This is used to
|
|
||||||
/// determine the texture size of a given subpass before it's rendered
|
|
||||||
/// to and passed through the subpass ImageFilter, if any.
|
|
||||||
///
|
|
||||||
/// @param[in] subpass The EntityPass for which to compute
|
|
||||||
/// pre-filteredcoverage.
|
|
||||||
/// @param[in] coverage_limit Confines coverage to a specified area. This
|
|
||||||
/// hint is used to trim coverage to the root
|
|
||||||
/// framebuffer area. `std::nullopt` means there
|
|
||||||
/// is no limit.
|
|
||||||
///
|
|
||||||
/// @return The screen space pixel area that the subpass contents will render
|
|
||||||
/// into, prior to being transformed by the subpass ImageFilter, if
|
|
||||||
/// any. `std::nullopt` means rendering the subpass will have no
|
|
||||||
/// effect on the color attachment.
|
|
||||||
///
|
|
||||||
std::optional<Rect> GetSubpassCoverage(
|
|
||||||
const EntityPass& subpass,
|
|
||||||
std::optional<Rect> coverage_limit) const;
|
|
||||||
|
|
||||||
std::optional<Rect> GetElementsCoverage(
|
|
||||||
std::optional<Rect> coverage_limit) const;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct EntityResult {
|
struct EntityResult {
|
||||||
enum Status {
|
enum Status {
|
||||||
@ -333,7 +289,6 @@ class EntityPass {
|
|||||||
BlendMode blend_mode_ = BlendMode::kSourceOver;
|
BlendMode blend_mode_ = BlendMode::kSourceOver;
|
||||||
bool flood_clip_ = false;
|
bool flood_clip_ = false;
|
||||||
std::optional<Rect> bounds_limit_;
|
std::optional<Rect> bounds_limit_;
|
||||||
ContentBoundsPromise bounds_promise_ = ContentBoundsPromise::kUnknown;
|
|
||||||
int32_t required_mip_count_ = 1;
|
int32_t required_mip_count_ = 1;
|
||||||
|
|
||||||
/// These values indicate whether something has been added to the EntityPass
|
/// These values indicate whether something has been added to the EntityPass
|
||||||
|
@ -119,73 +119,10 @@ auto CreatePassWithRectPath(
|
|||||||
PathBuilder{}.AddRect(rect).TakePath(), Color::Red()));
|
PathBuilder{}.AddRect(rect).TakePath(), Color::Red()));
|
||||||
subpass->AddEntity(std::move(entity));
|
subpass->AddEntity(std::move(entity));
|
||||||
subpass->SetDelegate(std::make_unique<TestPassDelegate>(collapse));
|
subpass->SetDelegate(std::make_unique<TestPassDelegate>(collapse));
|
||||||
subpass->SetBoundsLimit(bounds_hint, bounds_promise);
|
subpass->SetBoundsLimit(bounds_hint);
|
||||||
return subpass;
|
return subpass;
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_P(EntityTest, EntityPassRespectsUntrustedSubpassBoundsLimit) {
|
|
||||||
EntityPass pass;
|
|
||||||
|
|
||||||
auto subpass0 = CreatePassWithRectPath(Rect::MakeLTRB(0, 0, 100, 100),
|
|
||||||
Rect::MakeLTRB(50, 50, 150, 150));
|
|
||||||
auto subpass1 = CreatePassWithRectPath(Rect::MakeLTRB(500, 500, 1000, 1000),
|
|
||||||
Rect::MakeLTRB(800, 800, 900, 900));
|
|
||||||
|
|
||||||
auto subpass0_coverage =
|
|
||||||
pass.GetSubpassCoverage(*subpass0.get(), std::nullopt);
|
|
||||||
ASSERT_TRUE(subpass0_coverage.has_value());
|
|
||||||
ASSERT_RECT_NEAR(subpass0_coverage.value(), Rect::MakeLTRB(50, 50, 100, 100));
|
|
||||||
|
|
||||||
auto subpass1_coverage =
|
|
||||||
pass.GetSubpassCoverage(*subpass1.get(), std::nullopt);
|
|
||||||
ASSERT_TRUE(subpass1_coverage.has_value());
|
|
||||||
ASSERT_RECT_NEAR(subpass1_coverage.value(),
|
|
||||||
Rect::MakeLTRB(800, 800, 900, 900));
|
|
||||||
|
|
||||||
pass.AddSubpass(std::move(subpass0));
|
|
||||||
pass.AddSubpass(std::move(subpass1));
|
|
||||||
|
|
||||||
auto coverage = pass.GetElementsCoverage(std::nullopt);
|
|
||||||
ASSERT_TRUE(coverage.has_value());
|
|
||||||
ASSERT_RECT_NEAR(coverage.value(), Rect::MakeLTRB(50, 50, 900, 900));
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_P(EntityTest, EntityPassTrustsSnugSubpassBoundsLimit) {
|
|
||||||
EntityPass pass;
|
|
||||||
|
|
||||||
auto subpass0 = //
|
|
||||||
CreatePassWithRectPath(Rect::MakeLTRB(10, 10, 90, 90),
|
|
||||||
Rect::MakeLTRB(5, 5, 95, 95),
|
|
||||||
ContentBoundsPromise::kContainsContents);
|
|
||||||
auto subpass1 = //
|
|
||||||
CreatePassWithRectPath(Rect::MakeLTRB(500, 500, 1000, 1000),
|
|
||||||
Rect::MakeLTRB(495, 495, 1005, 1005),
|
|
||||||
ContentBoundsPromise::kContainsContents);
|
|
||||||
|
|
||||||
auto subpass0_coverage =
|
|
||||||
pass.GetSubpassCoverage(*subpass0.get(), std::nullopt);
|
|
||||||
EXPECT_TRUE(subpass0_coverage.has_value());
|
|
||||||
// Result should be the overridden bounds
|
|
||||||
// (we lied about them being snug, but the property is respected)
|
|
||||||
EXPECT_RECT_NEAR(subpass0_coverage.value(), Rect::MakeLTRB(5, 5, 95, 95));
|
|
||||||
|
|
||||||
auto subpass1_coverage =
|
|
||||||
pass.GetSubpassCoverage(*subpass1.get(), std::nullopt);
|
|
||||||
EXPECT_TRUE(subpass1_coverage.has_value());
|
|
||||||
// Result should be the overridden bounds
|
|
||||||
// (we lied about them being snug, but the property is respected)
|
|
||||||
EXPECT_RECT_NEAR(subpass1_coverage.value(),
|
|
||||||
Rect::MakeLTRB(495, 495, 1005, 1005));
|
|
||||||
|
|
||||||
pass.AddSubpass(std::move(subpass0));
|
|
||||||
pass.AddSubpass(std::move(subpass1));
|
|
||||||
|
|
||||||
auto coverage = pass.GetElementsCoverage(std::nullopt);
|
|
||||||
EXPECT_TRUE(coverage.has_value());
|
|
||||||
// This result should be the union of the overridden bounds
|
|
||||||
EXPECT_RECT_NEAR(coverage.value(), Rect::MakeLTRB(5, 5, 1005, 1005));
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_P(EntityTest, EntityPassCanMergeSubpassIntoParent) {
|
TEST_P(EntityTest, EntityPassCanMergeSubpassIntoParent) {
|
||||||
// Both a red and a blue box should appear if the pass merging has worked
|
// Both a red and a blue box should appear if the pass merging has worked
|
||||||
// correctly.
|
// correctly.
|
||||||
@ -208,36 +145,6 @@ TEST_P(EntityTest, EntityPassCanMergeSubpassIntoParent) {
|
|||||||
ASSERT_TRUE(OpenPlaygroundHere(pass));
|
ASSERT_TRUE(OpenPlaygroundHere(pass));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_P(EntityTest, EntityPassCoverageRespectsCoverageLimit) {
|
|
||||||
// Rect is drawn entirely in negative area.
|
|
||||||
auto pass = CreatePassWithRectPath(Rect::MakeLTRB(-200, -200, -100, -100),
|
|
||||||
std::nullopt);
|
|
||||||
|
|
||||||
// Without coverage limit.
|
|
||||||
{
|
|
||||||
auto pass_coverage = pass->GetElementsCoverage(std::nullopt);
|
|
||||||
ASSERT_TRUE(pass_coverage.has_value());
|
|
||||||
ASSERT_RECT_NEAR(pass_coverage.value(),
|
|
||||||
Rect::MakeLTRB(-200, -200, -100, -100));
|
|
||||||
}
|
|
||||||
|
|
||||||
// With limit that doesn't overlap.
|
|
||||||
{
|
|
||||||
auto pass_coverage =
|
|
||||||
pass->GetElementsCoverage(Rect::MakeLTRB(0, 0, 100, 100));
|
|
||||||
ASSERT_FALSE(pass_coverage.has_value());
|
|
||||||
}
|
|
||||||
|
|
||||||
// With limit that partially overlaps.
|
|
||||||
{
|
|
||||||
auto pass_coverage =
|
|
||||||
pass->GetElementsCoverage(Rect::MakeLTRB(-150, -150, 0, 0));
|
|
||||||
ASSERT_TRUE(pass_coverage.has_value());
|
|
||||||
ASSERT_RECT_NEAR(pass_coverage.value(),
|
|
||||||
Rect::MakeLTRB(-150, -150, -100, -100));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_P(EntityTest, FilterCoverageRespectsCropRect) {
|
TEST_P(EntityTest, FilterCoverageRespectsCropRect) {
|
||||||
auto image = CreateTextureForFixture("boston.jpg");
|
auto image = CreateTextureForFixture("boston.jpg");
|
||||||
auto filter = ColorFilterContents::MakeBlend(BlendMode::kSoftLight,
|
auto filter = ColorFilterContents::MakeBlend(BlendMode::kSoftLight,
|
||||||
|
90
engine/src/flutter/impeller/entity/save_layer_utils.cc
Normal file
90
engine/src/flutter/impeller/entity/save_layer_utils.cc
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
// 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 {
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
return coverage.TransformBounds(effect_transform)
|
||||||
|
.Intersection(source_coverage_limit.value());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
49
engine/src/flutter/impeller/entity/save_layer_utils.h
Normal file
49
engine/src/flutter/impeller/entity/save_layer_utils.h
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
#ifndef FLUTTER_IMPELLER_ENTITY_SAVE_LAYER_UTILS_H_
|
||||||
|
#define FLUTTER_IMPELLER_ENTITY_SAVE_LAYER_UTILS_H_
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
|
#include "impeller/entity/contents/filters/filter_contents.h"
|
||||||
|
#include "impeller/geometry/rect.h"
|
||||||
|
|
||||||
|
namespace impeller {
|
||||||
|
|
||||||
|
/// @brief Compute the coverage of a subpass in the global coordinate space.
|
||||||
|
///
|
||||||
|
/// @param content_coverage the computed coverage of the contents of the save
|
||||||
|
/// layer. This value may be empty if the save layer has
|
||||||
|
/// no contents, or Rect::Maximum if the contents are
|
||||||
|
/// unbounded (like a destructive blend).
|
||||||
|
///
|
||||||
|
/// @param effect_transform The CTM of the subpass.
|
||||||
|
/// @param coverage_limit The current clip coverage. This is used to bound the
|
||||||
|
/// subpass size.
|
||||||
|
/// @param image_filter A subpass image filter, or nullptr.
|
||||||
|
/// @param flood_output_coverage Whether the coverage should be flooded to clip
|
||||||
|
/// coverage regardless of input coverage. This should be set to true when the
|
||||||
|
/// restore Paint has a destructive blend mode.
|
||||||
|
/// @param flood_input_coverage Whther the content coverage should be flooded.
|
||||||
|
/// This should be set to true if the paint has a backdrop filter or if there is
|
||||||
|
/// a transparent black effecting color filter.
|
||||||
|
///
|
||||||
|
/// The coverage computation expects `content_coverage` to be in the child
|
||||||
|
/// coordinate space. `effect_transform` is used to transform this back into the
|
||||||
|
/// global coordinate space. A return value of std::nullopt indicates that the
|
||||||
|
/// coverage is empty or otherwise does not intersect with the parent coverage
|
||||||
|
/// limit and should be discarded.
|
||||||
|
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 = false,
|
||||||
|
bool flood_input_coverage = false);
|
||||||
|
|
||||||
|
} // namespace impeller
|
||||||
|
|
||||||
|
#endif // FLUTTER_IMPELLER_ENTITY_SAVE_LAYER_UTILS_H_
|
230
engine/src/flutter/impeller/entity/save_layer_utils_unittests.cc
Normal file
230
engine/src/flutter/impeller/entity/save_layer_utils_unittests.cc
Normal file
@ -0,0 +1,230 @@
|
|||||||
|
// 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 "flutter/testing/testing.h"
|
||||||
|
#include "impeller/entity/contents/filters/filter_contents.h"
|
||||||
|
#include "impeller/entity/save_layer_utils.h"
|
||||||
|
|
||||||
|
// TODO(zanderso): https://github.com/flutter/flutter/issues/127701
|
||||||
|
// NOLINTBEGIN(bugprone-unchecked-optional-access)
|
||||||
|
|
||||||
|
namespace impeller {
|
||||||
|
namespace testing {
|
||||||
|
|
||||||
|
using SaveLayerUtilsTest = ::testing::Test;
|
||||||
|
|
||||||
|
TEST(SaveLayerUtilsTest, SimplePaintComputedCoverage) {
|
||||||
|
// Basic Case, simple paint, computed coverage
|
||||||
|
auto coverage = ComputeSaveLayerCoverage(
|
||||||
|
/*content_coverage=*/Rect::MakeLTRB(0, 0, 10, 10), //
|
||||||
|
/*effect_transform=*/{}, //
|
||||||
|
/*coverage_limit=*/Rect::MakeLTRB(0, 0, 2400, 1800), //
|
||||||
|
/*image_filter=*/nullptr //
|
||||||
|
);
|
||||||
|
ASSERT_TRUE(coverage.has_value());
|
||||||
|
EXPECT_EQ(coverage.value(), Rect::MakeLTRB(0, 0, 10, 10));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(SaveLayerUtilsTest, BackdropFiterComputedCoverage) {
|
||||||
|
// Backdrop Filter, computed coverage
|
||||||
|
auto coverage = ComputeSaveLayerCoverage(
|
||||||
|
/*content_coverage=*/Rect::MakeLTRB(0, 0, 10, 10), //
|
||||||
|
/*effect_transform=*/{}, //
|
||||||
|
/*coverage_limit=*/Rect::MakeLTRB(0, 0, 2400, 1800), //
|
||||||
|
/*image_filter=*/nullptr,
|
||||||
|
/*flood_output_coverage=*/false, //
|
||||||
|
/*flood_input_coverage=*/true //
|
||||||
|
);
|
||||||
|
|
||||||
|
ASSERT_TRUE(coverage.has_value());
|
||||||
|
EXPECT_EQ(coverage.value(), Rect::MakeLTRB(0, 0, 2400, 1800));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(SaveLayerUtilsTest, ImageFiterComputedCoverage) {
|
||||||
|
// Image Filter, computed coverage
|
||||||
|
auto image_filter = FilterContents::MakeMatrixFilter(
|
||||||
|
FilterInput::Make(Rect()), Matrix::MakeScale({2, 2, 1}), {});
|
||||||
|
|
||||||
|
auto coverage = ComputeSaveLayerCoverage(
|
||||||
|
/*content_coverage=*/Rect::MakeLTRB(0, 0, 10, 10), //
|
||||||
|
/*effect_transform=*/{}, //
|
||||||
|
/*coverage_limit=*/Rect::MakeLTRB(0, 0, 2400, 1800), //
|
||||||
|
/*image_filter=*/image_filter //
|
||||||
|
);
|
||||||
|
|
||||||
|
ASSERT_TRUE(coverage.has_value());
|
||||||
|
EXPECT_EQ(coverage.value(), Rect::MakeLTRB(0, 0, 10, 10));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(SaveLayerUtilsTest,
|
||||||
|
ImageFiterSmallScaleComputedCoverageLargerThanBoundsLimit) {
|
||||||
|
// Image Filter scaling large, computed coverage is larger than bounds limit.
|
||||||
|
auto image_filter = FilterContents::MakeMatrixFilter(
|
||||||
|
FilterInput::Make(Rect()), Matrix::MakeScale({2, 2, 1}), {});
|
||||||
|
|
||||||
|
auto coverage = ComputeSaveLayerCoverage(
|
||||||
|
/*content_coverage=*/Rect::MakeLTRB(0, 0, 10, 10), //
|
||||||
|
/*effect_transform=*/{}, //
|
||||||
|
/*coverage_limit=*/Rect::MakeLTRB(0, 0, 5, 5), //
|
||||||
|
/*image_filter=*/image_filter //
|
||||||
|
);
|
||||||
|
|
||||||
|
ASSERT_TRUE(coverage.has_value());
|
||||||
|
EXPECT_EQ(coverage.value(), Rect::MakeLTRB(0, 0, 2.5, 2.5));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(SaveLayerUtilsTest,
|
||||||
|
ImageFiterLargeScaleComputedCoverageLargerThanBoundsLimit) {
|
||||||
|
// Image Filter scaling small, computed coverage is larger than bounds limit.
|
||||||
|
auto image_filter = FilterContents::MakeMatrixFilter(
|
||||||
|
FilterInput::Make(Rect()), Matrix::MakeScale({0.5, 0.5, 1}), {});
|
||||||
|
|
||||||
|
auto coverage = ComputeSaveLayerCoverage(
|
||||||
|
/*content_coverage=*/Rect::MakeLTRB(0, 0, 10, 10), //
|
||||||
|
/*effect_transform=*/{}, //
|
||||||
|
/*coverage_limit=*/Rect::MakeLTRB(0, 0, 5, 5), //
|
||||||
|
/*image_filter=*/image_filter //
|
||||||
|
);
|
||||||
|
|
||||||
|
ASSERT_TRUE(coverage.has_value());
|
||||||
|
EXPECT_EQ(coverage.value(), Rect::MakeLTRB(0, 0, 10, 10));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(SaveLayerUtilsTest, DisjointCoverage) {
|
||||||
|
// No intersection in coverage
|
||||||
|
auto coverage = ComputeSaveLayerCoverage(
|
||||||
|
/*content_coverage=*/Rect::MakeLTRB(200, 200, 210, 210), //
|
||||||
|
/*effect_transform=*/{}, //
|
||||||
|
/*coverage_limit=*/Rect::MakeLTRB(0, 0, 100, 100), //
|
||||||
|
/*image_filter=*/nullptr //
|
||||||
|
);
|
||||||
|
|
||||||
|
EXPECT_FALSE(coverage.has_value());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(SaveLayerUtilsTest, DisjointCoverageTransformedByImageFilter) {
|
||||||
|
// Coverage disjoint from parent coverage but transformed into parent space
|
||||||
|
// with image filter.
|
||||||
|
auto image_filter = FilterContents::MakeMatrixFilter(
|
||||||
|
FilterInput::Make(Rect()), Matrix::MakeTranslation({-200, -200, 0}), {});
|
||||||
|
|
||||||
|
auto coverage = ComputeSaveLayerCoverage(
|
||||||
|
/*content_coverage=*/Rect::MakeLTRB(200, 200, 210, 210), //
|
||||||
|
/*effect_transform=*/{}, //
|
||||||
|
/*coverage_limit=*/Rect::MakeLTRB(0, 0, 100, 100), //
|
||||||
|
/*image_filter=*/image_filter //
|
||||||
|
);
|
||||||
|
|
||||||
|
ASSERT_TRUE(coverage.has_value());
|
||||||
|
EXPECT_EQ(coverage.value(), Rect::MakeLTRB(200, 200, 210, 210));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(SaveLayerUtilsTest, DisjointCoveragTransformedByCTM) {
|
||||||
|
// Coverage disjoint from parent coverage.
|
||||||
|
Matrix ctm = Matrix::MakeTranslation({-200, -200, 0});
|
||||||
|
auto coverage = ComputeSaveLayerCoverage(
|
||||||
|
/*content_coverage=*/Rect::MakeLTRB(200, 200, 210, 210), //
|
||||||
|
/*effect_transform=*/ctm, //
|
||||||
|
/*coverage_limit=*/Rect::MakeLTRB(0, 0, 100, 100), //
|
||||||
|
/*image_filter=*/nullptr //
|
||||||
|
);
|
||||||
|
|
||||||
|
ASSERT_TRUE(coverage.has_value());
|
||||||
|
EXPECT_EQ(coverage.value(), Rect::MakeLTRB(0, 0, 10, 10));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(SaveLayerUtilsTest, BasicEmptyCoverage) {
|
||||||
|
auto coverage = ComputeSaveLayerCoverage(
|
||||||
|
/*content_coverage=*/Rect::MakeLTRB(0, 0, 0, 0), //
|
||||||
|
/*effect_transform=*/{}, //
|
||||||
|
/*coverage_limit=*/Rect::MakeLTRB(0, 0, 2400, 1800), //
|
||||||
|
/*image_filter=*/nullptr //
|
||||||
|
);
|
||||||
|
|
||||||
|
ASSERT_FALSE(coverage.has_value());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(SaveLayerUtilsTest, ImageFilterEmptyCoverage) {
|
||||||
|
// Empty coverage with Image Filter
|
||||||
|
auto image_filter = FilterContents::MakeMatrixFilter(
|
||||||
|
FilterInput::Make(Rect()), Matrix::MakeTranslation({-200, -200, 0}), {});
|
||||||
|
|
||||||
|
auto coverage = ComputeSaveLayerCoverage(
|
||||||
|
/*content_coverage=*/Rect::MakeLTRB(0, 0, 0, 0), //
|
||||||
|
/*effect_transform=*/{}, //
|
||||||
|
/*coverage_limit=*/Rect::MakeLTRB(0, 0, 2400, 1800), //
|
||||||
|
/*image_filter=*/image_filter //
|
||||||
|
);
|
||||||
|
|
||||||
|
ASSERT_FALSE(coverage.has_value());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(SaveLayerUtilsTest, BackdropFilterEmptyCoverage) {
|
||||||
|
// Empty coverage with backdrop filter.
|
||||||
|
auto coverage = ComputeSaveLayerCoverage(
|
||||||
|
/*content_coverage=*/Rect::MakeLTRB(0, 0, 0, 0), //
|
||||||
|
/*effect_transform=*/{}, //
|
||||||
|
/*coverage_limit=*/Rect::MakeLTRB(0, 0, 2400, 1800), //
|
||||||
|
/*image_filter=*/nullptr, //
|
||||||
|
/*flood_output_coverage=*/true //
|
||||||
|
);
|
||||||
|
|
||||||
|
ASSERT_TRUE(coverage.has_value());
|
||||||
|
EXPECT_EQ(coverage.value(), Rect::MakeLTRB(0, 0, 2400, 1800));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(SaveLayerUtilsTest, FloodInputCoverage) {
|
||||||
|
auto coverage = ComputeSaveLayerCoverage(
|
||||||
|
/*content_coverage=*/Rect::MakeLTRB(0, 0, 0, 0), //
|
||||||
|
/*effect_transform=*/{}, //
|
||||||
|
/*coverage_limit=*/Rect::MakeLTRB(0, 0, 2400, 1800), //
|
||||||
|
/*image_filter=*/nullptr, //
|
||||||
|
/*flood_output_coverage=*/false, //
|
||||||
|
/*flood_input_coverage=*/true //
|
||||||
|
);
|
||||||
|
|
||||||
|
ASSERT_TRUE(coverage.has_value());
|
||||||
|
EXPECT_EQ(coverage.value(), Rect::MakeLTRB(0, 0, 2400, 1800));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(SaveLayerUtilsTest, FloodInputCoverageWithImageFilter) {
|
||||||
|
auto image_filter = FilterContents::MakeMatrixFilter(
|
||||||
|
FilterInput::Make(Rect()), Matrix::MakeScale({0.5, 0.5, 1}), {});
|
||||||
|
|
||||||
|
auto coverage = ComputeSaveLayerCoverage(
|
||||||
|
/*content_coverage=*/Rect::MakeLTRB(0, 0, 0, 0), //
|
||||||
|
/*effect_transform=*/{}, //
|
||||||
|
/*coverage_limit=*/Rect::MakeLTRB(0, 0, 2400, 1800), //
|
||||||
|
/*image_filter=*/image_filter, //
|
||||||
|
/*flood_output_coverage=*/false, //
|
||||||
|
/*flood_input_coverage=*/true //
|
||||||
|
);
|
||||||
|
|
||||||
|
ASSERT_TRUE(coverage.has_value());
|
||||||
|
EXPECT_EQ(coverage.value(), Rect::MakeLTRB(0, 0, 4800, 3600));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(SaveLayerUtilsTest,
|
||||||
|
FloodInputCoverageWithImageFilterWithNoCoverageProducesNoCoverage) {
|
||||||
|
// Even if we flood the input coverage due to a bdf, we can still cull out the
|
||||||
|
// layer if the image filter results in no coverage.
|
||||||
|
auto image_filter = FilterContents::MakeMatrixFilter(
|
||||||
|
FilterInput::Make(Rect()), Matrix::MakeScale({1, 1, 0}), {});
|
||||||
|
|
||||||
|
auto coverage = ComputeSaveLayerCoverage(
|
||||||
|
/*content_coverage=*/Rect::MakeLTRB(0, 0, 0, 0), //
|
||||||
|
/*effect_transform=*/{}, //
|
||||||
|
/*coverage_limit=*/Rect::MakeLTRB(0, 0, 2400, 1800), //
|
||||||
|
/*image_filter=*/image_filter, //
|
||||||
|
/*flood_output_coverage=*/false, //
|
||||||
|
/*flood_input_coverage=*/true //
|
||||||
|
);
|
||||||
|
|
||||||
|
ASSERT_FALSE(coverage.has_value());
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace testing
|
||||||
|
} // namespace impeller
|
||||||
|
|
||||||
|
// NOLINTEND(bugprone-unchecked-optional-access)
|
Loading…
x
Reference in New Issue
Block a user