diff --git a/engine/src/flutter/impeller/entity/contents/contents.h b/engine/src/flutter/impeller/entity/contents/contents.h index a72ae642a58..9cf59ca3770 100644 --- a/engine/src/flutter/impeller/entity/contents/contents.h +++ b/engine/src/flutter/impeller/entity/contents/contents.h @@ -40,7 +40,7 @@ class Contents { virtual std::optional GetCoverage(const Entity& entity) const; /// @brief Render this contents to a snapshot, respecting the entity's - /// transform, path, stencil depth, blend mode, etc. + /// transform, path, stencil depth, and blend mode. /// The result texture size is always the size of /// `GetCoverage(entity)`. virtual std::optional RenderToSnapshot( diff --git a/engine/src/flutter/impeller/entity/contents/filters/border_mask_blur_filter_contents.cc b/engine/src/flutter/impeller/entity/contents/filters/border_mask_blur_filter_contents.cc index db1d5272acc..328f7d78e3c 100644 --- a/engine/src/flutter/impeller/entity/contents/filters/border_mask_blur_filter_contents.cc +++ b/engine/src/flutter/impeller/entity/contents/filters/border_mask_blur_filter_contents.cc @@ -108,18 +108,21 @@ bool BorderMaskBlurFilterContents::RenderFilter( return pass.AddCommand(std::move(cmd)); } -std::optional BorderMaskBlurFilterContents::GetCoverage( +std::optional BorderMaskBlurFilterContents::GetFilterCoverage( + const FilterInput::Vector& inputs, const Entity& entity) const { - auto coverage = FilterContents::GetCoverage(entity); + if (inputs.empty()) { + return std::nullopt; + } + + auto coverage = inputs[0]->GetCoverage(entity); if (!coverage.has_value()) { return std::nullopt; } - // Technically this works with all of our current filters, but this should be - // using the input[0] transform, not the entity transform! - // See: https://github.com/flutter/impeller/pull/130#issuecomment-1098892423 auto transformed_blur_vector = - entity.GetTransformation() + inputs[0] + ->GetTransform(entity) .TransformDirection( Vector2(Radius{sigma_x_}.radius, Radius{sigma_y_}.radius)) .Abs(); diff --git a/engine/src/flutter/impeller/entity/contents/filters/border_mask_blur_filter_contents.h b/engine/src/flutter/impeller/entity/contents/filters/border_mask_blur_filter_contents.h index dc327dc3f5e..335edd797e1 100644 --- a/engine/src/flutter/impeller/entity/contents/filters/border_mask_blur_filter_contents.h +++ b/engine/src/flutter/impeller/entity/contents/filters/border_mask_blur_filter_contents.h @@ -21,8 +21,9 @@ class BorderMaskBlurFilterContents final : public FilterContents { void SetBlurStyle(BlurStyle blur_style); - // |Contents| - std::optional GetCoverage(const Entity& entity) const override; + // |FilterContents| + std::optional GetFilterCoverage(const FilterInput::Vector& inputs, + const Entity& entity) const override; private: // |FilterContents| @@ -31,6 +32,7 @@ class BorderMaskBlurFilterContents final : public FilterContents { const Entity& entity, RenderPass& pass, const Rect& coverage) const override; + Sigma sigma_x_; Sigma sigma_y_; BlurStyle blur_style_ = BlurStyle::kNormal; diff --git a/engine/src/flutter/impeller/entity/contents/filters/filter_contents.cc b/engine/src/flutter/impeller/entity/contents/filters/filter_contents.cc index 790faf4a8bb..4a63da973c4 100644 --- a/engine/src/flutter/impeller/entity/contents/filters/filter_contents.cc +++ b/engine/src/flutter/impeller/entity/contents/filters/filter_contents.cc @@ -52,7 +52,8 @@ std::shared_ptr FilterContents::MakeBlend( new_blend->SetInputs({blend_input, *in_i}); new_blend->SetBlendMode(blend_mode); if (in_i < inputs.end() - 1) { - blend_input = FilterInput::Make(new_blend); + blend_input = FilterInput::Make( + std::static_pointer_cast(new_blend)); } } // new_blend will always be assigned because inputs.size() >= 2. @@ -139,6 +140,15 @@ bool FilterContents::Render(const ContentContext& renderer, } std::optional FilterContents::GetCoverage(const Entity& entity) const { + Entity entity_with_local_transform = entity; + entity_with_local_transform.SetTransformation( + GetTransform(entity.GetTransformation())); + return GetFilterCoverage(inputs_, entity_with_local_transform); +} + +std::optional FilterContents::GetFilterCoverage( + const FilterInput::Vector& inputs, + const Entity& entity) const { // The default coverage of FilterContents is just the union of its inputs' // coverage. FilterContents implementations may choose to adjust this // coverage depending on the use case. @@ -148,7 +158,7 @@ std::optional FilterContents::GetCoverage(const Entity& entity) const { } std::optional result; - for (const auto& input : inputs_) { + for (const auto& input : inputs) { auto coverage = input->GetCoverage(entity); if (!coverage.has_value()) { continue; @@ -157,7 +167,7 @@ std::optional FilterContents::GetCoverage(const Entity& entity) const { result = coverage; continue; } - result = result->Union(result.value()); + result = result->Union(coverage.value()); } return result; } @@ -165,16 +175,21 @@ std::optional FilterContents::GetCoverage(const Entity& entity) const { std::optional FilterContents::RenderToSnapshot( const ContentContext& renderer, const Entity& entity) const { - auto bounds = GetCoverage(entity); - if (!bounds.has_value() || bounds->IsEmpty()) { + Entity entity_with_local_transform = entity; + entity_with_local_transform.SetTransformation( + GetTransform(entity.GetTransformation())); + + auto coverage = GetFilterCoverage(inputs_, entity_with_local_transform); + if (!coverage.has_value() || coverage->IsEmpty()) { return std::nullopt; } // Render the filter into a new texture. auto texture = renderer.MakeSubpass( - ISize(bounds->size), + ISize(coverage->size), [=](const ContentContext& renderer, RenderPass& pass) -> bool { - return RenderFilter(inputs_, renderer, entity, pass, bounds.value()); + return RenderFilter(inputs_, renderer, entity_with_local_transform, + pass, coverage.value()); }); if (!texture) { @@ -182,7 +197,15 @@ std::optional FilterContents::RenderToSnapshot( } return Snapshot{.texture = texture, - .transform = Matrix::MakeTranslation(bounds->origin)}; + .transform = Matrix::MakeTranslation(coverage->origin)}; +} + +Matrix FilterContents::GetLocalTransform() const { + return Matrix(); +} + +Matrix FilterContents::GetTransform(const Matrix& parent_transform) const { + return parent_transform * GetLocalTransform(); } } // namespace impeller diff --git a/engine/src/flutter/impeller/entity/contents/filters/filter_contents.h b/engine/src/flutter/impeller/entity/contents/filters/filter_contents.h index 0917d5532b5..6f6ab02c234 100644 --- a/engine/src/flutter/impeller/entity/contents/filters/filter_contents.h +++ b/engine/src/flutter/impeller/entity/contents/filters/filter_contents.h @@ -122,23 +122,31 @@ class FilterContents : public Contents { std::optional GetCoverage(const Entity& entity) const override; // |Contents| - virtual std::optional RenderToSnapshot( - const ContentContext& renderer, - const Entity& entity) const override; + std::optional RenderToSnapshot(const ContentContext& renderer, + const Entity& entity) const override; + + virtual Matrix GetLocalTransform() const; + + Matrix GetTransform(const Matrix& parent_transform) const; private: - /// @brief Takes a set of zero or more input textures and writes to an output - /// texture. + virtual std::optional GetFilterCoverage( + const FilterInput::Vector& inputs, + const Entity& entity) const; + + /// @brief Takes a set of zero or more input textures and writes to an output + /// texture. virtual bool RenderFilter(const FilterInput::Vector& inputs, const ContentContext& renderer, const Entity& entity, RenderPass& pass, - const Rect& bounds) const = 0; + const Rect& coverage) const = 0; FilterInput::Vector inputs_; - Rect destination_; FML_DISALLOW_COPY_AND_ASSIGN(FilterContents); + + friend FilterContentsFilterInput; }; } // namespace impeller diff --git a/engine/src/flutter/impeller/entity/contents/filters/filter_input.cc b/engine/src/flutter/impeller/entity/contents/filters/filter_input.cc index 51ff74a58b3..32db85b66ec 100644 --- a/engine/src/flutter/impeller/entity/contents/filters/filter_input.cc +++ b/engine/src/flutter/impeller/entity/contents/filters/filter_input.cc @@ -8,14 +8,38 @@ #include #include #include +#include +#include "fml/logging.h" +#include "impeller/entity/contents/filters/filter_contents.h" #include "impeller/entity/contents/snapshot.h" #include "impeller/entity/entity.h" namespace impeller { +/******************************************************************************* + ******* FilterInput + ******************************************************************************/ + FilterInput::Ref FilterInput::Make(Variant input) { - return std::shared_ptr(new FilterInput(input)); + if (auto filter = std::get_if>(&input)) { + return std::static_pointer_cast( + std::shared_ptr( + new FilterContentsFilterInput(*filter))); + } + + if (auto contents = std::get_if>(&input)) { + return std::static_pointer_cast( + std::shared_ptr( + new ContentsFilterInput(*contents))); + } + + if (auto texture = std::get_if>(&input)) { + return std::static_pointer_cast( + std::shared_ptr(new TextureFilterInput(*texture))); + } + + FML_UNREACHABLE(); } FilterInput::Vector FilterInput::Make(std::initializer_list inputs) { @@ -27,63 +51,118 @@ FilterInput::Vector FilterInput::Make(std::initializer_list inputs) { return result; } -FilterInput::Variant FilterInput::GetInput() const { - return input_; +Matrix FilterInput::GetLocalTransform(const Entity& entity) const { + return Matrix(); } -std::optional FilterInput::GetCoverage(const Entity& entity) const { - if (snapshot_) { - return snapshot_->GetCoverage(); - } - - if (auto contents = std::get_if>(&input_)) { - return contents->get()->GetCoverage(entity); - } - - if (auto texture = std::get_if>(&input_)) { - return entity.GetPathCoverage(); - } - - FML_UNREACHABLE(); +Matrix FilterInput::GetTransform(const Entity& entity) const { + return entity.GetTransformation() * GetLocalTransform(entity); } -std::optional FilterInput::GetSnapshot(const ContentContext& renderer, - const Entity& entity) const { - if (snapshot_) { - return snapshot_; - } - snapshot_ = MakeSnapshot(renderer, entity); - - return snapshot_; -} - -FilterInput::FilterInput(Variant input) : input_(input) {} - FilterInput::~FilterInput() = default; -std::optional FilterInput::MakeSnapshot( +/******************************************************************************* + ******* FilterContentsFilterInput + ******************************************************************************/ + +FilterContentsFilterInput::FilterContentsFilterInput( + std::shared_ptr filter) + : filter_(filter) {} + +FilterContentsFilterInput::~FilterContentsFilterInput() = default; + +FilterInput::Variant FilterContentsFilterInput::GetInput() const { + return filter_; +} + +std::optional FilterContentsFilterInput::GetSnapshot( const ContentContext& renderer, const Entity& entity) const { - if (auto contents = std::get_if>(&input_)) { - return contents->get()->RenderToSnapshot(renderer, entity); + if (!snapshot_.has_value()) { + snapshot_ = filter_->RenderToSnapshot(renderer, entity); } + return snapshot_; +} - if (auto texture = std::get_if>(&input_)) { - // Rendered textures stretch to fit the entity path coverage, so we - // incorporate this behavior by translating and scaling the snapshot - // transform. - auto path_bounds = entity.GetPath().GetBoundingBox(); - if (!path_bounds.has_value()) { - return std::nullopt; - } - auto transform = entity.GetTransformation() * - Matrix::MakeTranslation(path_bounds->origin) * - Matrix::MakeScale(Vector2(path_bounds->size) / - texture->get()->GetSize()); - return Snapshot{.texture = *texture, .transform = transform}; +std::optional FilterContentsFilterInput::GetCoverage( + const Entity& entity) const { + return filter_->GetCoverage(entity); +} + +Matrix FilterContentsFilterInput::GetLocalTransform( + const Entity& entity) const { + return filter_->GetLocalTransform(); +} + +Matrix FilterContentsFilterInput::GetTransform(const Entity& entity) const { + return filter_->GetTransform(entity.GetTransformation()); +} + +/******************************************************************************* + ******* ContentsFilterInput + ******************************************************************************/ + +ContentsFilterInput::ContentsFilterInput(std::shared_ptr contents) + : contents_(contents) {} + +ContentsFilterInput::~ContentsFilterInput() = default; + +FilterInput::Variant ContentsFilterInput::GetInput() const { + return contents_; +} + +std::optional ContentsFilterInput::GetSnapshot( + const ContentContext& renderer, + const Entity& entity) const { + if (!snapshot_.has_value()) { + snapshot_ = contents_->RenderToSnapshot(renderer, entity); } + return snapshot_; +} - FML_UNREACHABLE(); +std::optional ContentsFilterInput::GetCoverage( + const Entity& entity) const { + return contents_->GetCoverage(entity); +} + +/******************************************************************************* + ******* TextureFilterInput + ******************************************************************************/ + +TextureFilterInput::TextureFilterInput(std::shared_ptr texture) + : texture_(texture) {} + +TextureFilterInput::~TextureFilterInput() = default; + +FilterInput::Variant TextureFilterInput::GetInput() const { + return texture_; +} + +std::optional TextureFilterInput::GetSnapshot( + const ContentContext& renderer, + const Entity& entity) const { + return Snapshot{.texture = texture_, .transform = GetTransform(entity)}; +} + +std::optional TextureFilterInput::GetCoverage( + const Entity& entity) const { + auto path_bounds = entity.GetPath().GetBoundingBox(); + if (!path_bounds.has_value()) { + return std::nullopt; + } + return Rect::MakeSize(Size(texture_->GetSize())) + .TransformBounds(GetTransform(entity)); +} + +Matrix TextureFilterInput::GetLocalTransform(const Entity& entity) const { + // Compute the local transform such that the texture will cover the entity + // path bounding box. + auto path_bounds = entity.GetPath().GetBoundingBox(); + if (!path_bounds.has_value()) { + return Matrix(); + } + return Matrix::MakeTranslation(path_bounds->origin) * + Matrix::MakeScale(Vector2(path_bounds->size) / texture_->GetSize()); } } // namespace impeller diff --git a/engine/src/flutter/impeller/entity/contents/filters/filter_input.h b/engine/src/flutter/impeller/entity/contents/filters/filter_input.h index eee31df2651..dd1f907a6df 100644 --- a/engine/src/flutter/impeller/entity/contents/filters/filter_input.h +++ b/engine/src/flutter/impeller/entity/contents/filters/filter_input.h @@ -17,46 +17,137 @@ namespace impeller { class ContentContext; class Entity; +class FilterContents; + +/******************************************************************************* + ******* FilterInput + ******************************************************************************/ /// `FilterInput` is a lazy/single eval `Snapshot` which may be shared across -/// filter parameters and used to evaluate input bounds. +/// filter parameters and used to evaluate input coverage. /// -/// A `FilterInput` can be created from either a `Texture` or any `Contents` -/// class (including `FilterContents`), and can be re-used for any filter inputs -/// across an entity's filter graph without repeating subpasses unnecessarily. +/// A `FilterInput` can be re-used for any filter inputs across an entity's +/// filter graph without repeating subpasses unnecessarily. /// /// Filters may decide to not evaluate inputs in situations where they won't /// contribute to the filter's output texture. -class FilterInput final { +class FilterInput { public: using Ref = std::shared_ptr; using Vector = std::vector; - using Variant = - std::variant, std::shared_ptr>; + using Variant = std::variant, + std::shared_ptr, + std::shared_ptr>; - ~FilterInput(); + virtual ~FilterInput(); static FilterInput::Ref Make(Variant input); static FilterInput::Vector Make(std::initializer_list inputs); - Variant GetInput() const; + virtual Variant GetInput() const = 0; - std::optional GetCoverage(const Entity& entity) const; + virtual std::optional GetSnapshot(const ContentContext& renderer, + const Entity& entity) const = 0; + virtual std::optional GetCoverage(const Entity& entity) const = 0; + + /// @brief Get the local transform of this filter input. This transform is + /// relative to the `Entity` transform space. + virtual Matrix GetLocalTransform(const Entity& entity) const; + + /// @brief Get the transform of this `FilterInput`. This is equivalent to + /// calling `entity.GetTransformation() * GetLocalTransform()`. + virtual Matrix GetTransform(const Entity& entity) const; +}; + +/******************************************************************************* + ******* FilterContentsFilterInput + ******************************************************************************/ + +class FilterContentsFilterInput final : public FilterInput { + public: + ~FilterContentsFilterInput() override; + + // |FilterInput| + Variant GetInput() const override; + + // |FilterInput| std::optional GetSnapshot(const ContentContext& renderer, - const Entity& entity) const; + const Entity& entity) const override; + + // |FilterInput| + std::optional GetCoverage(const Entity& entity) const override; + + // |FilterInput| + Matrix GetLocalTransform(const Entity& entity) const override; + + // |FilterInput| + Matrix GetTransform(const Entity& entity) const override; private: - FilterInput(Variant input); + FilterContentsFilterInput(std::shared_ptr filter); - std::optional MakeSnapshot(const ContentContext& renderer, - const Entity& entity) const; - - std::optional MakeSnapshotForTexture(const Entity& entity) const; - - Variant input_; + std::shared_ptr filter_; mutable std::optional snapshot_; + + friend FilterInput; +}; + +/******************************************************************************* + ******* ContentsFilterInput + ******************************************************************************/ + +class ContentsFilterInput final : public FilterInput { + public: + ~ContentsFilterInput() override; + + // |FilterInput| + Variant GetInput() const override; + + // |FilterInput| + std::optional GetSnapshot(const ContentContext& renderer, + const Entity& entity) const override; + + // |FilterInput| + std::optional GetCoverage(const Entity& entity) const override; + + private: + ContentsFilterInput(std::shared_ptr contents); + + std::shared_ptr contents_; + mutable std::optional snapshot_; + + friend FilterInput; +}; + +/******************************************************************************* + ******* TextureFilterInput + ******************************************************************************/ + +class TextureFilterInput final : public FilterInput { + public: + ~TextureFilterInput() override; + + // |FilterInput| + Variant GetInput() const override; + + // |FilterInput| + std::optional GetSnapshot(const ContentContext& renderer, + const Entity& entity) const override; + + // |FilterInput| + std::optional GetCoverage(const Entity& entity) const override; + + // |FilterInput| + Matrix GetLocalTransform(const Entity& entity) const override; + + private: + TextureFilterInput(std::shared_ptr texture); + + std::shared_ptr texture_; + + friend FilterInput; }; } // namespace impeller diff --git a/engine/src/flutter/impeller/entity/contents/filters/gaussian_blur_filter_contents.cc b/engine/src/flutter/impeller/entity/contents/filters/gaussian_blur_filter_contents.cc index 140ded547bb..1be110668a5 100644 --- a/engine/src/flutter/impeller/entity/contents/filters/gaussian_blur_filter_contents.cc +++ b/engine/src/flutter/impeller/entity/contents/filters/gaussian_blur_filter_contents.cc @@ -163,17 +163,22 @@ bool DirectionalGaussianBlurFilterContents::RenderFilter( return pass.AddCommand(cmd); } -std::optional DirectionalGaussianBlurFilterContents::GetCoverage( +std::optional DirectionalGaussianBlurFilterContents::GetFilterCoverage( + const FilterInput::Vector& inputs, const Entity& entity) const { - auto coverage = FilterContents::GetCoverage(entity); + if (inputs.empty()) { + return std::nullopt; + } + + auto coverage = inputs[0]->GetCoverage(entity); if (!coverage.has_value()) { return std::nullopt; } auto transformed_blur_vector = - entity.GetTransformation() - .TransformDirection(blur_direction_ * - ceil(Radius{blur_sigma_}.radius)) + inputs[0] + ->GetTransform(entity) + .TransformDirection(blur_direction_ * Radius{blur_sigma_}.radius) .Abs(); auto extent = coverage->size + transformed_blur_vector * 2; return Rect(coverage->origin - transformed_blur_vector, diff --git a/engine/src/flutter/impeller/entity/contents/filters/gaussian_blur_filter_contents.h b/engine/src/flutter/impeller/entity/contents/filters/gaussian_blur_filter_contents.h index b39ec2ceb6d..6b95d52a7a2 100644 --- a/engine/src/flutter/impeller/entity/contents/filters/gaussian_blur_filter_contents.h +++ b/engine/src/flutter/impeller/entity/contents/filters/gaussian_blur_filter_contents.h @@ -25,8 +25,9 @@ class DirectionalGaussianBlurFilterContents final : public FilterContents { void SetSourceOverride(FilterInput::Ref alpha_mask); - // |Contents| - std::optional GetCoverage(const Entity& entity) const override; + // |FilterContents| + std::optional GetFilterCoverage(const FilterInput::Vector& inputs, + const Entity& entity) const override; private: // |FilterContents|