Make filter inputs lazy and sharable (flutter/engine#103)

This commit is contained in:
Brandon DeRosier 2022-03-30 12:47:33 -07:00 committed by Dan Field
parent bc8eb90c27
commit e1bda0b4f9
19 changed files with 448 additions and 264 deletions

View File

@ -41,10 +41,14 @@ impeller_component("entity") {
"contents/filters/blend_filter_contents.h",
"contents/filters/filter_contents.cc",
"contents/filters/filter_contents.h",
"contents/filters/filter_input.cc",
"contents/filters/filter_input.h",
"contents/filters/gaussian_blur_filter_contents.cc",
"contents/filters/gaussian_blur_filter_contents.h",
"contents/linear_gradient_contents.cc",
"contents/linear_gradient_contents.h",
"contents/snapshot.cc",
"contents/snapshot.h",
"contents/solid_color_contents.cc",
"contents/solid_color_contents.h",
"contents/solid_stroke_contents.cc",

View File

@ -6,6 +6,10 @@
#include <sstream>
#include "impeller/renderer/command_buffer.h"
#include "impeller/renderer/render_pass.h"
#include "impeller/renderer/render_target.h"
namespace impeller {
ContentContext::ContentContext(std::shared_ptr<Context> context)
@ -60,6 +64,44 @@ bool ContentContext::IsValid() const {
return is_valid_;
}
std::shared_ptr<Texture> ContentContext::MakeSubpass(
ISize texture_size,
SubpassCallback subpass_callback) const {
auto context = GetContext();
auto subpass_target = RenderTarget::CreateOffscreen(*context, texture_size);
auto subpass_texture = subpass_target.GetRenderTargetTexture();
if (!subpass_texture) {
return nullptr;
}
auto sub_command_buffer = context->CreateRenderCommandBuffer();
sub_command_buffer->SetLabel("Offscreen Contents Command Buffer");
if (!sub_command_buffer) {
return nullptr;
}
auto sub_renderpass = sub_command_buffer->CreateRenderPass(subpass_target);
if (!sub_renderpass) {
return nullptr;
}
sub_renderpass->SetLabel("OffscreenContentsPass");
if (!subpass_callback(*this, *sub_renderpass)) {
return nullptr;
}
if (!sub_renderpass->EncodeCommands(*context->GetTransientsAllocator())) {
return nullptr;
}
if (!sub_command_buffer->SubmitCommands()) {
return nullptr;
}
return subpass_texture;
}
std::shared_ptr<Context> ContentContext::GetContext() const {
return context_;
}

View File

@ -130,6 +130,14 @@ class ContentContext {
std::shared_ptr<Context> GetContext() const;
using SubpassCallback =
std::function<bool(const ContentContext&, RenderPass&)>;
/// @brief Creates a new texture of size `texture_size` and calls
/// `subpass_callback` with a `RenderPass` for drawing to the texture.
std::shared_ptr<Texture> MakeSubpass(ISize texture_size,
SubpassCallback subpass_callback) const;
private:
std::shared_ptr<Context> context_;

View File

@ -38,13 +38,13 @@ Rect Contents::GetBounds(const Entity& entity) const {
return Rect::MakePointBounds({points.begin(), points.end()});
}
std::optional<Contents::Snapshot> Contents::RenderToTexture(
std::optional<Snapshot> Contents::RenderToTexture(
const ContentContext& renderer,
const Entity& entity) const {
auto bounds = GetBounds(entity);
auto texture = MakeSubpass(
renderer, ISize(bounds.size),
auto texture = renderer.MakeSubpass(
ISize::Ceil(bounds.size),
[&contents = *this, &entity, &bounds](const ContentContext& renderer,
RenderPass& pass) -> bool {
Entity sub_entity;
@ -56,52 +56,11 @@ std::optional<Contents::Snapshot> Contents::RenderToTexture(
return contents.Render(renderer, sub_entity, pass);
});
if (!texture.has_value()) {
if (!texture) {
return std::nullopt;
}
return Snapshot{.texture = texture.value(), .position = bounds.origin};
}
using SubpassCallback = std::function<bool(const ContentContext&, RenderPass&)>;
std::optional<std::shared_ptr<Texture>> Contents::MakeSubpass(
const ContentContext& renderer,
ISize texture_size,
SubpassCallback subpass_callback) {
auto context = renderer.GetContext();
auto subpass_target = RenderTarget::CreateOffscreen(*context, texture_size);
auto subpass_texture = subpass_target.GetRenderTargetTexture();
if (!subpass_texture) {
return std::nullopt;
}
auto sub_command_buffer = context->CreateRenderCommandBuffer();
sub_command_buffer->SetLabel("Offscreen Contents Command Buffer");
if (!sub_command_buffer) {
return std::nullopt;
}
auto sub_renderpass = sub_command_buffer->CreateRenderPass(subpass_target);
if (!sub_renderpass) {
return std::nullopt;
}
sub_renderpass->SetLabel("OffscreenContentsPass");
if (!subpass_callback(renderer, *sub_renderpass)) {
return std::nullopt;
}
if (!sub_renderpass->EncodeCommands(*context->GetTransientsAllocator())) {
return std::nullopt;
}
if (!sub_command_buffer->SubmitCommands()) {
return std::nullopt;
}
return subpass_texture;
return Snapshot{.texture = texture, .position = bounds.origin};
}
} // namespace impeller

View File

@ -9,6 +9,7 @@
#include <vector>
#include "flutter/fml/macros.h"
#include "impeller/entity/contents/snapshot.h"
#include "impeller/geometry/rect.h"
#include "impeller/renderer/texture.h"
@ -27,14 +28,6 @@ ContentContextOptions OptionsFromPassAndEntity(const RenderPass& pass,
class Contents {
public:
/// Represents a screen space texture and it's intended draw position.
struct Snapshot {
std::shared_ptr<Texture> texture;
/// The offset from the origin where this texture is intended to be
/// rendered.
Vector2 position;
};
Contents();
virtual ~Contents();
@ -54,13 +47,6 @@ class Contents {
const ContentContext& renderer,
const Entity& entity) const;
using SubpassCallback =
std::function<bool(const ContentContext&, RenderPass&)>;
static std::optional<std::shared_ptr<Texture>> MakeSubpass(
const ContentContext& renderer,
ISize texture_size,
SubpassCallback subpass_callback);
protected:
private:

View File

@ -5,6 +5,7 @@
#include "impeller/entity/contents/filters/blend_filter_contents.h"
#include "impeller/entity/contents/content_context.h"
#include "impeller/entity/contents/filters/filter_input.h"
#include "impeller/renderer/render_pass.h"
#include "impeller/renderer/sampler_library.h"
@ -20,11 +21,13 @@ using PipelineProc =
std::shared_ptr<Pipeline> (ContentContext::*)(ContentContextOptions) const;
template <typename VS, typename FS>
static bool AdvancedBlend(const std::vector<Contents::Snapshot>& input_textures,
static bool AdvancedBlend(const FilterInput::Vector& inputs,
const ContentContext& renderer,
const Entity& entity,
RenderPass& pass,
const Rect& bounds,
PipelineProc pipeline_proc) {
if (input_textures.size() < 2) {
if (inputs.size() < 2) {
return false;
}
@ -56,19 +59,21 @@ static bool AdvancedBlend(const std::vector<Contents::Snapshot>& input_textures,
typename VS::FrameInfo frame_info;
frame_info.mvp = Matrix::MakeOrthographic(size);
auto dst_snapshot = input_textures[1];
FS::BindTextureSamplerSrc(cmd, dst_snapshot.texture, sampler);
auto dst_snapshot = inputs[1]->GetSnapshot(renderer, entity);
FS::BindTextureSamplerSrc(cmd, dst_snapshot->texture, sampler);
frame_info.dst_uv_transform =
Matrix::MakeTranslation(-dst_snapshot.position / size) *
Matrix::MakeTranslation(-(dst_snapshot->position - bounds.origin) /
size) *
Matrix::MakeScale(
Vector3(Size(size) / Size(dst_snapshot.texture->GetSize())));
Vector3(Size(size) / Size(dst_snapshot->texture->GetSize())));
auto src_snapshot = input_textures[0];
FS::BindTextureSamplerDst(cmd, src_snapshot.texture, sampler);
auto src_snapshot = inputs[0]->GetSnapshot(renderer, entity);
FS::BindTextureSamplerDst(cmd, src_snapshot->texture, sampler);
frame_info.src_uv_transform =
Matrix::MakeTranslation(-src_snapshot.position / size) *
Matrix::MakeTranslation(-(src_snapshot->position - bounds.origin) /
size) *
Matrix::MakeScale(
Vector3(Size(size) / Size(src_snapshot.texture->GetSize())));
Vector3(Size(size) / Size(src_snapshot->texture->GetSize())));
auto uniform_view = host_buffer.EmplaceUniform(frame_info);
VS::BindFrameInfo(cmd, uniform_view);
@ -91,13 +96,14 @@ void BlendFilterContents::SetBlendMode(Entity::BlendMode blend_mode) {
switch (blend_mode) {
case Entity::BlendMode::kScreen:
advanced_blend_proc_ = [](const std::vector<Snapshot>& input_textures,
advanced_blend_proc_ = [](const FilterInput::Vector& inputs,
const ContentContext& renderer,
RenderPass& pass) {
const Entity& entity, RenderPass& pass,
const Rect& bounds) {
PipelineProc p = &ContentContext::GetTextureBlendScreenPipeline;
return AdvancedBlend<TextureBlendScreenPipeline::VertexShader,
TextureBlendScreenPipeline::FragmentShader>(
input_textures, renderer, pass, p);
inputs, renderer, entity, pass, bounds, p);
};
break;
default:
@ -106,9 +112,11 @@ void BlendFilterContents::SetBlendMode(Entity::BlendMode blend_mode) {
}
}
static bool BasicBlend(const std::vector<Contents::Snapshot>& input_textures,
static bool BasicBlend(const FilterInput::Vector& inputs,
const ContentContext& renderer,
const Entity& entity,
RenderPass& pass,
const Rect& bounds,
Entity::BlendMode basic_blend) {
using VS = TextureBlendPipeline::VertexShader;
using FS = TextureBlendPipeline::FragmentShader;
@ -138,21 +146,21 @@ static bool BasicBlend(const std::vector<Contents::Snapshot>& input_textures,
options.blend_mode = Entity::BlendMode::kSource;
cmd.pipeline = renderer.GetTextureBlendPipeline(options);
{
auto input = input_textures[0];
FS::BindTextureSamplerSrc(cmd, input.texture, sampler);
auto input = inputs[0]->GetSnapshot(renderer, entity);
FS::BindTextureSamplerSrc(cmd, input->texture, sampler);
VS::FrameInfo frame_info;
frame_info.mvp =
Matrix::MakeOrthographic(size) *
Matrix::MakeTranslation(input.position) *
Matrix::MakeScale(Size(input.texture->GetSize()) / Size(size));
Matrix::MakeTranslation(input->position - bounds.origin) *
Matrix::MakeScale(Size(input->texture->GetSize()) / Size(size));
auto uniform_view = host_buffer.EmplaceUniform(frame_info);
VS::BindFrameInfo(cmd, uniform_view);
}
pass.AddCommand(cmd);
if (input_textures.size() < 2) {
if (inputs.size() < 2) {
return true;
}
@ -161,16 +169,16 @@ static bool BasicBlend(const std::vector<Contents::Snapshot>& input_textures,
options.blend_mode = basic_blend;
cmd.pipeline = renderer.GetTextureBlendPipeline(options);
for (auto texture_i = input_textures.begin() + 1;
texture_i < input_textures.end(); texture_i++) {
auto input = *texture_i;
FS::BindTextureSamplerSrc(cmd, input.texture, sampler);
for (auto texture_i = inputs.begin() + 1; texture_i < inputs.end();
texture_i++) {
auto input = texture_i->get()->GetSnapshot(renderer, entity);
FS::BindTextureSamplerSrc(cmd, input->texture, sampler);
VS::FrameInfo frame_info;
frame_info.mvp = frame_info.mvp =
Matrix::MakeOrthographic(size) *
Matrix::MakeTranslation(input.position) *
Matrix::MakeScale(Size(input.texture->GetSize()) / Size(size));
Matrix::MakeTranslation(input->position - bounds.origin) *
Matrix::MakeScale(Size(input->texture->GetSize()) / Size(size));
auto uniform_view = host_buffer.EmplaceUniform(frame_info);
VS::BindFrameInfo(cmd, uniform_view);
@ -180,27 +188,27 @@ static bool BasicBlend(const std::vector<Contents::Snapshot>& input_textures,
return true;
}
bool BlendFilterContents::RenderFilter(
const std::vector<Snapshot>& input_textures,
const ContentContext& renderer,
RenderPass& pass,
const Matrix& transform) const {
if (input_textures.empty()) {
bool BlendFilterContents::RenderFilter(const FilterInput::Vector& inputs,
const ContentContext& renderer,
const Entity& entity,
RenderPass& pass,
const Rect& bounds) const {
if (inputs.empty()) {
return true;
}
if (input_textures.size() == 1) {
if (inputs.size() == 1) {
// Nothing to blend.
return BasicBlend(input_textures, renderer, pass,
return BasicBlend(inputs, renderer, entity, pass, bounds,
Entity::BlendMode::kSource);
}
if (blend_mode_ <= Entity::BlendMode::kLastPipelineBlendMode) {
return BasicBlend(input_textures, renderer, pass, blend_mode_);
return BasicBlend(inputs, renderer, entity, pass, bounds, blend_mode_);
}
if (blend_mode_ <= Entity::BlendMode::kLastAdvancedBlendMode) {
return advanced_blend_proc_(input_textures, renderer, pass);
return advanced_blend_proc_(inputs, renderer, entity, pass, bounds);
}
FML_UNREACHABLE();

View File

@ -5,15 +5,18 @@
#pragma once
#include "impeller/entity/contents/filters/filter_contents.h"
#include "impeller/entity/contents/filters/filter_input.h"
namespace impeller {
class BlendFilterContents : public FilterContents {
public:
using AdvancedBlendProc =
std::function<bool(const std::vector<Snapshot>& input_textures,
std::function<bool(const FilterInput::Vector& inputs,
const ContentContext& renderer,
RenderPass& pass)>;
const Entity& entity,
RenderPass& pass,
const Rect& bounds)>;
BlendFilterContents();
@ -23,10 +26,11 @@ class BlendFilterContents : public FilterContents {
private:
// |FilterContents|
bool RenderFilter(const std::vector<Snapshot>& input_textures,
bool RenderFilter(const FilterInput::Vector& inputs,
const ContentContext& renderer,
const Entity& entity,
RenderPass& pass,
const Matrix& transform) const override;
const Rect& bounds) const override;
Entity::BlendMode blend_mode_;
AdvancedBlendProc advanced_blend_proc_;

View File

@ -15,6 +15,7 @@
#include "impeller/base/validation.h"
#include "impeller/entity/contents/content_context.h"
#include "impeller/entity/contents/filters/blend_filter_contents.h"
#include "impeller/entity/contents/filters/filter_input.h"
#include "impeller/entity/contents/filters/gaussian_blur_filter_contents.h"
#include "impeller/entity/contents/texture_contents.h"
#include "impeller/entity/entity.h"
@ -27,61 +28,62 @@ namespace impeller {
std::shared_ptr<FilterContents> FilterContents::MakeBlend(
Entity::BlendMode blend_mode,
InputTextures input_textures) {
FilterInput::Vector inputs) {
if (blend_mode > Entity::BlendMode::kLastAdvancedBlendMode) {
VALIDATION_LOG << "Invalid blend mode " << static_cast<int>(blend_mode)
<< " passed to FilterContents::MakeBlend.";
return nullptr;
}
if (input_textures.size() < 2 ||
if (inputs.size() < 2 ||
blend_mode <= Entity::BlendMode::kLastPipelineBlendMode) {
auto blend = std::make_shared<BlendFilterContents>();
blend->SetInputTextures(input_textures);
blend->SetInputs(inputs);
blend->SetBlendMode(blend_mode);
return blend;
}
if (blend_mode <= Entity::BlendMode::kLastAdvancedBlendMode) {
InputVariant blend = input_textures[0];
for (auto in_i = input_textures.begin() + 1; in_i < input_textures.end();
in_i++) {
auto new_blend = std::make_shared<BlendFilterContents>();
new_blend->SetInputTextures({blend, *in_i});
auto blend_input = inputs[0];
std::shared_ptr<BlendFilterContents> new_blend;
for (auto in_i = inputs.begin() + 1; in_i < inputs.end(); in_i++) {
new_blend = std::make_shared<BlendFilterContents>();
new_blend->SetInputs({blend_input, *in_i});
new_blend->SetBlendMode(blend_mode);
blend = new_blend;
blend_input = FilterInput::Make(new_blend);
}
auto contents = std::get<std::shared_ptr<Contents>>(blend);
// This downcast is safe because we know blend is a BlendFilterContents.
return std::static_pointer_cast<FilterContents>(contents);
// new_blend will always be assigned because inputs.size() >= 2.
return new_blend;
}
FML_UNREACHABLE();
}
std::shared_ptr<FilterContents> FilterContents::MakeDirectionalGaussianBlur(
InputVariant input_texture,
FilterInput::Ref input,
Vector2 blur_vector) {
auto blur = std::make_shared<DirectionalGaussianBlurFilterContents>();
blur->SetInputTextures({input_texture});
blur->SetInputs({input});
blur->SetBlurVector(blur_vector);
return blur;
}
std::shared_ptr<FilterContents> FilterContents::MakeGaussianBlur(
InputVariant input_texture,
FilterInput::Ref input,
Scalar sigma_x,
Scalar sigma_y) {
auto x_blur = MakeDirectionalGaussianBlur(input_texture, Point(sigma_x, 0));
return MakeDirectionalGaussianBlur(x_blur, Point(0, sigma_y));
auto x_blur = MakeDirectionalGaussianBlur(input, Point(sigma_x, 0));
auto y_blur =
MakeDirectionalGaussianBlur(FilterInput::Make(x_blur), Point(0, sigma_y));
return y_blur;
}
FilterContents::FilterContents() = default;
FilterContents::~FilterContents() = default;
void FilterContents::SetInputTextures(InputTextures input_textures) {
input_textures_ = std::move(input_textures);
void FilterContents::SetInputs(FilterInput::Vector inputs) {
inputs_ = std::move(inputs);
}
bool FilterContents::Render(const ContentContext& renderer,
@ -108,81 +110,24 @@ bool FilterContents::Render(const ContentContext& renderer,
return contents->Render(renderer, e, pass);
}
Rect FilterContents::GetBoundsForInput(const Entity& entity,
const InputVariant& input) {
if (auto contents = std::get_if<std::shared_ptr<Contents>>(&input)) {
return contents->get()->GetBounds(entity);
}
if (auto texture = std::get_if<std::shared_ptr<Texture>>(&input)) {
auto points = entity.GetPath().GetBoundingBox()->GetPoints();
const auto& transform = entity.GetTransformation();
for (uint i = 0; i < points.size(); i++) {
points[i] = transform * points[i];
}
return Rect::MakePointBounds({points.begin(), points.end()});
}
FML_UNREACHABLE();
}
Rect FilterContents::GetBounds(const Entity& entity) const {
// The default bounds of FilterContents is just the union of its inputs.
// FilterContents implementations may choose to increase the bounds in any
// direction, but it should never
if (input_textures_.empty()) {
if (inputs_.empty()) {
return Rect();
}
Rect result = GetBoundsForInput(entity, input_textures_.front());
for (auto input_i = input_textures_.begin() + 1;
input_i < input_textures_.end(); input_i++) {
result.Union(GetBoundsForInput(entity, *input_i));
Rect result = inputs_.front()->GetBounds(entity);
for (auto input_i = inputs_.begin() + 1; input_i < inputs_.end(); input_i++) {
result.Union(input_i->get()->GetBounds(entity));
}
return result;
}
static std::optional<Contents::Snapshot> ResolveSnapshotForInput(
const ContentContext& renderer,
const Entity& entity,
FilterContents::InputVariant input) {
if (auto contents = std::get_if<std::shared_ptr<Contents>>(&input)) {
return contents->get()->RenderToTexture(renderer, entity);
}
if (auto input_texture = std::get_if<std::shared_ptr<Texture>>(&input)) {
auto input_bounds = FilterContents::GetBoundsForInput(entity, input);
// If the input is a texture, render the version of it which is transformed.
auto texture = Contents::MakeSubpass(
renderer, ISize(input_bounds.size),
[texture = *input_texture, entity, input_bounds](
const ContentContext& renderer, RenderPass& pass) -> bool {
TextureContents contents;
contents.SetTexture(texture);
contents.SetSourceRect(Rect::MakeSize(Size(texture->GetSize())));
Entity sub_entity;
sub_entity.SetPath(entity.GetPath());
sub_entity.SetBlendMode(Entity::BlendMode::kSource);
sub_entity.SetTransformation(
Matrix::MakeTranslation(Vector3(-input_bounds.origin)) *
entity.GetTransformation());
return contents.Render(renderer, sub_entity, pass);
});
if (!texture.has_value()) {
return std::nullopt;
}
return Contents::Snapshot{.texture = texture.value(),
.position = input_bounds.origin};
}
FML_UNREACHABLE();
}
std::optional<Contents::Snapshot> FilterContents::RenderToTexture(
std::optional<Snapshot> FilterContents::RenderToTexture(
const ContentContext& renderer,
const Entity& entity) const {
auto bounds = GetBounds(entity);
@ -190,38 +135,18 @@ std::optional<Contents::Snapshot> FilterContents::RenderToTexture(
return std::nullopt;
}
// Resolve all inputs as textures.
std::vector<Snapshot> input_textures;
input_textures.reserve(input_textures_.size());
for (const auto& input : input_textures_) {
auto texture_and_offset = ResolveSnapshotForInput(renderer, entity, input);
if (!texture_and_offset.has_value()) {
continue;
}
// Make the position of all input snapshots relative to this filter's
// snapshot position.
texture_and_offset->position -= bounds.origin;
input_textures.push_back(texture_and_offset.value());
}
// Create a new texture and render the filter to it.
auto texture = MakeSubpass(
renderer, ISize(GetBounds(entity).size),
// Render the filter into a new texture.
auto texture = renderer.MakeSubpass(
ISize(GetBounds(entity).size),
[=](const ContentContext& renderer, RenderPass& pass) -> bool {
return RenderFilter(input_textures, renderer, pass,
entity.GetTransformation());
return RenderFilter(inputs_, renderer, entity, pass, bounds);
});
if (!texture.has_value()) {
if (!texture) {
return std::nullopt;
}
return Snapshot{.texture = texture.value(), .position = bounds.origin};
return Snapshot{.texture = texture, .position = bounds.origin};
}
} // namespace impeller

View File

@ -8,6 +8,7 @@
#include <variant>
#include <vector>
#include "impeller/entity/contents/filters/filter_input.h"
#include "impeller/entity/entity.h"
#include "impeller/renderer/formats.h"
@ -17,35 +18,26 @@ class Pipeline;
class FilterContents : public Contents {
public:
using InputVariant =
std::variant<std::shared_ptr<Texture>, std::shared_ptr<Contents>>;
using InputTextures = std::vector<InputVariant>;
static std::shared_ptr<FilterContents> MakeBlend(
Entity::BlendMode blend_mode,
InputTextures input_textures);
static std::shared_ptr<FilterContents> MakeBlend(Entity::BlendMode blend_mode,
FilterInput::Vector inputs);
static std::shared_ptr<FilterContents> MakeDirectionalGaussianBlur(
InputVariant input_texture,
FilterInput::Ref input,
Vector2 blur_vector);
static std::shared_ptr<FilterContents>
MakeGaussianBlur(InputVariant input_texture, Scalar sigma_x, Scalar sigma_y);
static Rect GetBoundsForInput(const Entity& entity,
const InputVariant& input);
MakeGaussianBlur(FilterInput::Ref input, Scalar sigma_x, Scalar sigma_y);
FilterContents();
~FilterContents() override;
/// @brief The input texture sources for this filter. All texture sources are
/// expected to have or produce premultiplied alpha colors.
/// Any input can either be a `Texture` or another `FilterContents`.
/// @brief The input texture sources for this filter. Each input's emitted
/// texture is expected to have premultiplied alpha colors.
///
/// The number of required or optional textures depends on the
/// particular filter's implementation.
void SetInputTextures(InputTextures input_textures);
void SetInputs(FilterInput::Vector inputs);
// |Contents|
bool Render(const ContentContext& renderer,
@ -63,12 +55,13 @@ class FilterContents : public Contents {
private:
/// @brief Takes a set of zero or more input textures and writes to an output
/// texture.
virtual bool RenderFilter(const std::vector<Snapshot>& input_textures,
virtual bool RenderFilter(const FilterInput::Vector& inputs,
const ContentContext& renderer,
const Entity& entity,
RenderPass& pass,
const Matrix& transform) const = 0;
const Rect& bounds) const = 0;
InputTextures input_textures_;
FilterInput::Vector inputs_;
Rect destination_;
FML_DISALLOW_COPY_AND_ASSIGN(FilterContents);

View File

@ -0,0 +1,77 @@
// 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/contents/filters/filter_input.h"
#include <cstdarg>
#include <initializer_list>
#include <memory>
#include "impeller/entity/contents/snapshot.h"
#include "impeller/entity/entity.h"
namespace impeller {
FilterInput::Ref FilterInput::Make(Variant input) {
return std::shared_ptr<FilterInput>(new FilterInput(input));
}
FilterInput::Vector FilterInput::Make(std::initializer_list<Variant> inputs) {
FilterInput::Vector result;
result.reserve(inputs.size());
for (const auto& input : inputs) {
result.push_back(Make(input));
}
return result;
}
FilterInput::Variant FilterInput::GetInput() const {
return input_;
}
Rect FilterInput::GetBounds(const Entity& entity) const {
if (snapshot_) {
return Rect(snapshot_->position, Size(snapshot_->texture->GetSize()));
}
if (auto contents = std::get_if<std::shared_ptr<Contents>>(&input_)) {
return contents->get()->GetBounds(entity);
}
if (auto texture = std::get_if<std::shared_ptr<Texture>>(&input_)) {
return entity.GetTransformedPathBounds();
}
FML_UNREACHABLE();
}
std::optional<Snapshot> FilterInput::GetSnapshot(const ContentContext& renderer,
const Entity& entity) const {
if (snapshot_) {
return snapshot_;
}
snapshot_ = RenderToTexture(renderer, entity);
return snapshot_;
}
FilterInput::FilterInput(Variant input) : input_(input) {}
FilterInput::~FilterInput() = default;
std::optional<Snapshot> FilterInput::RenderToTexture(
const ContentContext& renderer,
const Entity& entity) const {
if (auto contents = std::get_if<std::shared_ptr<Contents>>(&input_)) {
return contents->get()->RenderToTexture(renderer, entity);
}
if (auto texture = std::get_if<std::shared_ptr<Texture>>(&input_)) {
return Snapshot::FromTransformedTexture(renderer, entity, *texture);
}
FML_UNREACHABLE();
}
} // namespace impeller

View File

@ -0,0 +1,60 @@
// 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.
#pragma once
#include <memory>
#include <optional>
#include <variant>
#include <vector>
#include "impeller/entity/contents/contents.h"
#include "impeller/geometry/rect.h"
#include "impeller/renderer/formats.h"
namespace impeller {
class ContentContext;
class Entity;
/// `FilterInput` is a lazy/single eval `Snapshot` which may be shared across
/// filter parameters and used to evaluate input bounds.
///
/// 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.
///
/// Filters may decide to not evaluate inputs in situations where they won't
/// contribute to the filter's output texture.
class FilterInput final {
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>>;
~FilterInput();
static FilterInput::Ref Make(Variant input);
static FilterInput::Vector Make(std::initializer_list<Variant> inputs);
Variant GetInput() const;
Rect GetBounds(const Entity& entity) const;
std::optional<Snapshot> GetSnapshot(const ContentContext& renderer,
const Entity& entity) const;
private:
FilterInput(Variant input);
std::optional<Snapshot> RenderToTexture(const ContentContext& renderer,
const Entity& entity) const;
Variant input_;
mutable std::optional<Snapshot> snapshot_;
};
} // namespace impeller

View File

@ -6,6 +6,7 @@
#include <valarray>
#include "impeller/base/validation.h"
#include "impeller/entity/contents/content_context.h"
#include "impeller/entity/contents/filters/filter_contents.h"
#include "impeller/geometry/rect.h"
@ -30,11 +31,12 @@ void DirectionalGaussianBlurFilterContents::SetBlurVector(Vector2 blur_vector) {
}
bool DirectionalGaussianBlurFilterContents::RenderFilter(
const std::vector<Snapshot>& input_textures,
const FilterInput::Vector& inputs,
const ContentContext& renderer,
const Entity& entity,
RenderPass& pass,
const Matrix& transform) const {
if (input_textures.empty()) {
const Rect& bounds) const {
if (inputs.empty()) {
return true;
}
@ -43,16 +45,19 @@ bool DirectionalGaussianBlurFilterContents::RenderFilter(
auto& host_buffer = pass.GetTransientsBuffer();
// Because this filter is intended to be used with only one input parameter,
// Because this filter is intended to be used with only one input parameter,
// and GetBounds just increases the input size by a factor of the direction,
// we we can just scale up the UVs by the same amount and don't need to worry
// about mapping the UVs to destination rect (like we do in
// about mapping the UVs to the destination rect (like we do in
// BlendFilterContents).
auto size = pass.GetRenderTargetSize();
auto transformed_blur = transform.TransformDirection(blur_vector_);
auto uv_offset = transformed_blur.Abs() / size;
auto input = inputs[0]->GetSnapshot(renderer, entity);
auto input_size = input->texture->GetSize();
auto transformed_blur =
entity.GetTransformation().TransformDirection(blur_vector_);
auto uv_offset = transformed_blur.Abs() / input_size;
// LTRB
Scalar uv[4] = {
-uv_offset.x,
@ -62,6 +67,7 @@ bool DirectionalGaussianBlurFilterContents::RenderFilter(
};
VertexBufferBuilder<VS::PerVertexData> vtx_builder;
auto size = pass.GetRenderTargetSize();
vtx_builder.AddVertices({
{Point(0, 0), Point(uv[0], uv[1])},
{Point(size.width, 0), Point(uv[2], uv[1])},
@ -73,7 +79,7 @@ bool DirectionalGaussianBlurFilterContents::RenderFilter(
auto vtx_buffer = vtx_builder.CreateVertexBuffer(host_buffer);
VS::FrameInfo frame_info;
frame_info.texture_size = Point(size);
frame_info.texture_size = Point(input_size);
frame_info.blur_radius = transformed_blur.GetLength();
frame_info.blur_direction = transformed_blur.Normalize();
@ -86,16 +92,13 @@ bool DirectionalGaussianBlurFilterContents::RenderFilter(
cmd.pipeline = renderer.GetGaussianBlurPipeline(options);
cmd.BindVertices(vtx_buffer);
const auto& [texture, _] = input_textures[0];
FS::BindTextureSampler(cmd, texture, sampler);
FS::BindTextureSampler(cmd, input->texture, sampler);
frame_info.mvp = Matrix::MakeOrthographic(size);
auto uniform_view = host_buffer.EmplaceUniform(frame_info);
VS::BindFrameInfo(cmd, uniform_view);
pass.AddCommand(cmd);
return true;
return pass.AddCommand(cmd);
}
Rect DirectionalGaussianBlurFilterContents::GetBounds(

View File

@ -5,6 +5,7 @@
#pragma once
#include "impeller/entity/contents/filters/filter_contents.h"
#include "impeller/entity/contents/filters/filter_input.h"
#include "impeller/geometry/matrix.h"
namespace impeller {
@ -22,10 +23,11 @@ class DirectionalGaussianBlurFilterContents final : public FilterContents {
private:
// |FilterContents|
bool RenderFilter(const std::vector<Snapshot>& input_textures,
bool RenderFilter(const FilterInput::Vector& input_textures,
const ContentContext& renderer,
const Entity& entity,
RenderPass& pass,
const Matrix& transform) const override;
const Rect& bounds) const override;
Vector2 blur_vector_;

View File

@ -0,0 +1,40 @@
// 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/contents/snapshot.h"
#include "impeller/entity/contents/content_context.h"
#include "impeller/entity/contents/texture_contents.h"
namespace impeller {
std::optional<Snapshot> Snapshot::FromTransformedTexture(
const ContentContext& renderer,
const Entity& entity,
std::shared_ptr<Texture> texture) {
Rect bounds = entity.GetTransformedPathBounds();
auto result = renderer.MakeSubpass(
ISize(bounds.size),
[&texture, &entity, bounds](const ContentContext& renderer,
RenderPass& pass) -> bool {
TextureContents contents;
contents.SetTexture(texture);
contents.SetSourceRect(Rect::MakeSize(Size(texture->GetSize())));
Entity sub_entity;
sub_entity.SetPath(entity.GetPath());
sub_entity.SetBlendMode(Entity::BlendMode::kSource);
sub_entity.SetTransformation(
Matrix::MakeTranslation(Vector3(-bounds.origin)) *
entity.GetTransformation());
return contents.Render(renderer, sub_entity, pass);
});
if (!result) {
return std::nullopt;
}
return Snapshot{.texture = result, .position = bounds.origin};
}
} // namespace impeller

View File

@ -0,0 +1,36 @@
// 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.
#pragma once
#include <functional>
#include <memory>
#include <vector>
#include "flutter/fml/macros.h"
#include "impeller/geometry/matrix.h"
#include "impeller/geometry/rect.h"
#include "impeller/renderer/texture.h"
namespace impeller {
class ContentContext;
class Entity;
/// Represents a texture and its intended draw position.
struct Snapshot {
std::shared_ptr<Texture> texture;
/// The offset from the origin where this texture is intended to be
/// rendered.
Vector2 position;
/// Transform a texture by the given `entity`'s transformation matrix to a new
/// texture.
static std::optional<Snapshot> FromTransformedTexture(
const ContentContext& renderer,
const Entity& entity,
std::shared_ptr<Texture> texture);
};
} // namespace impeller

View File

@ -30,6 +30,16 @@ void Entity::SetPath(Path path) {
path_ = std::move(path);
}
Rect Entity::GetTransformedPathBounds() const {
auto points = GetPath().GetBoundingBox()->GetPoints();
const auto& transform = GetTransformation();
for (uint i = 0; i < points.size(); i++) {
points[i] = transform * points[i];
}
return Rect::MakePointBounds({points.begin(), points.end()});
}
void Entity::SetAddsToCoverage(bool adds) {
adds_to_coverage_ = adds;
}

View File

@ -65,6 +65,8 @@ class Entity {
void SetPath(Path path);
Rect GetTransformedPathBounds() const;
void SetAddsToCoverage(bool adds);
bool AddsToCoverage() const;

View File

@ -2,8 +2,11 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <memory>
#include "flutter/testing/testing.h"
#include "impeller/entity/contents/filters/filter_contents.h"
#include "impeller/entity/contents/filters/filter_input.h"
#include "impeller/entity/contents/solid_color_contents.h"
#include "impeller/entity/contents/solid_stroke_contents.h"
#include "impeller/entity/entity.h"
@ -657,11 +660,16 @@ TEST_F(EntityTest, Filters) {
ASSERT_TRUE(bridge && boston && kalimba);
auto callback = [&](ContentContext& context, RenderPass& pass) -> bool {
auto blend0 = FilterContents::MakeBlend(Entity::BlendMode::kModulate,
{kalimba, boston});
auto fi_bridge = FilterInput::Make(bridge);
auto fi_boston = FilterInput::Make(boston);
auto fi_kalimba = FilterInput::Make(kalimba);
auto blend1 = FilterContents::MakeBlend(Entity::BlendMode::kScreen,
{bridge, blend0, bridge, bridge});
auto blend0 = FilterContents::MakeBlend(Entity::BlendMode::kModulate,
{fi_kalimba, fi_boston});
auto blend1 = FilterContents::MakeBlend(
Entity::BlendMode::kScreen,
{fi_bridge, FilterInput::Make(blend0), fi_bridge, fi_bridge});
Entity entity;
entity.SetPath(PathBuilder{}.AddRect({100, 100, 300, 300}).TakePath());
@ -681,52 +689,71 @@ TEST_F(EntityTest, GaussianBlurFilter) {
auto callback = [&](ContentContext& context, RenderPass& pass) -> bool {
if (first_frame) {
first_frame = false;
ImGui::SetNextWindowSize({500, 170});
ImGui::SetNextWindowSize({500, 190});
ImGui::SetNextWindowPos({300, 550});
}
ImGui::Begin("Controls");
static float blur_amount[2] = {20, 20};
ImGui::SliderFloat2("Blur", &blur_amount[0], 0, 200);
static Color cover_color(1, 0, 0, 0.2);
ImGui::ColorEdit4("Cover color", reinterpret_cast<float*>(&cover_color));
static Color bounds_color(0, 1, 0, 0.1);
static float offset[2] = {500, 400};
static float rotation = 0;
static float scale[2] = {0.8, 0.8};
static float skew[2] = {0, 0};
ImGui::Begin("Controls");
ImGui::SliderFloat2("Blur", &blur_amount[0], 0, 200);
ImGui::ColorEdit4("Cover color", reinterpret_cast<float*>(&cover_color));
ImGui::ColorEdit4("Bounds color", reinterpret_cast<float*>(&bounds_color));
ImGui::SliderFloat2("Translation", &offset[0], 0,
pass.GetRenderTargetSize().width);
static float rotation = 0;
ImGui::SliderFloat("Rotation", &rotation, 0, kPi * 2);
static float scale[2] = {0.8, 0.8};
ImGui::SliderFloat2("Scale", &scale[0], 0, 3);
static float skew[2] = {0, 0};
ImGui::SliderFloat2("Skew", &skew[0], -3, 3);
ImGui::End();
auto blend = FilterContents::MakeBlend(Entity::BlendMode::kPlus,
{boston, bridge, bridge});
auto blend = FilterContents::MakeBlend(
Entity::BlendMode::kPlus, FilterInput::Make({boston, bridge, bridge}));
auto blur =
FilterContents::MakeGaussianBlur(blend, blur_amount[0], blur_amount[1]);
auto blur = FilterContents::MakeGaussianBlur(
FilterInput::Make(blend), blur_amount[0], blur_amount[1]);
auto rect = Rect(-Point(boston->GetSize()) / 2, Size(boston->GetSize()));
ISize input_size = boston->GetSize();
auto rect = Rect(-Point(input_size) / 2, Size(input_size));
auto ctm = Matrix::MakeTranslation(Vector3(offset[0], offset[1])) *
Matrix::MakeRotation(rotation, Vector4(0, 0, 1, 1)) *
Matrix::MakeRotationZ(Radians(rotation)) *
Matrix::MakeScale(Vector3(scale[0], scale[1])) *
Matrix::MakeSkew(skew[0], skew[1]);
auto target_contents = blur;
Entity entity;
entity.SetPath(PathBuilder{}.AddRect(rect).TakePath());
entity.SetContents(blur);
entity.SetContents(target_contents);
entity.SetTransformation(ctm);
entity.Render(context, pass);
// The following entity renders the expected transformed input.
// Renders a red "cover" rectangle that shows the original position of the
// unfiltered input.
Entity cover_entity;
cover_entity.SetPath(PathBuilder{}.AddRect(rect).TakePath());
cover_entity.SetContents(SolidColorContents::Make(cover_color));
cover_entity.SetContents(
SolidColorContents::Make(cover_color.Premultiply()));
cover_entity.SetTransformation(ctm);
cover_entity.Render(context, pass);
// Renders a green bounding rect of the target filter.
Entity bounds_entity;
bounds_entity.SetPath(
PathBuilder{}.AddRect(target_contents->GetBounds(entity)).TakePath());
bounds_entity.SetContents(
SolidColorContents::Make(bounds_color.Premultiply()));
bounds_entity.SetTransformation(Matrix());
bounds_entity.Render(context, pass);
return true;
};
ASSERT_TRUE(OpenPlaygroundHere(callback));

View File

@ -35,8 +35,6 @@ vec4 SampleWithBorder(vec2 uv) {
}
void main() {
vec2 blur_radius_uv = vec2(v_blur_radius) / v_texture_size;
vec4 total = vec4(0);
float total_gaussian = 0;
for (float i = -v_blur_radius; i <= v_blur_radius; i++) {