diff --git a/impeller/aiks/aiks_unittests.cc b/impeller/aiks/aiks_unittests.cc index 3438056a0b6a9..74807ad2a593a 100644 --- a/impeller/aiks/aiks_unittests.cc +++ b/impeller/aiks/aiks_unittests.cc @@ -3173,7 +3173,7 @@ TEST_P(AiksTest, MatrixSaveLayerFilter) { canvas.SaveLayer({.image_filter = ImageFilter::MakeMatrix( Matrix::MakeTranslation(Vector2(1, 1) * (200 + 100 * k1OverSqrt2)) * - Matrix::MakeScale(Vector2(1, 1) * 0.2) * + Matrix::MakeScale(Vector2(1, 1) * 0.5) * Matrix::MakeTranslation(Vector2(-200, -200)), SamplerDescriptor{})}, std::nullopt); @@ -3201,7 +3201,7 @@ TEST_P(AiksTest, MatrixBackdropFilter) { {}, std::nullopt, ImageFilter::MakeMatrix( Matrix::MakeTranslation(Vector2(1, 1) * (100 + 100 * k1OverSqrt2)) * - Matrix::MakeScale(Vector2(1, 1) * 0.2) * + Matrix::MakeScale(Vector2(1, 1) * 0.5) * Matrix::MakeTranslation(Vector2(-100, -100)), SamplerDescriptor{})); canvas.Restore(); diff --git a/impeller/aiks/paint.cc b/impeller/aiks/paint.cc index a7bb6d00d3152..5c2b808c69eb3 100644 --- a/impeller/aiks/paint.cc +++ b/impeller/aiks/paint.cc @@ -60,14 +60,21 @@ std::shared_ptr Paint::WithFilters( std::shared_ptr input) const { input = WithColorFilter(input, /*absorb_opacity=*/true); input = WithInvertFilter(input); - input = WithImageFilter(input, Matrix(), /*is_subpass=*/false); + auto image_filter = WithImageFilter(input, Matrix(), /*is_subpass=*/false); + if (image_filter) { + input = image_filter; + } return input; } std::shared_ptr Paint::WithFiltersForSubpassTarget( std::shared_ptr input, const Matrix& effect_transform) const { - input = WithImageFilter(input, effect_transform, /*is_subpass=*/true); + auto image_filter = + WithImageFilter(input, effect_transform, /*is_subpass=*/true); + if (image_filter) { + input = image_filter; + } input = WithColorFilter(input, /*absorb_opacity=*/true); return input; } @@ -81,12 +88,12 @@ std::shared_ptr Paint::WithMaskBlur(std::shared_ptr input, return input; } -std::shared_ptr Paint::WithImageFilter( - std::shared_ptr input, +std::shared_ptr Paint::WithImageFilter( + const FilterInput::Variant& input, const Matrix& effect_transform, bool is_subpass) const { if (!image_filter) { - return input; + return nullptr; } auto filter = image_filter->WrapInput(FilterInput::Make(input)); filter->SetIsForSubpass(is_subpass); diff --git a/impeller/aiks/paint.h b/impeller/aiks/paint.h index d77a298d5bd21..384b4bdb1f150 100644 --- a/impeller/aiks/paint.h +++ b/impeller/aiks/paint.h @@ -98,11 +98,12 @@ struct Paint { std::shared_ptr WithMaskBlur(std::shared_ptr input, bool is_solid_color) const; - private: - std::shared_ptr WithImageFilter(std::shared_ptr input, - const Matrix& effect_transform, - bool is_subpass) const; + std::shared_ptr WithImageFilter( + const FilterInput::Variant& input, + const Matrix& effect_transform, + bool is_subpass) const; + private: std::shared_ptr WithColorFilter(std::shared_ptr input, bool absorb_opacity = false) const; diff --git a/impeller/aiks/paint_pass_delegate.cc b/impeller/aiks/paint_pass_delegate.cc index 382ca3457cd93..6a72338028d5a 100644 --- a/impeller/aiks/paint_pass_delegate.cc +++ b/impeller/aiks/paint_pass_delegate.cc @@ -47,6 +47,13 @@ std::shared_ptr PaintPassDelegate::CreateContentsForSubpassTarget( effect_transform); } +// |EntityPassDelgate| +std::shared_ptr PaintPassDelegate::WithImageFilter( + const FilterInput::Variant& input, + const Matrix& effect_transform) const { + return paint_.WithImageFilter(input, effect_transform, true); +} + /// OpacityPeepholePassDelegate /// ---------------------------------------------- @@ -140,4 +147,11 @@ OpacityPeepholePassDelegate::CreateContentsForSubpassTarget( effect_transform); } +// |EntityPassDelgate| +std::shared_ptr OpacityPeepholePassDelegate::WithImageFilter( + const FilterInput::Variant& input, + const Matrix& effect_transform) const { + return paint_.WithImageFilter(input, effect_transform, true); +} + } // namespace impeller diff --git a/impeller/aiks/paint_pass_delegate.h b/impeller/aiks/paint_pass_delegate.h index 967b59ea81e63..17e87b20bd366 100644 --- a/impeller/aiks/paint_pass_delegate.h +++ b/impeller/aiks/paint_pass_delegate.h @@ -32,6 +32,11 @@ class PaintPassDelegate final : public EntityPassDelegate { std::shared_ptr target, const Matrix& effect_transform) override; + // |EntityPassDelgate| + std::shared_ptr WithImageFilter( + const FilterInput::Variant& input, + const Matrix& effect_transform) const override; + private: const Paint paint_; @@ -61,6 +66,11 @@ class OpacityPeepholePassDelegate final : public EntityPassDelegate { std::shared_ptr target, const Matrix& effect_transform) override; + // |EntityPassDelgate| + std::shared_ptr WithImageFilter( + const FilterInput::Variant& input, + const Matrix& effect_transform) const override; + private: const Paint paint_; diff --git a/impeller/entity/contents/contents.cc b/impeller/entity/contents/contents.cc index 85e70e534e061..a181e22302b21 100644 --- a/impeller/entity/contents/contents.cc +++ b/impeller/entity/contents/contents.cc @@ -123,6 +123,10 @@ std::optional Contents::AsBackgroundColor(const Entity& entity, return {}; } +const FilterContents* Contents::AsFilter() const { + return nullptr; +} + bool Contents::ApplyColorFilter( const Contents::ColorFilterProc& color_filter_proc) { return false; diff --git a/impeller/entity/contents/contents.h b/impeller/entity/contents/contents.h index 245b8f2d6d514..b9dec5db2d12e 100644 --- a/impeller/entity/contents/contents.h +++ b/impeller/entity/contents/contents.h @@ -23,6 +23,7 @@ struct ContentContextOptions; class Entity; class Surface; class RenderPass; +class FilterContents; ContentContextOptions OptionsFromPass(const RenderPass& pass); @@ -155,6 +156,12 @@ class Contents { virtual std::optional AsBackgroundColor(const Entity& entity, ISize target_size) const; + //---------------------------------------------------------------------------- + /// @brief Cast to a filter. Returns `nullptr` if this Contents is not a + /// filter. + /// + virtual const FilterContents* AsFilter() const; + //---------------------------------------------------------------------------- /// @brief If possible, applies a color filter to this contents inputs on /// the CPU. diff --git a/impeller/entity/contents/filters/filter_contents.cc b/impeller/entity/contents/filters/filter_contents.cc index ae48cec892ddc..3d4fa96a6321b 100644 --- a/impeller/entity/contents/filters/filter_contents.cc +++ b/impeller/entity/contents/filters/filter_contents.cc @@ -259,6 +259,10 @@ std::optional FilterContents::RenderToSnapshot( return std::nullopt; } +const FilterContents* FilterContents::AsFilter() const { + return this; +} + Matrix FilterContents::GetLocalTransform(const Matrix& parent_transform) const { return Matrix(); } @@ -266,6 +270,14 @@ Matrix FilterContents::GetLocalTransform(const Matrix& parent_transform) const { Matrix FilterContents::GetTransform(const Matrix& parent_transform) const { return parent_transform * GetLocalTransform(parent_transform); } +bool FilterContents::IsTranslationOnly() const { + for (auto& input : inputs_) { + if (!input->IsTranslationOnly()) { + return false; + } + } + return true; +} bool FilterContents::IsLeaf() const { for (auto& input : inputs_) { diff --git a/impeller/entity/contents/filters/filter_contents.h b/impeller/entity/contents/filters/filter_contents.h index e5e7604434821..c14393444980c 100644 --- a/impeller/entity/contents/filters/filter_contents.h +++ b/impeller/entity/contents/filters/filter_contents.h @@ -125,10 +125,23 @@ class FilterContents : public Contents { bool msaa_enabled = true, const std::string& label = "Filter Snapshot") const override; + // |Contents| + const FilterContents* AsFilter() const override; + virtual Matrix GetLocalTransform(const Matrix& parent_transform) const; Matrix GetTransform(const Matrix& parent_transform) const; + /// @brief Returns true if this filter graph doesn't perform any basis + /// transformations to the filtered content. For example: Rotating, + /// scaling, and skewing are all basis transformations, but + /// translating is not. + /// + /// This is useful for determining whether a filtered object's space + /// is compatible enough with the parent pass space to perform certain + /// subpass clipping optimizations. + virtual bool IsTranslationOnly() const; + /// @brief Returns `true` if this filter does not have any `FilterInput` /// children. bool IsLeaf() const; diff --git a/impeller/entity/contents/filters/inputs/filter_contents_filter_input.cc b/impeller/entity/contents/filters/inputs/filter_contents_filter_input.cc index e1af31f8bede4..78a1cec98e3b2 100644 --- a/impeller/entity/contents/filters/inputs/filter_contents_filter_input.cc +++ b/impeller/entity/contents/filters/inputs/filter_contents_filter_input.cc @@ -58,6 +58,10 @@ void FilterContentsFilterInput::PopulateGlyphAtlas( filter_->PopulateGlyphAtlas(lazy_glyph_atlas, scale); } +bool FilterContentsFilterInput::IsTranslationOnly() const { + return filter_->IsTranslationOnly(); +} + bool FilterContentsFilterInput::IsLeaf() const { return false; } diff --git a/impeller/entity/contents/filters/inputs/filter_contents_filter_input.h b/impeller/entity/contents/filters/inputs/filter_contents_filter_input.h index 7aaaa3aebdd7a..45d44993520cf 100644 --- a/impeller/entity/contents/filters/inputs/filter_contents_filter_input.h +++ b/impeller/entity/contents/filters/inputs/filter_contents_filter_input.h @@ -36,6 +36,9 @@ class FilterContentsFilterInput final : public FilterInput { const std::shared_ptr& lazy_glyph_atlas, Scalar scale) override; + // |FilterInput| + bool IsTranslationOnly() const override; + // |FilterInput| bool IsLeaf() const override; diff --git a/impeller/entity/contents/filters/inputs/filter_input.cc b/impeller/entity/contents/filters/inputs/filter_input.cc index 1cb4744468c20..e438212688a44 100644 --- a/impeller/entity/contents/filters/inputs/filter_input.cc +++ b/impeller/entity/contents/filters/inputs/filter_input.cc @@ -76,6 +76,10 @@ void FilterInput::PopulateGlyphAtlas( FilterInput::~FilterInput() = default; +bool FilterInput::IsTranslationOnly() const { + return true; +} + bool FilterInput::IsLeaf() const { return true; } diff --git a/impeller/entity/contents/filters/inputs/filter_input.h b/impeller/entity/contents/filters/inputs/filter_input.h index 598ca1b560cca..43db41eb6a8ef 100644 --- a/impeller/entity/contents/filters/inputs/filter_input.h +++ b/impeller/entity/contents/filters/inputs/filter_input.h @@ -69,6 +69,9 @@ class FilterInput { const std::shared_ptr& lazy_glyph_atlas, Scalar scale); + /// @see `FilterContents::HasBasisTransformations` + virtual bool IsTranslationOnly() const; + /// @brief Returns `true` unless this input is a `FilterInput`, which may /// take other inputs. virtual bool IsLeaf() const; diff --git a/impeller/entity/contents/filters/matrix_filter_contents.cc b/impeller/entity/contents/filters/matrix_filter_contents.cc index 5483b4d7d306b..c767ffe6cd19f 100644 --- a/impeller/entity/contents/filters/matrix_filter_contents.cc +++ b/impeller/entity/contents/filters/matrix_filter_contents.cc @@ -19,6 +19,10 @@ void MatrixFilterContents::SetIsForSubpass(bool is_subpass) { FilterContents::SetIsForSubpass(is_subpass); } +bool MatrixFilterContents::IsTranslationOnly() const { + return matrix_.Basis().IsIdentity() && FilterContents::IsTranslationOnly(); +} + void MatrixFilterContents::SetSamplerDescriptor(SamplerDescriptor desc) { sampler_descriptor_ = std::move(desc); } diff --git a/impeller/entity/contents/filters/matrix_filter_contents.h b/impeller/entity/contents/filters/matrix_filter_contents.h index a2e4c5f239953..ea221301d8e33 100644 --- a/impeller/entity/contents/filters/matrix_filter_contents.h +++ b/impeller/entity/contents/filters/matrix_filter_contents.h @@ -20,6 +20,9 @@ class MatrixFilterContents final : public FilterContents { // |FilterContents| void SetIsForSubpass(bool is_for_subpass) override; + // |FilterContents| + bool IsTranslationOnly() const override; + void SetSamplerDescriptor(SamplerDescriptor desc); // |FilterContents| diff --git a/impeller/entity/entity_pass.cc b/impeller/entity/entity_pass.cc index 29a737e8f57e5..b93d997d468d2 100644 --- a/impeller/entity/entity_pass.cc +++ b/impeller/entity/entity_pass.cc @@ -110,12 +110,39 @@ std::optional EntityPass::GetElementsCoverage( if (auto entity = std::get_if(&element)) { coverage = entity->GetCoverage(); + // When the coverage limit is std::nullopt, that means there is no limit, + // as opposed to empty coverage. if (coverage.has_value() && coverage_limit.has_value()) { - coverage = coverage->Intersection(coverage_limit.value()); + const auto* filter = entity->GetContents()->AsFilter(); + if (!filter || filter->IsTranslationOnly()) { + coverage = coverage->Intersection(coverage_limit.value()); + } } - } else if (auto subpass = + } else if (auto subpass_ptr = std::get_if>(&element)) { - coverage = GetSubpassCoverage(*subpass->get(), coverage_limit); + auto& subpass = *subpass_ptr->get(); + + std::optional unfiltered_coverage = + GetSubpassCoverage(subpass, std::nullopt); + if (!unfiltered_coverage.has_value()) { + continue; + } + + std::shared_ptr image_filter = + subpass.delegate_->WithImageFilter(*unfiltered_coverage, + subpass.xformation_); + if (image_filter) { + Entity subpass_entity; + subpass_entity.SetTransformation(subpass.xformation_); + coverage = image_filter->GetCoverage(subpass_entity); + } else { + coverage = unfiltered_coverage; + } + + if (coverage.has_value() && coverage_limit.has_value() && + (!image_filter || image_filter->IsTranslationOnly())) { + coverage = coverage->Intersection(coverage_limit.value()); + } } else { FML_UNREACHABLE(); } @@ -135,6 +162,16 @@ std::optional EntityPass::GetElementsCoverage( std::optional EntityPass::GetSubpassCoverage( const EntityPass& subpass, std::optional coverage_limit) const { + std::shared_ptr image_filter = + subpass.delegate_->WithImageFilter(Rect(), subpass.xformation_); + + // If the filter graph transforms the basis of the subpass, then its space + // has deviated too much from the parent pass to safely intersect with the + // pass coverage limit. + coverage_limit = + (image_filter && image_filter->IsTranslationOnly() ? std::nullopt + : coverage_limit); + auto entities_coverage = subpass.GetElementsCoverage(coverage_limit); // The entities don't cover anything. There is nothing to do. if (!entities_coverage.has_value()) { diff --git a/impeller/entity/entity_pass.h b/impeller/entity/entity_pass.h index f9b11d155f50f..f00c9d3653978 100644 --- a/impeller/entity/entity_pass.h +++ b/impeller/entity/entity_pass.h @@ -141,6 +141,9 @@ class EntityPass { void SetEnableOffscreenCheckerboard(bool enabled); + //---------------------------------------------------------------------------- + /// @brief Get the coverage of an unfiltered subpass. + /// std::optional GetSubpassCoverage( const EntityPass& subpass, std::optional coverage_limit) const; diff --git a/impeller/entity/entity_pass_delegate.cc b/impeller/entity/entity_pass_delegate.cc index b996995b57de6..e6a1021956176 100644 --- a/impeller/entity/entity_pass_delegate.cc +++ b/impeller/entity/entity_pass_delegate.cc @@ -34,6 +34,13 @@ class DefaultEntityPassDelegate final : public EntityPassDelegate { FML_UNREACHABLE(); } + // |EntityPassDelgate| + std::shared_ptr WithImageFilter( + const FilterInput::Variant& input, + const Matrix& effect_transform) const override { + return nullptr; + } + private: FML_DISALLOW_COPY_AND_ASSIGN(DefaultEntityPassDelegate); }; diff --git a/impeller/entity/entity_pass_delegate.h b/impeller/entity/entity_pass_delegate.h index fbc7f97be2bbb..4675236b35a79 100644 --- a/impeller/entity/entity_pass_delegate.h +++ b/impeller/entity/entity_pass_delegate.h @@ -9,6 +9,8 @@ #include "flutter/fml/macros.h" #include "impeller/core/texture.h" #include "impeller/entity/contents/contents.h" +#include "impeller/entity/contents/filters/filter_contents.h" +#include "impeller/entity/contents/filters/inputs/filter_input.h" namespace impeller { @@ -32,6 +34,10 @@ class EntityPassDelegate { std::shared_ptr target, const Matrix& effect_transform) = 0; + virtual std::shared_ptr WithImageFilter( + const FilterInput::Variant& input, + const Matrix& effect_transform) const = 0; + private: FML_DISALLOW_COPY_AND_ASSIGN(EntityPassDelegate); }; diff --git a/impeller/entity/entity_unittests.cc b/impeller/entity/entity_unittests.cc index b5d36ca2abad8..3ff5548509b5e 100644 --- a/impeller/entity/entity_unittests.cc +++ b/impeller/entity/entity_unittests.cc @@ -91,6 +91,13 @@ class TestPassDelegate final : public EntityPassDelegate { return nullptr; } + // |EntityPassDelegate| + std::shared_ptr WithImageFilter( + const FilterInput::Variant& input, + const Matrix& effect_transform) const override { + return nullptr; + } + private: const std::optional coverage_; const bool collapse_;