From b3cbf05b81800a415049c73bdcf3a3b820860ffc Mon Sep 17 00:00:00 2001 From: Brandon DeRosier Date: Thu, 10 Mar 2022 12:35:53 -0800 Subject: [PATCH] Chainable texture filters (#67) --- impeller/entity/BUILD.gn | 3 +- impeller/entity/contents/content_context.h | 6 + impeller/entity/contents/filter_contents.cc | 203 ++++++++++++++++++++ impeller/entity/contents/filter_contents.h | 94 +++++++++ impeller/entity/entity.cc | 3 +- impeller/entity/entity.h | 3 +- impeller/entity/entity_pass.cc | 2 +- impeller/entity/entity_unittests.cc | 30 ++- 8 files changed, 339 insertions(+), 5 deletions(-) create mode 100644 impeller/entity/contents/filter_contents.cc create mode 100644 impeller/entity/contents/filter_contents.h diff --git a/impeller/entity/BUILD.gn b/impeller/entity/BUILD.gn index e069fdde3cf0a..4585aed2e3102 100644 --- a/impeller/entity/BUILD.gn +++ b/impeller/entity/BUILD.gn @@ -29,6 +29,8 @@ impeller_component("entity") { "contents/content_context.h", "contents/contents.cc", "contents/contents.h", + "contents/filter_contents.cc", + "contents/filter_contents.h", "contents/linear_gradient_contents.cc", "contents/linear_gradient_contents.h", "contents/solid_color_contents.cc", @@ -55,7 +57,6 @@ impeller_component("entity") { "../typographer", ] - deps = [ "//flutter/fml", diff --git a/impeller/entity/contents/content_context.h b/impeller/entity/contents/content_context.h index 042dc6aea3562..e09e85e400ebf 100644 --- a/impeller/entity/contents/content_context.h +++ b/impeller/entity/contents/content_context.h @@ -159,6 +159,12 @@ class ContentContext { color0.src_alpha_blend_factor = BlendFactor::kOneMinusDestinationAlpha; color0.src_color_blend_factor = BlendFactor::kOneMinusDestinationAlpha; break; + case Entity::BlendMode::kPlus: + color0.dst_alpha_blend_factor = BlendFactor::kOneMinusSourceAlpha; + color0.dst_color_blend_factor = BlendFactor::kOne; + color0.src_alpha_blend_factor = BlendFactor::kSourceAlpha; + color0.src_color_blend_factor = BlendFactor::kOne; + break; } desc.SetColorAttachmentDescriptor(0u, std::move(color0)); } diff --git a/impeller/entity/contents/filter_contents.cc b/impeller/entity/contents/filter_contents.cc new file mode 100644 index 0000000000000..d043a6b3334d1 --- /dev/null +++ b/impeller/entity/contents/filter_contents.cc @@ -0,0 +1,203 @@ +// 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 "filter_contents.h" + +#include +#include +#include + +#include "flutter/fml/logging.h" +#include "impeller/base/validation.h" +#include "impeller/entity/contents/content_context.h" +#include "impeller/entity/contents/solid_color_contents.h" +#include "impeller/entity/contents/texture_contents.h" +#include "impeller/entity/entity.h" +#include "impeller/geometry/path_builder.h" +#include "impeller/renderer/command_buffer.h" +#include "impeller/renderer/render_pass.h" +#include "impeller/renderer/sampler_library.h" + +namespace impeller { + +/******************************************************************************* + ******* FilterContents + ******************************************************************************/ + +std::shared_ptr FilterContents::MakeBlend( + Entity::BlendMode blend_mode, + InputTextures input_textures) { + auto blend = std::make_shared(); + blend->SetInputTextures(input_textures); + blend->SetBlendMode(blend_mode); + return blend; +} + +FilterContents::FilterContents() = default; + +FilterContents::~FilterContents() = default; + +void FilterContents::SetInputTextures(InputTextures input_textures) { + input_textures_ = std::move(input_textures); +} + +bool FilterContents::Render(const ContentContext& renderer, + const Entity& entity, + RenderPass& pass) const { + // Run the filter. + + auto maybe_texture = RenderFilterToTexture(renderer, entity, pass); + if (!maybe_texture.has_value()) { + return false; + } + auto& texture = maybe_texture.value(); + + // Draw the resulting texture to the given destination rect, respecting the + // transform and clip stack. + + auto contents = std::make_shared(); + contents->SetTexture(texture); + contents->SetSourceRect(IRect::MakeSize(texture->GetSize())); + + return contents->Render(renderer, entity, pass); +} + +std::optional> FilterContents::RenderFilterToTexture( + const ContentContext& renderer, + const Entity& entity, + RenderPass& pass) const { + auto output_size = GetOutputSize(); + if (output_size.IsZero()) { + return std::nullopt; + } + + // Resolve all inputs as textures. + + std::vector> input_textures; + input_textures.reserve(input_textures_.size()); + for (const auto& input : input_textures_) { + if (auto filter = std::get_if>(&input)) { + auto texture = + filter->get()->RenderFilterToTexture(renderer, entity, pass); + if (!texture.has_value()) { + return std::nullopt; + } + input_textures.push_back(std::move(texture.value())); + } else if (auto texture = std::get_if>(&input)) { + input_textures.push_back(*texture); + } else { + FML_UNREACHABLE(); + } + } + + // Create a new texture and render the filter to it. + + auto context = renderer.GetContext(); + + auto subpass_target = RenderTarget::CreateOffscreen(*context, output_size); + auto subpass_texture = subpass_target.GetRenderTargetTexture(); + if (!subpass_texture) { + return std::nullopt; + } + + auto sub_command_buffer = context->CreateRenderCommandBuffer(); + sub_command_buffer->SetLabel("Offscreen Filter 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("OffscreenFilterPass"); + + if (!RenderFilter(input_textures, 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; +} + +ISize FilterContents::GetOutputSize() const { + if (input_textures_.empty()) { + return {}; + } + + if (auto filter = + std::get_if>(&input_textures_[0])) { + return filter->get()->GetOutputSize(); + } + + if (auto texture = + std::get_if>(&input_textures_[0])) { + return texture->get()->GetSize(); + } + + FML_UNREACHABLE(); +} + +/******************************************************************************* + ******* BlendFilterContents + ******************************************************************************/ + +BlendFilterContents::BlendFilterContents() = default; + +BlendFilterContents::~BlendFilterContents() = default; + +void BlendFilterContents::SetBlendMode(Entity::BlendMode blend_mode) { + blend_mode_ = blend_mode; +} + +bool BlendFilterContents::RenderFilter( + const std::vector>& input_textures, + const ContentContext& renderer, + RenderPass& pass) const { + using VS = TexturePipeline::VertexShader; + using FS = TexturePipeline::FragmentShader; + + auto& host_buffer = pass.GetTransientsBuffer(); + + VertexBufferBuilder vtx_builder; + vtx_builder.AddVertices({ + {Point(0, 0), Point(0, 0)}, + {Point(1, 0), Point(1, 0)}, + {Point(1, 1), Point(1, 1)}, + {Point(0, 0), Point(0, 0)}, + {Point(1, 1), Point(1, 1)}, + {Point(0, 1), Point(0, 1)}, + }); + auto vtx_buffer = vtx_builder.CreateVertexBuffer(host_buffer); + + VS::FrameInfo frame_info; + frame_info.mvp = Matrix::MakeOrthographic(ISize(1, 1)); + frame_info.alpha = 1; + + auto uniform_view = host_buffer.EmplaceUniform(frame_info); + auto sampler = renderer.GetContext()->GetSamplerLibrary()->GetSampler({}); + + Command cmd; + cmd.label = "Blend Filter"; + auto options = OptionsFromPass(pass); + options.blend_mode = blend_mode_; + cmd.pipeline = renderer.GetTexturePipeline(options); + cmd.BindVertices(vtx_buffer); + VS::BindFrameInfo(cmd, uniform_view); + for (const auto& texture : input_textures) { + FS::BindTextureSampler(cmd, texture, sampler); + pass.AddCommand(cmd); + } + + return true; +} + +} // namespace impeller diff --git a/impeller/entity/contents/filter_contents.h b/impeller/entity/contents/filter_contents.h new file mode 100644 index 0000000000000..b1839cff4efbd --- /dev/null +++ b/impeller/entity/contents/filter_contents.h @@ -0,0 +1,94 @@ +// 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 "impeller/entity/contents/contents.h" +#include "impeller/entity/entity.h" +#include "impeller/renderer/formats.h" + +namespace impeller { + +/******************************************************************************* + ******* FilterContents + ******************************************************************************/ + +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); + + 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`. + /// + /// The number of required or optional textures depends on the + /// particular filter's implementation. + void SetInputTextures(InputTextures input_textures); + + // |Contents| + bool Render(const ContentContext& renderer, + const Entity& entity, + RenderPass& pass) const override; + + /// @brief Renders dependency filters, creates a subpass, and calls the + /// `RenderFilter` defined by the subclasses. + std::optional> RenderFilterToTexture( + const ContentContext& renderer, + const Entity& entity, + RenderPass& pass) const; + + 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, + const ContentContext& renderer, + RenderPass& pass) const = 0; + + /// @brief Determines the size of the output texture. + virtual ISize GetOutputSize() const; + + InputTextures input_textures_; + Rect destination_; + + FML_DISALLOW_COPY_AND_ASSIGN(FilterContents); +}; + +/******************************************************************************* + ******* BlendFilterContents + ******************************************************************************/ + +class BlendFilterContents : public FilterContents { + public: + BlendFilterContents(); + + ~BlendFilterContents() override; + + void SetBlendMode(Entity::BlendMode blend_mode); + + private: + bool RenderFilter(const std::vector>& input_textures, + const ContentContext& renderer, + RenderPass& pass) const override; + + Entity::BlendMode blend_mode_ = Entity::BlendMode::kSourceOver; + + FML_DISALLOW_COPY_AND_ASSIGN(BlendFilterContents); +}; + +} // namespace impeller diff --git a/impeller/entity/entity.cc b/impeller/entity/entity.cc index dc976aa6874d3..7f595024f989c 100644 --- a/impeller/entity/entity.cc +++ b/impeller/entity/entity.cc @@ -65,7 +65,8 @@ void Entity::IncrementStencilDepth(uint32_t increment) { stencil_depth_ += increment; } -bool Entity::Render(ContentContext& renderer, RenderPass& parent_pass) const { +bool Entity::Render(const ContentContext& renderer, + RenderPass& parent_pass) const { if (!contents_) { return true; } diff --git a/impeller/entity/entity.h b/impeller/entity/entity.h index c43b549df6316..7d22c2bfd5ad7 100644 --- a/impeller/entity/entity.h +++ b/impeller/entity/entity.h @@ -27,6 +27,7 @@ class Entity { kDestination, kSourceOver, kDestinationOver, + kPlus, }; Entity(); @@ -57,7 +58,7 @@ class Entity { uint32_t GetStencilDepth() const; - bool Render(ContentContext& renderer, RenderPass& parent_pass) const; + bool Render(const ContentContext& renderer, RenderPass& parent_pass) const; private: Matrix transformation_; diff --git a/impeller/entity/entity_pass.cc b/impeller/entity/entity_pass.cc index b33f44400227c..6840c2b30e896 100644 --- a/impeller/entity/entity_pass.cc +++ b/impeller/entity/entity_pass.cc @@ -75,7 +75,7 @@ std::optional EntityPass::GetSubpassCoverage( return entities_coverage; } - // If the delete tells us the coverage is smaller than it needs to be, then + // If the delegate tells us the coverage is smaller than it needs to be, then // great. OTOH, if the delegate is being wasteful, limit coverage to what is // actually needed. return entities_coverage->Intersection(delegate_coverage.value()); diff --git a/impeller/entity/entity_unittests.cc b/impeller/entity/entity_unittests.cc index b0fca2aa83139..c418ff7cb62f2 100644 --- a/impeller/entity/entity_unittests.cc +++ b/impeller/entity/entity_unittests.cc @@ -3,6 +3,7 @@ // found in the LICENSE file. #include "flutter/testing/testing.h" +#include "impeller/entity/contents/filter_contents.h" #include "impeller/entity/contents/solid_color_contents.h" #include "impeller/entity/contents/solid_stroke_contents.h" #include "impeller/entity/entity.h" @@ -521,6 +522,9 @@ TEST_F(EntityTest, BlendingModeOptions) { case Entity::BlendMode::kDestinationOver: blend_mode_names.push_back("DestinationOver"); blend_mode_values.push_back(Entity::BlendMode::kDestinationOver); + case Entity::BlendMode::kPlus: + blend_mode_names.push_back("Plus"); + blend_mode_values.push_back(Entity::BlendMode::kPlus); }; } @@ -616,10 +620,34 @@ TEST_F(EntityTest, BezierCircleScaled) { .Close() .TakePath(); entity.SetPath(path); - entity.SetTransformation(Matrix::MakeScale({20.0, 20.0, 1.0}).Translate({-80, -15, 0})); + entity.SetTransformation( + Matrix::MakeScale({20.0, 20.0, 1.0}).Translate({-80, -15, 0})); entity.SetContents(SolidColorContents::Make(Color::Red())); ASSERT_TRUE(OpenPlaygroundHere(entity)); } +TEST_F(EntityTest, Filters) { + auto bridge = CreateTextureForFixture("bay_bridge.jpg"); + auto boston = CreateTextureForFixture("boston.jpg"); + auto kalimba = CreateTextureForFixture("kalimba.jpg"); + ASSERT_TRUE(bridge && boston && kalimba); + + auto callback = [&](ContentContext& context, RenderPass& pass) -> bool { + // Draws kalimba and overwrites it with boston. + auto blend0 = FilterContents::MakeBlend( + Entity::BlendMode::kSourceOver, {kalimba, boston}); + + // Adds bridge*3 to boston. + auto blend1 = FilterContents::MakeBlend( + Entity::BlendMode::kPlus, {bridge, bridge, blend0, bridge}); + + Entity entity; + entity.SetPath(PathBuilder{}.AddRect({100, 100, 300, 300}).TakePath()); + entity.SetContents(blend1); + return entity.Render(context, pass); + }; + ASSERT_TRUE(OpenPlaygroundHere(callback)); +} + } // namespace testing } // namespace impeller