Skip to content

Commit

Permalink
Filters: Add local transforms (flutter#140)
Browse files Browse the repository at this point in the history
  • Loading branch information
bdero authored Apr 21, 2022
1 parent 397d0fa commit 528e68d
Show file tree
Hide file tree
Showing 9 changed files with 298 additions and 86 deletions.
2 changes: 1 addition & 1 deletion impeller/entity/contents/contents.h
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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|
Expand All @@ -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;
Expand Down
39 changes: 31 additions & 8 deletions impeller/entity/contents/filters/filter_contents.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand All @@ -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;
Expand All @@ -157,32 +167,45 @@ std::optional<Rect> FilterContents::GetCoverage(const Entity& entity) const {
result = coverage;
continue;
}
result = result->Union(result.value());
result = result->Union(coverage.value());
}
return result;
}

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) {
return std::nullopt;
}

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
22 changes: 15 additions & 7 deletions impeller/entity/contents/filters/filter_contents.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
157 changes: 118 additions & 39 deletions impeller/entity/contents/filters/filter_input.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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();
}
Matrix FilterInput::GetTransform(const Entity& entity) const {
return entity.GetTransformation() * GetLocalTransform(entity);
}

if (auto contents = std::get_if<std::shared_ptr<Contents>>(&input_)) {
return contents->get()->GetCoverage(entity);
}
FilterInput::~FilterInput() = default;

if (auto texture = std::get_if<std::shared_ptr<Texture>>(&input_)) {
return entity.GetPathCoverage();
}
/*******************************************************************************
******* FilterContentsFilterInput
******************************************************************************/

FML_UNREACHABLE();
FilterContentsFilterInput::FilterContentsFilterInput(
std::shared_ptr<FilterContents> filter)
: filter_(filter) {}

FilterContentsFilterInput::~FilterContentsFilterInput() = default;

FilterInput::Variant FilterContentsFilterInput::GetInput() const {
return filter_;
}

std::optional<Snapshot> FilterInput::GetSnapshot(const ContentContext& renderer,
const Entity& entity) const {
if (snapshot_) {
return snapshot_;
std::optional<Snapshot> FilterContentsFilterInput::GetSnapshot(
const ContentContext& renderer,
const Entity& entity) const {
if (!snapshot_.has_value()) {
snapshot_ = filter_->RenderToSnapshot(renderer, entity);
}
snapshot_ = MakeSnapshot(renderer, entity);

return snapshot_;
}

FilterInput::FilterInput(Variant input) : input_(input) {}
std::optional<Rect> FilterContentsFilterInput::GetCoverage(
const Entity& entity) const {
return filter_->GetCoverage(entity);
}

FilterInput::~FilterInput() = default;
Matrix FilterContentsFilterInput::GetLocalTransform(
const Entity& entity) const {
return filter_->GetLocalTransform();
}

std::optional<Snapshot> FilterInput::MakeSnapshot(
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 (auto contents = std::get_if<std::shared_ptr<Contents>>(&input_)) {
return contents->get()->RenderToSnapshot(renderer, entity);
if (!snapshot_.has_value()) {
snapshot_ = contents_->RenderToSnapshot(renderer, entity);
}
return snapshot_;
}

std::optional<Rect> ContentsFilterInput::GetCoverage(
const Entity& entity) const {
return contents_->GetCoverage(entity);
}

/*******************************************************************************
******* TextureFilterInput
******************************************************************************/

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};
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));
}

FML_UNREACHABLE();
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
Loading

0 comments on commit 528e68d

Please sign in to comment.