diff --git a/impeller/entity/BUILD.gn b/impeller/entity/BUILD.gn index 9e5f951d407fd..3d0c58cbcad54 100644 --- a/impeller/entity/BUILD.gn +++ b/impeller/entity/BUILD.gn @@ -18,6 +18,8 @@ impeller_shaders("entity_shaders") { "shaders/texture_blend.vert", "shaders/texture_blend_screen.frag", "shaders/texture_blend_screen.vert", + "shaders/gaussian_blur.frag", + "shaders/gaussian_blur.vert", "shaders/texture_fill.frag", "shaders/texture_fill.vert", "shaders/glyph_atlas.frag", diff --git a/impeller/entity/contents/content_context.cc b/impeller/entity/contents/content_context.cc index ad22a5e474a39..ae42a008691e3 100644 --- a/impeller/entity/contents/content_context.cc +++ b/impeller/entity/contents/content_context.cc @@ -23,6 +23,8 @@ ContentContext::ContentContext(std::shared_ptr context) texture_blend_screen_pipelines_[{}] = std::make_unique(*context_); texture_pipelines_[{}] = std::make_unique(*context_); + gaussian_blur_pipelines_[{}] = + std::make_unique(*context_); solid_stroke_pipelines_[{}] = std::make_unique(*context_); glyph_atlas_pipelines_[{}] = std::make_unique(*context_); diff --git a/impeller/entity/contents/content_context.h b/impeller/entity/contents/content_context.h index 7caa6aa01e104..d5ff659d5ea6d 100644 --- a/impeller/entity/contents/content_context.h +++ b/impeller/entity/contents/content_context.h @@ -12,6 +12,8 @@ #include "fml/logging.h" #include "impeller/base/validation.h" #include "impeller/entity/entity.h" +#include "impeller/entity/gaussian_blur.frag.h" +#include "impeller/entity/gaussian_blur.vert.h" #include "impeller/entity/glyph_atlas.frag.h" #include "impeller/entity/glyph_atlas.vert.h" #include "impeller/entity/gradient_fill.frag.h" @@ -20,13 +22,13 @@ #include "impeller/entity/solid_fill.vert.h" #include "impeller/entity/solid_stroke.frag.h" #include "impeller/entity/solid_stroke.vert.h" +#include "impeller/entity/texture_blend.frag.h" +#include "impeller/entity/texture_blend.vert.h" +#include "impeller/entity/texture_blend_screen.frag.h" +#include "impeller/entity/texture_blend_screen.vert.h" #include "impeller/entity/texture_fill.frag.h" #include "impeller/entity/texture_fill.vert.h" #include "impeller/renderer/formats.h" -#include "texture_blend.frag.h" -#include "texture_blend.vert.h" -#include "texture_blend_screen.frag.h" -#include "texture_blend_screen.vert.h" namespace impeller { @@ -40,6 +42,8 @@ using TextureBlendScreenPipeline = PipelineT; using TexturePipeline = PipelineT; +using GaussianBlurPipeline = + PipelineT; using SolidStrokePipeline = PipelineT; using GlyphAtlasPipeline = @@ -100,6 +104,11 @@ class ContentContext { return GetPipeline(texture_pipelines_, opts); } + std::shared_ptr GetGaussianBlurPipeline( + ContentContextOptions opts) const { + return GetPipeline(gaussian_blur_pipelines_, opts); + } + std::shared_ptr GetSolidStrokePipeline( ContentContextOptions opts) const { return GetPipeline(solid_stroke_pipelines_, opts); @@ -138,6 +147,7 @@ class ContentContext { mutable Variants texture_blend_pipelines_; mutable Variants texture_blend_screen_pipelines_; mutable Variants texture_pipelines_; + mutable Variants gaussian_blur_pipelines_; mutable Variants solid_stroke_pipelines_; mutable Variants clip_pipelines_; mutable Variants clip_restoration_pipelines_; diff --git a/impeller/entity/contents/filter_contents.cc b/impeller/entity/contents/filter_contents.cc index d1c22cfeac04e..579318d51bf30 100644 --- a/impeller/entity/contents/filter_contents.cc +++ b/impeller/entity/contents/filter_contents.cc @@ -4,6 +4,8 @@ #include "filter_contents.h" +#include +#include #include #include #include @@ -16,7 +18,9 @@ #include "impeller/entity/entity.h" #include "impeller/geometry/path_builder.h" #include "impeller/renderer/command_buffer.h" +#include "impeller/renderer/formats.h" #include "impeller/renderer/render_pass.h" +#include "impeller/renderer/sampler_descriptor.h" #include "impeller/renderer/sampler_library.h" namespace impeller { @@ -57,6 +61,28 @@ std::shared_ptr FilterContents::MakeBlend( FML_UNREACHABLE(); } +std::shared_ptr FilterContents::MakeDirectionalGaussianBlur( + InputVariant input_texture, + Scalar radius, + Vector2 direction, + bool clip_border) { + auto blur = std::make_shared(); + blur->SetInputTextures({input_texture}); + blur->SetRadius(radius); + blur->SetDirection(direction); + blur->SetClipBorder(clip_border); + return blur; +} + +std::shared_ptr FilterContents::MakeGaussianBlur( + InputVariant input_texture, + Scalar radius, + bool clip_border) { + auto x_blur = MakeDirectionalGaussianBlur(input_texture, radius, Point(1, 0), + clip_border); + return MakeDirectionalGaussianBlur(x_blur, radius, Point(0, 1), false); +} + FilterContents::FilterContents() = default; FilterContents::~FilterContents() = default; @@ -90,7 +116,7 @@ std::optional> FilterContents::RenderFilterToTexture( const ContentContext& renderer, const Entity& entity, RenderPass& pass) const { - auto output_size = GetOutputSize(); + auto output_size = GetOutputSize(input_textures_); if (output_size.IsZero()) { return std::nullopt; } @@ -155,14 +181,17 @@ ISize FilterContents::GetOutputSize() const { if (input_textures_.empty()) { return {}; } + return GetOutputSize(input_textures_); +} +ISize FilterContents::GetOutputSize(const InputTextures& input_textures) const { if (auto filter = - std::get_if>(&input_textures_[0])) { - return filter->get()->GetOutputSize(); + std::get_if>(&input_textures[0])) { + return filter->get()->GetOutputSize(input_textures); } if (auto texture = - std::get_if>(&input_textures_[0])) { + std::get_if>(&input_textures[0])) { return texture->get()->GetSize(); } @@ -348,4 +377,96 @@ bool BlendFilterContents::RenderFilter( FML_UNREACHABLE(); } +/******************************************************************************* + ******* DirectionalGaussianBlurFilterContents + ******************************************************************************/ + +DirectionalGaussianBlurFilterContents::DirectionalGaussianBlurFilterContents() = + default; + +DirectionalGaussianBlurFilterContents:: + ~DirectionalGaussianBlurFilterContents() = default; + +void DirectionalGaussianBlurFilterContents::SetRadius(Scalar radius) { + radius_ = std::max(radius, 1e-3f); +} + +void DirectionalGaussianBlurFilterContents::SetDirection(Vector2 direction) { + direction_ = direction.Normalize(); +} + +void DirectionalGaussianBlurFilterContents::SetClipBorder(bool clip) { + clip_ = clip; +} + +bool DirectionalGaussianBlurFilterContents::RenderFilter( + const std::vector>& input_textures, + const ContentContext& renderer, + RenderPass& pass) const { + using VS = GaussianBlurPipeline::VertexShader; + using FS = GaussianBlurPipeline::FragmentShader; + + auto& host_buffer = pass.GetTransientsBuffer(); + + ISize size = FilterContents::GetOutputSize(); + Point uv_offset = clip_ ? (Point(radius_, radius_) / size) : Point(); + // LTRB + Scalar uv[4] = { + -uv_offset.x, + -uv_offset.y, + 1 + uv_offset.x, + 1 + uv_offset.y, + }; + + VertexBufferBuilder vtx_builder; + vtx_builder.AddVertices({ + {Point(0, 0), Point(uv[0], uv[1])}, + {Point(size.width, 0), Point(uv[2], uv[1])}, + {Point(size.width, size.height), Point(uv[2], uv[3])}, + {Point(0, 0), Point(uv[0], uv[1])}, + {Point(size.width, size.height), Point(uv[2], uv[3])}, + {Point(0, size.height), Point(uv[0], uv[3])}, + }); + auto vtx_buffer = vtx_builder.CreateVertexBuffer(host_buffer); + + VS::FrameInfo frame_info; + frame_info.mvp = Matrix::MakeOrthographic(size); + frame_info.texture_size = Point(size); + frame_info.blur_radius = radius_; + frame_info.blur_direction = direction_; + + auto uniform_view = host_buffer.EmplaceUniform(frame_info); + auto sampler = renderer.GetContext()->GetSamplerLibrary()->GetSampler({}); + + Command cmd; + cmd.label = "Gaussian Blur Filter"; + auto options = OptionsFromPass(pass); + options.blend_mode = Entity::BlendMode::kSource; + cmd.pipeline = renderer.GetGaussianBlurPipeline(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; +} + +ISize DirectionalGaussianBlurFilterContents::GetOutputSize( + const InputTextures& input_textures) const { + ISize size; + if (auto filter = + std::get_if>(&input_textures[0])) { + size = filter->get()->GetOutputSize(); + } else if (auto texture = + std::get_if>(&input_textures[0])) { + size = texture->get()->GetSize(); + } else { + FML_UNREACHABLE(); + } + + return size + (clip_ ? ISize(radius_ * 2, radius_ * 2) : ISize()); +} + } // namespace impeller diff --git a/impeller/entity/contents/filter_contents.h b/impeller/entity/contents/filter_contents.h index bcee31d0e55a6..eca9c0aa1a783 100644 --- a/impeller/entity/contents/filter_contents.h +++ b/impeller/entity/contents/filter_contents.h @@ -30,6 +30,17 @@ class FilterContents : public Contents { Entity::BlendMode blend_mode, InputTextures input_textures); + static std::shared_ptr MakeDirectionalGaussianBlur( + InputVariant input_texture, + Scalar radius, + Vector2 direction, + bool clip_border = false); + + static std::shared_ptr MakeGaussianBlur( + InputVariant input_texture, + Scalar radius, + bool clip_border = false); + FilterContents(); ~FilterContents() override; @@ -54,6 +65,9 @@ class FilterContents : public Contents { const Entity& entity, RenderPass& pass) const; + /// @brief Fetch the size of the output texture. + ISize GetOutputSize() const; + private: /// @brief Takes a set of zero or more input textures and writes to an output /// texture. @@ -63,7 +77,7 @@ class FilterContents : public Contents { RenderPass& pass) const = 0; /// @brief Determines the size of the output texture. - virtual ISize GetOutputSize() const; + virtual ISize GetOutputSize(const InputTextures& input_textures) const; InputTextures input_textures_; Rect destination_; @@ -89,6 +103,7 @@ class BlendFilterContents : public FilterContents { void SetBlendMode(Entity::BlendMode blend_mode); private: + // |FilterContents| bool RenderFilter(const std::vector>& input_textures, const ContentContext& renderer, RenderPass& pass) const override; @@ -99,4 +114,37 @@ class BlendFilterContents : public FilterContents { FML_DISALLOW_COPY_AND_ASSIGN(BlendFilterContents); }; +/******************************************************************************* + ******* DirectionalGaussionBlurFilterContents + ******************************************************************************/ + +class DirectionalGaussianBlurFilterContents final : public FilterContents { + public: + DirectionalGaussianBlurFilterContents(); + + ~DirectionalGaussianBlurFilterContents() override; + + void SetRadius(Scalar radius); + + void SetDirection(Vector2 direction); + + void SetClipBorder(bool clip); + + private: + // |FilterContents| + bool RenderFilter(const std::vector>& input_textures, + const ContentContext& renderer, + RenderPass& pass) const override; + + // |FilterContents| + virtual ISize GetOutputSize( + const InputTextures& input_textures) const override; + + Scalar radius_ = 0; + Vector2 direction_; + bool clip_ = false; + + FML_DISALLOW_COPY_AND_ASSIGN(DirectionalGaussianBlurFilterContents); +}; + } // namespace impeller diff --git a/impeller/entity/entity_unittests.cc b/impeller/entity/entity_unittests.cc index f20f811c26c77..5c182cbf0c1d9 100644 --- a/impeller/entity/entity_unittests.cc +++ b/impeller/entity/entity_unittests.cc @@ -671,5 +671,49 @@ TEST_F(EntityTest, Filters) { ASSERT_TRUE(OpenPlaygroundHere(callback)); } +TEST_F(EntityTest, GaussianBlurFilter) { + auto bridge = CreateTextureForFixture("bay_bridge.jpg"); + auto boston = CreateTextureForFixture("boston.jpg"); + auto kalimba = CreateTextureForFixture("kalimba.jpg"); + ASSERT_TRUE(bridge && boston && kalimba); + + bool first_frame = true; + auto callback = [&](ContentContext& context, RenderPass& pass) -> bool { + if (first_frame) { + first_frame = false; + ImGui::SetNextWindowSize({450, 150}); + ImGui::SetNextWindowPos({200, 450}); + } + + ImGui::Begin("Controls"); + static float offset[2] = {500, 400}; + ImGui::SliderFloat2("Position offset", &offset[0], 0, 1000); + static float scale = 1; + ImGui::SliderFloat("Scale", &scale, 0, 1); + static float blur_radius = 20; + ImGui::SliderFloat("Blur radius", &blur_radius, 0, 200); + static bool clip_border = true; + ImGui::Checkbox("Clip", &clip_border); + + auto blend = FilterContents::MakeBlend(Entity::BlendMode::kPlus, + {boston, bridge, bridge}); + + auto blur = + FilterContents::MakeGaussianBlur(blend, blur_radius, clip_border); + + auto output_size = Size(blur->GetOutputSize()); + Rect bounds(Point(offset[0], offset[1]) - output_size / 2 * scale, + output_size * scale); + + ImGui::End(); + + Entity entity; + entity.SetPath(PathBuilder{}.AddRect(bounds).TakePath()); + entity.SetContents(blur); + return entity.Render(context, pass); + }; + ASSERT_TRUE(OpenPlaygroundHere(callback)); +} + } // namespace testing } // namespace impeller diff --git a/impeller/entity/shaders/gaussian_blur.frag b/impeller/entity/shaders/gaussian_blur.frag new file mode 100644 index 0000000000000..5edfafae7e970 --- /dev/null +++ b/impeller/entity/shaders/gaussian_blur.frag @@ -0,0 +1,49 @@ +// 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. + +// 1D (directional) gaussian blur. +// +// Paths for future optimization: +// * Remove the uv bounds check branch in SampleColor by adding optional +// support for SamplerAddressMode::ClampToBorder in the texture sampler. +// * Sample from higher mipmap levels when the blur radius is high enough. + +uniform sampler2D texture_sampler; + +in vec2 v_texture_coords; +in vec2 v_texture_size; +in vec2 v_blur_direction; +in float v_blur_radius; + +out vec4 frag_color; + +const float kTwoPi = 6.283185307179586; + +float Gaussian(float x) { + float stddev = v_blur_radius * 0.5; + float xnorm = x / stddev; + return exp(-0.5 * xnorm * xnorm) / (kTwoPi * stddev * stddev); +} + +// Emulate SamplerAddressMode::ClampToBorder. +vec4 SampleWithBorder(vec2 uv) { + if (uv.x > 0 && uv.y > 0 && uv.x < 1 && uv.y < 1) { + return texture(texture_sampler, uv); + } + return vec4(0); +} + +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++) { + float gaussian = Gaussian(i); + total_gaussian += gaussian; + total += gaussian * SampleWithBorder(v_texture_coords + + v_blur_direction * i / v_texture_size); + } + frag_color = total / total_gaussian; +} diff --git a/impeller/entity/shaders/gaussian_blur.vert b/impeller/entity/shaders/gaussian_blur.vert new file mode 100644 index 0000000000000..8d7d1db931de1 --- /dev/null +++ b/impeller/entity/shaders/gaussian_blur.vert @@ -0,0 +1,27 @@ +// 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. + +uniform FrameInfo { + mat4 mvp; + vec2 texture_size; + vec2 blur_direction; + float blur_radius; +} +frame_info; + +in vec2 vertices; +in vec2 texture_coords; + +out vec2 v_texture_coords; +out vec2 v_texture_size; +out vec2 v_blur_direction; +out float v_blur_radius; + +void main() { + gl_Position = frame_info.mvp * vec4(vertices, 0.0, 1.0); + v_texture_coords = texture_coords; + v_texture_size = frame_info.texture_size; + v_blur_direction = frame_info.blur_direction; + v_blur_radius = frame_info.blur_radius; +} diff --git a/impeller/geometry/point.h b/impeller/geometry/point.h index c334993c2de68..194bc1815d1df 100644 --- a/impeller/geometry/point.h +++ b/impeller/geometry/point.h @@ -269,6 +269,7 @@ constexpr TPoint operator/(const TSize& s, const TPoint& p) { using Point = TPoint; using IPoint = TPoint; +using Vector2 = Point; } // namespace impeller