Filters: Add local transforms (flutter/engine#140)

This commit is contained in:
Brandon DeRosier 2022-04-20 19:16:39 -07:00 committed by Dan Field
parent 04bbef5e1b
commit 63aac1a68c
9 changed files with 307 additions and 95 deletions

View File

@ -40,7 +40,7 @@ class Contents {
virtual std::optional<Rect> 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<Snapshot> RenderToSnapshot(

View File

@ -108,18 +108,21 @@ bool BorderMaskBlurFilterContents::RenderFilter(
return pass.AddCommand(std::move(cmd));
}
std::optional<Rect> BorderMaskBlurFilterContents::GetCoverage(
std::optional<Rect> 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();

View File

@ -21,8 +21,9 @@ class BorderMaskBlurFilterContents final : public FilterContents {
void SetBlurStyle(BlurStyle blur_style);
// |Contents|
std::optional<Rect> GetCoverage(const Entity& entity) const override;
// |FilterContents|
std::optional<Rect> 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;

View File

@ -52,7 +52,8 @@ std::shared_ptr<FilterContents> 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<FilterContents>(new_blend));
}
}
// new_blend will always be assigned because inputs.size() >= 2.
@ -139,6 +140,15 @@ bool FilterContents::Render(const ContentContext& renderer,
}
std::optional<Rect> 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<Rect> 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<Rect> FilterContents::GetCoverage(const Entity& entity) const {
}
std::optional<Rect> 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<Rect> 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<Rect> FilterContents::GetCoverage(const Entity& entity) const {
std::optional<Snapshot> 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<Snapshot> 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

View File

@ -122,23 +122,31 @@ class FilterContents : public Contents {
std::optional<Rect> GetCoverage(const Entity& entity) const override;
// |Contents|
virtual std::optional<Snapshot> RenderToSnapshot(
const ContentContext& renderer,
const Entity& entity) const override;
std::optional<Snapshot> 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<Rect> 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

View File

@ -8,14 +8,38 @@
#include <initializer_list>
#include <memory>
#include <optional>
#include <variant>
#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<FilterInput>(new FilterInput(input));
if (auto filter = std::get_if<std::shared_ptr<FilterContents>>(&input)) {
return std::static_pointer_cast<FilterInput>(
std::shared_ptr<FilterContentsFilterInput>(
new FilterContentsFilterInput(*filter)));
}
if (auto contents = std::get_if<std::shared_ptr<Contents>>(&input)) {
return std::static_pointer_cast<FilterInput>(
std::shared_ptr<ContentsFilterInput>(
new ContentsFilterInput(*contents)));
}
if (auto texture = std::get_if<std::shared_ptr<Texture>>(&input)) {
return std::static_pointer_cast<FilterInput>(
std::shared_ptr<TextureFilterInput>(new TextureFilterInput(*texture)));
}
FML_UNREACHABLE();
}
FilterInput::Vector FilterInput::Make(std::initializer_list<Variant> inputs) {
@ -27,63 +51,118 @@ FilterInput::Vector FilterInput::Make(std::initializer_list<Variant> inputs) {
return result;
}
FilterInput::Variant FilterInput::GetInput() const {
return input_;
Matrix FilterInput::GetLocalTransform(const Entity& entity) const {
return Matrix();
}
std::optional<Rect> FilterInput::GetCoverage(const Entity& entity) const {
if (snapshot_) {
return snapshot_->GetCoverage();
}
if (auto contents = std::get_if<std::shared_ptr<Contents>>(&input_)) {
return contents->get()->GetCoverage(entity);
}
if (auto texture = std::get_if<std::shared_ptr<Texture>>(&input_)) {
return entity.GetPathCoverage();
}
FML_UNREACHABLE();
Matrix FilterInput::GetTransform(const Entity& entity) const {
return entity.GetTransformation() * GetLocalTransform(entity);
}
std::optional<Snapshot> 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<Snapshot> FilterInput::MakeSnapshot(
/*******************************************************************************
******* FilterContentsFilterInput
******************************************************************************/
FilterContentsFilterInput::FilterContentsFilterInput(
std::shared_ptr<FilterContents> filter)
: filter_(filter) {}
FilterContentsFilterInput::~FilterContentsFilterInput() = default;
FilterInput::Variant FilterContentsFilterInput::GetInput() const {
return filter_;
}
std::optional<Snapshot> FilterContentsFilterInput::GetSnapshot(
const ContentContext& renderer,
const Entity& entity) const {
if (auto contents = std::get_if<std::shared_ptr<Contents>>(&input_)) {
return contents->get()->RenderToSnapshot(renderer, entity);
if (!snapshot_.has_value()) {
snapshot_ = filter_->RenderToSnapshot(renderer, entity);
}
return snapshot_;
}
if (auto texture = std::get_if<std::shared_ptr<Texture>>(&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<Rect> 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_(contents) {}
ContentsFilterInput::~ContentsFilterInput() = default;
FilterInput::Variant ContentsFilterInput::GetInput() const {
return contents_;
}
std::optional<Snapshot> ContentsFilterInput::GetSnapshot(
const ContentContext& renderer,
const Entity& entity) const {
if (!snapshot_.has_value()) {
snapshot_ = contents_->RenderToSnapshot(renderer, entity);
}
return snapshot_;
}
FML_UNREACHABLE();
std::optional<Rect> ContentsFilterInput::GetCoverage(
const Entity& entity) const {
return contents_->GetCoverage(entity);
}
/*******************************************************************************
******* TextureFilterInput
******************************************************************************/
TextureFilterInput::TextureFilterInput(std::shared_ptr<Texture> texture)
: texture_(texture) {}
TextureFilterInput::~TextureFilterInput() = default;
FilterInput::Variant TextureFilterInput::GetInput() const {
return texture_;
}
std::optional<Snapshot> TextureFilterInput::GetSnapshot(
const ContentContext& renderer,
const Entity& entity) const {
return Snapshot{.texture = texture_, .transform = GetTransform(entity)};
}
std::optional<Rect> 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

View File

@ -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<FilterInput>;
using Vector = std::vector<FilterInput::Ref>;
using Variant =
std::variant<std::shared_ptr<Texture>, std::shared_ptr<Contents>>;
using Variant = std::variant<std::shared_ptr<FilterContents>,
std::shared_ptr<Contents>,
std::shared_ptr<Texture>>;
~FilterInput();
virtual ~FilterInput();
static FilterInput::Ref Make(Variant input);
static FilterInput::Vector Make(std::initializer_list<Variant> inputs);
Variant GetInput() const;
virtual Variant GetInput() const = 0;
std::optional<Rect> GetCoverage(const Entity& entity) const;
virtual std::optional<Snapshot> GetSnapshot(const ContentContext& renderer,
const Entity& entity) const = 0;
virtual std::optional<Rect> 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<Snapshot> GetSnapshot(const ContentContext& renderer,
const Entity& entity) const;
const Entity& entity) const override;
// |FilterInput|
std::optional<Rect> 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<FilterContents> filter);
std::optional<Snapshot> MakeSnapshot(const ContentContext& renderer,
const Entity& entity) const;
std::optional<Snapshot> MakeSnapshotForTexture(const Entity& entity) const;
Variant input_;
std::shared_ptr<FilterContents> filter_;
mutable std::optional<Snapshot> snapshot_;
friend FilterInput;
};
/*******************************************************************************
******* ContentsFilterInput
******************************************************************************/
class ContentsFilterInput final : public FilterInput {
public:
~ContentsFilterInput() override;
// |FilterInput|
Variant GetInput() const override;
// |FilterInput|
std::optional<Snapshot> GetSnapshot(const ContentContext& renderer,
const Entity& entity) const override;
// |FilterInput|
std::optional<Rect> GetCoverage(const Entity& entity) const override;
private:
ContentsFilterInput(std::shared_ptr<Contents> contents);
std::shared_ptr<Contents> contents_;
mutable std::optional<Snapshot> snapshot_;
friend FilterInput;
};
/*******************************************************************************
******* TextureFilterInput
******************************************************************************/
class TextureFilterInput final : public FilterInput {
public:
~TextureFilterInput() override;
// |FilterInput|
Variant GetInput() const override;
// |FilterInput|
std::optional<Snapshot> GetSnapshot(const ContentContext& renderer,
const Entity& entity) const override;
// |FilterInput|
std::optional<Rect> GetCoverage(const Entity& entity) const override;
// |FilterInput|
Matrix GetLocalTransform(const Entity& entity) const override;
private:
TextureFilterInput(std::shared_ptr<Texture> texture);
std::shared_ptr<Texture> texture_;
friend FilterInput;
};
} // namespace impeller

View File

@ -163,17 +163,22 @@ bool DirectionalGaussianBlurFilterContents::RenderFilter(
return pass.AddCommand(cmd);
}
std::optional<Rect> DirectionalGaussianBlurFilterContents::GetCoverage(
std::optional<Rect> 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,

View File

@ -25,8 +25,9 @@ class DirectionalGaussianBlurFilterContents final : public FilterContents {
void SetSourceOverride(FilterInput::Ref alpha_mask);
// |Contents|
std::optional<Rect> GetCoverage(const Entity& entity) const override;
// |FilterContents|
std::optional<Rect> GetFilterCoverage(const FilterInput::Vector& inputs,
const Entity& entity) const override;
private:
// |FilterContents|