From e1bda0b4f9b045aebb16b7a4b29bb04bb958628a Mon Sep 17 00:00:00 2001 From: Brandon DeRosier Date: Wed, 30 Mar 2022 12:47:33 -0700 Subject: [PATCH] Make filter inputs lazy and sharable (flutter/engine#103) --- engine/src/flutter/impeller/entity/BUILD.gn | 4 + .../entity/contents/content_context.cc | 42 ++++++ .../entity/contents/content_context.h | 8 + .../impeller/entity/contents/contents.cc | 51 +------ .../impeller/entity/contents/contents.h | 16 +- .../contents/filters/blend_filter_contents.cc | 78 +++++----- .../contents/filters/blend_filter_contents.h | 12 +- .../contents/filters/filter_contents.cc | 139 ++++-------------- .../entity/contents/filters/filter_contents.h | 31 ++-- .../entity/contents/filters/filter_input.cc | 77 ++++++++++ .../entity/contents/filters/filter_input.h | 60 ++++++++ .../filters/gaussian_blur_filter_contents.cc | 31 ++-- .../filters/gaussian_blur_filter_contents.h | 6 +- .../impeller/entity/contents/snapshot.cc | 40 +++++ .../impeller/entity/contents/snapshot.h | 36 +++++ engine/src/flutter/impeller/entity/entity.cc | 10 ++ engine/src/flutter/impeller/entity/entity.h | 2 + .../impeller/entity/entity_unittests.cc | 67 ++++++--- .../entity/shaders/gaussian_blur.frag | 2 - 19 files changed, 448 insertions(+), 264 deletions(-) create mode 100644 engine/src/flutter/impeller/entity/contents/filters/filter_input.cc create mode 100644 engine/src/flutter/impeller/entity/contents/filters/filter_input.h create mode 100644 engine/src/flutter/impeller/entity/contents/snapshot.cc create mode 100644 engine/src/flutter/impeller/entity/contents/snapshot.h diff --git a/engine/src/flutter/impeller/entity/BUILD.gn b/engine/src/flutter/impeller/entity/BUILD.gn index e04288155e8..d5062f30389 100644 --- a/engine/src/flutter/impeller/entity/BUILD.gn +++ b/engine/src/flutter/impeller/entity/BUILD.gn @@ -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", diff --git a/engine/src/flutter/impeller/entity/contents/content_context.cc b/engine/src/flutter/impeller/entity/contents/content_context.cc index 36dc90ca0bb..016e0b3d576 100644 --- a/engine/src/flutter/impeller/entity/contents/content_context.cc +++ b/engine/src/flutter/impeller/entity/contents/content_context.cc @@ -6,6 +6,10 @@ #include +#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) @@ -60,6 +64,44 @@ bool ContentContext::IsValid() const { return is_valid_; } +std::shared_ptr 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 ContentContext::GetContext() const { return context_; } diff --git a/engine/src/flutter/impeller/entity/contents/content_context.h b/engine/src/flutter/impeller/entity/contents/content_context.h index fc406d4f3dc..b1194cc9cd0 100644 --- a/engine/src/flutter/impeller/entity/contents/content_context.h +++ b/engine/src/flutter/impeller/entity/contents/content_context.h @@ -130,6 +130,14 @@ class ContentContext { std::shared_ptr GetContext() const; + using SubpassCallback = + std::function; + + /// @brief Creates a new texture of size `texture_size` and calls + /// `subpass_callback` with a `RenderPass` for drawing to the texture. + std::shared_ptr MakeSubpass(ISize texture_size, + SubpassCallback subpass_callback) const; + private: std::shared_ptr context_; diff --git a/engine/src/flutter/impeller/entity/contents/contents.cc b/engine/src/flutter/impeller/entity/contents/contents.cc index 4f0ba354c4c..83fa02514ca 100644 --- a/engine/src/flutter/impeller/entity/contents/contents.cc +++ b/engine/src/flutter/impeller/entity/contents/contents.cc @@ -38,13 +38,13 @@ Rect Contents::GetBounds(const Entity& entity) const { return Rect::MakePointBounds({points.begin(), points.end()}); } -std::optional Contents::RenderToTexture( +std::optional 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::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; - -std::optional> 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 diff --git a/engine/src/flutter/impeller/entity/contents/contents.h b/engine/src/flutter/impeller/entity/contents/contents.h index 915b89c34c3..ee9b0b0535f 100644 --- a/engine/src/flutter/impeller/entity/contents/contents.h +++ b/engine/src/flutter/impeller/entity/contents/contents.h @@ -9,6 +9,7 @@ #include #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; - /// 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; - static std::optional> MakeSubpass( - const ContentContext& renderer, - ISize texture_size, - SubpassCallback subpass_callback); - protected: private: diff --git a/engine/src/flutter/impeller/entity/contents/filters/blend_filter_contents.cc b/engine/src/flutter/impeller/entity/contents/filters/blend_filter_contents.cc index 81a048e5a1f..293065e6790 100644 --- a/engine/src/flutter/impeller/entity/contents/filters/blend_filter_contents.cc +++ b/engine/src/flutter/impeller/entity/contents/filters/blend_filter_contents.cc @@ -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 (ContentContext::*)(ContentContextOptions) const; template -static bool AdvancedBlend(const std::vector& 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& 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& 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( - 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& 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& 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& 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& input_textures, return true; } -bool BlendFilterContents::RenderFilter( - const std::vector& 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(); diff --git a/engine/src/flutter/impeller/entity/contents/filters/blend_filter_contents.h b/engine/src/flutter/impeller/entity/contents/filters/blend_filter_contents.h index 2346037f7b2..6baf5ec5be0 100644 --- a/engine/src/flutter/impeller/entity/contents/filters/blend_filter_contents.h +++ b/engine/src/flutter/impeller/entity/contents/filters/blend_filter_contents.h @@ -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& input_textures, + std::function; + const Entity& entity, + RenderPass& pass, + const Rect& bounds)>; BlendFilterContents(); @@ -23,10 +26,11 @@ class BlendFilterContents : public FilterContents { private: // |FilterContents| - bool RenderFilter(const std::vector& 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_; 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 e270857d81f..a038b0e8d63 100644 --- a/engine/src/flutter/impeller/entity/contents/filters/filter_contents.cc +++ b/engine/src/flutter/impeller/entity/contents/filters/filter_contents.cc @@ -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::MakeBlend( Entity::BlendMode blend_mode, - InputTextures input_textures) { + FilterInput::Vector inputs) { if (blend_mode > Entity::BlendMode::kLastAdvancedBlendMode) { VALIDATION_LOG << "Invalid blend mode " << static_cast(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(); - 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(); - new_blend->SetInputTextures({blend, *in_i}); + auto blend_input = inputs[0]; + std::shared_ptr new_blend; + for (auto in_i = inputs.begin() + 1; in_i < inputs.end(); in_i++) { + new_blend = std::make_shared(); + 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>(blend); - // This downcast is safe because we know blend is a BlendFilterContents. - return std::static_pointer_cast(contents); + // new_blend will always be assigned because inputs.size() >= 2. + return new_blend; } FML_UNREACHABLE(); } std::shared_ptr FilterContents::MakeDirectionalGaussianBlur( - InputVariant input_texture, + FilterInput::Ref input, Vector2 blur_vector) { auto blur = std::make_shared(); - blur->SetInputTextures({input_texture}); + blur->SetInputs({input}); blur->SetBlurVector(blur_vector); return blur; } std::shared_ptr 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>(&input)) { - return contents->get()->GetBounds(entity); - } - - if (auto texture = std::get_if>(&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 ResolveSnapshotForInput( - const ContentContext& renderer, - const Entity& entity, - FilterContents::InputVariant input) { - if (auto contents = std::get_if>(&input)) { - return contents->get()->RenderToTexture(renderer, entity); - } - - if (auto input_texture = std::get_if>(&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 FilterContents::RenderToTexture( +std::optional FilterContents::RenderToTexture( const ContentContext& renderer, const Entity& entity) const { auto bounds = GetBounds(entity); @@ -190,38 +135,18 @@ std::optional FilterContents::RenderToTexture( return std::nullopt; } - // Resolve all inputs as textures. - - std::vector 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 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 fda5b8a7afd..20881632777 100644 --- a/engine/src/flutter/impeller/entity/contents/filters/filter_contents.h +++ b/engine/src/flutter/impeller/entity/contents/filters/filter_contents.h @@ -8,6 +8,7 @@ #include #include +#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>; - using InputTextures = std::vector; - - static std::shared_ptr MakeBlend( - Entity::BlendMode blend_mode, - InputTextures input_textures); + static std::shared_ptr MakeBlend(Entity::BlendMode blend_mode, + FilterInput::Vector inputs); static std::shared_ptr MakeDirectionalGaussianBlur( - InputVariant input_texture, + FilterInput::Ref input, Vector2 blur_vector); static std::shared_ptr - 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& 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); diff --git a/engine/src/flutter/impeller/entity/contents/filters/filter_input.cc b/engine/src/flutter/impeller/entity/contents/filters/filter_input.cc new file mode 100644 index 00000000000..eaf75a982ab --- /dev/null +++ b/engine/src/flutter/impeller/entity/contents/filters/filter_input.cc @@ -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 +#include +#include + +#include "impeller/entity/contents/snapshot.h" +#include "impeller/entity/entity.h" + +namespace impeller { + +FilterInput::Ref FilterInput::Make(Variant input) { + return std::shared_ptr(new FilterInput(input)); +} + +FilterInput::Vector FilterInput::Make(std::initializer_list 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>(&input_)) { + return contents->get()->GetBounds(entity); + } + + if (auto texture = std::get_if>(&input_)) { + return entity.GetTransformedPathBounds(); + } + + FML_UNREACHABLE(); +} + +std::optional 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 FilterInput::RenderToTexture( + const ContentContext& renderer, + const Entity& entity) const { + if (auto contents = std::get_if>(&input_)) { + return contents->get()->RenderToTexture(renderer, entity); + } + + if (auto texture = std::get_if>(&input_)) { + return Snapshot::FromTransformedTexture(renderer, entity, *texture); + } + + FML_UNREACHABLE(); +} + +} // 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 new file mode 100644 index 00000000000..b96e7203cc1 --- /dev/null +++ b/engine/src/flutter/impeller/entity/contents/filters/filter_input.h @@ -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 +#include +#include +#include + +#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; + using Vector = std::vector; + using Variant = + std::variant, std::shared_ptr>; + + ~FilterInput(); + + static FilterInput::Ref Make(Variant input); + + static FilterInput::Vector Make(std::initializer_list inputs); + + Variant GetInput() const; + + Rect GetBounds(const Entity& entity) const; + + std::optional GetSnapshot(const ContentContext& renderer, + const Entity& entity) const; + + private: + FilterInput(Variant input); + + std::optional RenderToTexture(const ContentContext& renderer, + const Entity& entity) const; + + Variant input_; + mutable std::optional snapshot_; +}; + +} // 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 f83b5804301..3f7f572ffa6 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 @@ -6,6 +6,7 @@ #include +#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& 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 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( 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 ad2e4284574..17aa92af792 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 @@ -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& 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_; diff --git a/engine/src/flutter/impeller/entity/contents/snapshot.cc b/engine/src/flutter/impeller/entity/contents/snapshot.cc new file mode 100644 index 00000000000..32dd34b5ef5 --- /dev/null +++ b/engine/src/flutter/impeller/entity/contents/snapshot.cc @@ -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::FromTransformedTexture( + const ContentContext& renderer, + const Entity& entity, + std::shared_ptr 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 diff --git a/engine/src/flutter/impeller/entity/contents/snapshot.h b/engine/src/flutter/impeller/entity/contents/snapshot.h new file mode 100644 index 00000000000..4169758ee1f --- /dev/null +++ b/engine/src/flutter/impeller/entity/contents/snapshot.h @@ -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 +#include +#include + +#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; + /// 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 FromTransformedTexture( + const ContentContext& renderer, + const Entity& entity, + std::shared_ptr texture); +}; + +} // namespace impeller diff --git a/engine/src/flutter/impeller/entity/entity.cc b/engine/src/flutter/impeller/entity/entity.cc index df13ef0bfe6..417d61636d9 100644 --- a/engine/src/flutter/impeller/entity/entity.cc +++ b/engine/src/flutter/impeller/entity/entity.cc @@ -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; } diff --git a/engine/src/flutter/impeller/entity/entity.h b/engine/src/flutter/impeller/entity/entity.h index 0f13dca6469..5c609671571 100644 --- a/engine/src/flutter/impeller/entity/entity.h +++ b/engine/src/flutter/impeller/entity/entity.h @@ -65,6 +65,8 @@ class Entity { void SetPath(Path path); + Rect GetTransformedPathBounds() const; + void SetAddsToCoverage(bool adds); bool AddsToCoverage() const; diff --git a/engine/src/flutter/impeller/entity/entity_unittests.cc b/engine/src/flutter/impeller/entity/entity_unittests.cc index b6d75a35279..b2c9779df02 100644 --- a/engine/src/flutter/impeller/entity/entity_unittests.cc +++ b/engine/src/flutter/impeller/entity/entity_unittests.cc @@ -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 + #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(&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(&cover_color)); + ImGui::ColorEdit4("Bounds color", reinterpret_cast(&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)); diff --git a/engine/src/flutter/impeller/entity/shaders/gaussian_blur.frag b/engine/src/flutter/impeller/entity/shaders/gaussian_blur.frag index 5edfafae7e9..61fa0f060d5 100644 --- a/engine/src/flutter/impeller/entity/shaders/gaussian_blur.frag +++ b/engine/src/flutter/impeller/entity/shaders/gaussian_blur.frag @@ -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++) {