From a08cb8b4fe64fffc2eb59b1c3b17b9f08a0f10eb Mon Sep 17 00:00:00 2001 From: Brandon DeRosier Date: Thu, 14 Apr 2022 16:33:14 -0700 Subject: [PATCH] Border mask blur (#132) --- impeller/entity/BUILD.gn | 4 + impeller/entity/contents/content_context.cc | 2 + impeller/entity/contents/content_context.h | 10 ++ .../border_mask_blur_filter_contents.cc | 131 ++++++++++++++++++ .../border_mask_blur_filter_contents.h | 44 ++++++ .../contents/filters/filter_contents.cc | 13 ++ .../entity/contents/filters/filter_contents.h | 6 + .../filters/gaussian_blur_filter_contents.h | 1 - impeller/entity/entity_unittests.cc | 15 +- impeller/entity/shaders/border_mask_blur.frag | 60 ++++++++ impeller/entity/shaders/border_mask_blur.vert | 32 +++++ impeller/geometry/matrix.h | 6 + 12 files changed, 320 insertions(+), 4 deletions(-) create mode 100644 impeller/entity/contents/filters/border_mask_blur_filter_contents.cc create mode 100644 impeller/entity/contents/filters/border_mask_blur_filter_contents.h create mode 100644 impeller/entity/shaders/border_mask_blur.frag create mode 100644 impeller/entity/shaders/border_mask_blur.vert diff --git a/impeller/entity/BUILD.gn b/impeller/entity/BUILD.gn index 51c2766dbc99b..3add3ae8c27f7 100644 --- a/impeller/entity/BUILD.gn +++ b/impeller/entity/BUILD.gn @@ -20,6 +20,8 @@ impeller_shaders("entity_shaders") { "shaders/texture_blend_screen.vert", "shaders/gaussian_blur.frag", "shaders/gaussian_blur.vert", + "shaders/border_mask_blur.frag", + "shaders/border_mask_blur.vert", "shaders/texture_fill.frag", "shaders/texture_fill.vert", "shaders/glyph_atlas.frag", @@ -45,6 +47,8 @@ impeller_component("entity") { "contents/filters/filter_input.h", "contents/filters/gaussian_blur_filter_contents.cc", "contents/filters/gaussian_blur_filter_contents.h", + "contents/filters/border_mask_blur_filter_contents.cc", + "contents/filters/border_mask_blur_filter_contents.h", "contents/linear_gradient_contents.cc", "contents/linear_gradient_contents.h", "contents/snapshot.cc", diff --git a/impeller/entity/contents/content_context.cc b/impeller/entity/contents/content_context.cc index 016e0b3d57674..f7a584f4af126 100644 --- a/impeller/entity/contents/content_context.cc +++ b/impeller/entity/contents/content_context.cc @@ -29,6 +29,8 @@ ContentContext::ContentContext(std::shared_ptr context) texture_pipelines_[{}] = std::make_unique(*context_); gaussian_blur_pipelines_[{}] = std::make_unique(*context_); + border_mask_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 b1194cc9cd062..9898b217add98 100644 --- a/impeller/entity/contents/content_context.h +++ b/impeller/entity/contents/content_context.h @@ -11,6 +11,8 @@ #include "flutter/fml/macros.h" #include "fml/logging.h" #include "impeller/base/validation.h" +#include "impeller/entity/border_mask_blur.frag.h" +#include "impeller/entity/border_mask_blur.vert.h" #include "impeller/entity/entity.h" #include "impeller/entity/gaussian_blur.frag.h" #include "impeller/entity/gaussian_blur.vert.h" @@ -44,6 +46,8 @@ using TexturePipeline = PipelineT; using GaussianBlurPipeline = PipelineT; +using BorderMaskBlurPipeline = + PipelineT; using SolidStrokePipeline = PipelineT; using GlyphAtlasPipeline = @@ -114,6 +118,11 @@ class ContentContext { return GetPipeline(gaussian_blur_pipelines_, opts); } + std::shared_ptr GetBorderMaskBlurPipeline( + ContentContextOptions opts) const { + return GetPipeline(border_mask_blur_pipelines_, opts); + } + std::shared_ptr GetSolidStrokePipeline( ContentContextOptions opts) const { return GetPipeline(solid_stroke_pipelines_, opts); @@ -156,6 +165,7 @@ class ContentContext { mutable Variants texture_blend_screen_pipelines_; mutable Variants texture_pipelines_; mutable Variants gaussian_blur_pipelines_; + mutable Variants border_mask_blur_pipelines_; mutable Variants solid_stroke_pipelines_; mutable Variants clip_pipelines_; mutable Variants glyph_atlas_pipelines_; diff --git a/impeller/entity/contents/filters/border_mask_blur_filter_contents.cc b/impeller/entity/contents/filters/border_mask_blur_filter_contents.cc new file mode 100644 index 0000000000000..db1d5272acce1 --- /dev/null +++ b/impeller/entity/contents/filters/border_mask_blur_filter_contents.cc @@ -0,0 +1,131 @@ +// 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/border_mask_blur_filter_contents.h" +#include "impeller/entity/contents/content_context.h" + +#include "impeller/entity/contents/contents.h" +#include "impeller/renderer/render_pass.h" +#include "impeller/renderer/sampler_library.h" + +namespace impeller { + +BorderMaskBlurFilterContents::BorderMaskBlurFilterContents() = default; + +BorderMaskBlurFilterContents::~BorderMaskBlurFilterContents() = default; + +void BorderMaskBlurFilterContents::SetSigma(Sigma sigma_x, Sigma sigma_y) { + sigma_x_ = sigma_x; + sigma_y_ = sigma_y; +} + +void BorderMaskBlurFilterContents::SetBlurStyle(BlurStyle blur_style) { + blur_style_ = blur_style; + + switch (blur_style) { + case FilterContents::BlurStyle::kNormal: + src_color_factor_ = false; + inner_blur_factor_ = true; + outer_blur_factor_ = true; + break; + case FilterContents::BlurStyle::kSolid: + src_color_factor_ = true; + inner_blur_factor_ = false; + outer_blur_factor_ = true; + break; + case FilterContents::BlurStyle::kOuter: + src_color_factor_ = false; + inner_blur_factor_ = false; + outer_blur_factor_ = true; + break; + case FilterContents::BlurStyle::kInner: + src_color_factor_ = false; + inner_blur_factor_ = true; + outer_blur_factor_ = false; + break; + } +} + +bool BorderMaskBlurFilterContents::RenderFilter( + const FilterInput::Vector& inputs, + const ContentContext& renderer, + const Entity& entity, + RenderPass& pass, + const Rect& coverage) const { + if (inputs.empty()) { + return true; + } + + using VS = BorderMaskBlurPipeline::VertexShader; + using FS = BorderMaskBlurPipeline::FragmentShader; + + auto& host_buffer = pass.GetTransientsBuffer(); + + auto input_snapshot = inputs[0]->GetSnapshot(renderer, entity); + if (!input_snapshot.has_value()) { + return true; + } + auto maybe_input_uvs = input_snapshot->GetCoverageUVs(coverage); + if (!maybe_input_uvs.has_value()) { + return true; + } + auto input_uvs = maybe_input_uvs.value(); + + VertexBufferBuilder vtx_builder; + vtx_builder.AddVertices({ + {Point(0, 0), input_uvs[0]}, + {Point(1, 0), input_uvs[1]}, + {Point(1, 1), input_uvs[3]}, + {Point(0, 0), input_uvs[0]}, + {Point(1, 1), input_uvs[3]}, + {Point(0, 1), input_uvs[2]}, + }); + auto vtx_buffer = vtx_builder.CreateVertexBuffer(host_buffer); + + Command cmd; + cmd.label = "Border Mask Blur Filter"; + auto options = OptionsFromPass(pass); + options.blend_mode = Entity::BlendMode::kSource; + cmd.pipeline = renderer.GetBorderMaskBlurPipeline(options); + cmd.BindVertices(vtx_buffer); + + VS::FrameInfo frame_info; + frame_info.mvp = Matrix::MakeOrthographic(ISize(1, 1)); + auto scale = entity.GetTransformation().GetScale(); + frame_info.sigma_uv = Vector2(scale.x, scale.y) * + Vector2(sigma_x_.sigma, sigma_y_.sigma).Abs() / + input_snapshot->texture->GetSize(); + frame_info.src_factor = src_color_factor_; + frame_info.inner_blur_factor = inner_blur_factor_; + frame_info.outer_blur_factor = outer_blur_factor_; + auto uniform_view = host_buffer.EmplaceUniform(frame_info); + VS::BindFrameInfo(cmd, uniform_view); + + auto sampler = renderer.GetContext()->GetSamplerLibrary()->GetSampler({}); + FS::BindTextureSampler(cmd, input_snapshot->texture, sampler); + + return pass.AddCommand(std::move(cmd)); +} + +std::optional BorderMaskBlurFilterContents::GetCoverage( + const Entity& entity) const { + auto coverage = FilterContents::GetCoverage(entity); + if (!coverage.has_value()) { + return std::nullopt; + } + + // Technically this works with all of our current filters, but this should be + // using the input[0] transform, not the entity transform! + // See: https://github.com/flutter/impeller/pull/130#issuecomment-1098892423 + auto transformed_blur_vector = + entity.GetTransformation() + .TransformDirection( + Vector2(Radius{sigma_x_}.radius, Radius{sigma_y_}.radius)) + .Abs(); + auto extent = coverage->size + transformed_blur_vector * 2; + return Rect(coverage->origin - transformed_blur_vector, + Size(extent.x, extent.y)); +} + +} // namespace impeller diff --git a/impeller/entity/contents/filters/border_mask_blur_filter_contents.h b/impeller/entity/contents/filters/border_mask_blur_filter_contents.h new file mode 100644 index 0000000000000..dc327dc3f5edb --- /dev/null +++ b/impeller/entity/contents/filters/border_mask_blur_filter_contents.h @@ -0,0 +1,44 @@ +// 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 "impeller/entity/contents/filters/filter_contents.h" +#include "impeller/entity/contents/filters/filter_input.h" + +namespace impeller { + +class BorderMaskBlurFilterContents final : public FilterContents { + public: + BorderMaskBlurFilterContents(); + + ~BorderMaskBlurFilterContents() override; + + void SetSigma(Sigma sigma_x, Sigma sigma_y); + + void SetBlurStyle(BlurStyle blur_style); + + // |Contents| + std::optional GetCoverage(const Entity& entity) const override; + + private: + // |FilterContents| + bool RenderFilter(const FilterInput::Vector& input_textures, + const ContentContext& renderer, + const Entity& entity, + RenderPass& pass, + const Rect& coverage) const override; + Sigma sigma_x_; + Sigma sigma_y_; + BlurStyle blur_style_ = BlurStyle::kNormal; + bool src_color_factor_ = false; + bool inner_blur_factor_ = true; + bool outer_blur_factor_ = true; + + FML_DISALLOW_COPY_AND_ASSIGN(BorderMaskBlurFilterContents); +}; + +} // namespace impeller diff --git a/impeller/entity/contents/filters/filter_contents.cc b/impeller/entity/contents/filters/filter_contents.cc index 5b820b09a5188..790faf4a8bb16 100644 --- a/impeller/entity/contents/filters/filter_contents.cc +++ b/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/border_mask_blur_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" @@ -88,6 +89,18 @@ std::shared_ptr FilterContents::MakeGaussianBlur( return y_blur; } +std::shared_ptr FilterContents::MakeBorderMaskBlur( + FilterInput::Ref input, + Sigma sigma_x, + Sigma sigma_y, + BlurStyle blur_style) { + auto filter = std::make_shared(); + filter->SetInputs({input}); + filter->SetSigma(sigma_x, sigma_y); + filter->SetBlurStyle(blur_style); + return filter; +} + FilterContents::FilterContents() = default; FilterContents::~FilterContents() = default; diff --git a/impeller/entity/contents/filters/filter_contents.h b/impeller/entity/contents/filters/filter_contents.h index 236bf1193b836..0917d5532b54d 100644 --- a/impeller/entity/contents/filters/filter_contents.h +++ b/impeller/entity/contents/filters/filter_contents.h @@ -96,6 +96,12 @@ class FilterContents : public Contents { Sigma sigma_y, BlurStyle blur_style = BlurStyle::kNormal); + static std::shared_ptr MakeBorderMaskBlur( + FilterInput::Ref input, + Sigma sigma_x, + Sigma sigma_y, + BlurStyle blur_style = BlurStyle::kNormal); + FilterContents(); ~FilterContents() override; diff --git a/impeller/entity/contents/filters/gaussian_blur_filter_contents.h b/impeller/entity/contents/filters/gaussian_blur_filter_contents.h index cc686c0459c2b..b39ec2ceb6d5b 100644 --- a/impeller/entity/contents/filters/gaussian_blur_filter_contents.h +++ b/impeller/entity/contents/filters/gaussian_blur_filter_contents.h @@ -8,7 +8,6 @@ #include #include "impeller/entity/contents/filters/filter_contents.h" #include "impeller/entity/contents/filters/filter_input.h" -#include "impeller/geometry/matrix.h" namespace impeller { diff --git a/impeller/entity/entity_unittests.cc b/impeller/entity/entity_unittests.cc index b2ce20529d1b3..2978d2dd8018a 100644 --- a/impeller/entity/entity_unittests.cc +++ b/impeller/entity/entity_unittests.cc @@ -698,16 +698,18 @@ TEST_F(EntityTest, GaussianBlurFilter) { auto callback = [&](ContentContext& context, RenderPass& pass) -> bool { if (first_frame) { first_frame = false; - ImGui::SetNextWindowSize({500, 220}); - ImGui::SetNextWindowPos({300, 550}); + ImGui::SetNextWindowSize({500, 250}); + ImGui::SetNextWindowPos({300, 500}); } + const char* blur_type_names[] = {"Image blur", "Mask blur"}; const char* blur_style_names[] = {"Normal", "Solid", "Outer", "Inner"}; const FilterContents::BlurStyle blur_styles[] = { FilterContents::BlurStyle::kNormal, FilterContents::BlurStyle::kSolid, FilterContents::BlurStyle::kOuter, FilterContents::BlurStyle::kInner}; // UI state. + static int selected_blur_type = 0; static float blur_amount[2] = {20, 20}; static int selected_blur_style = 0; static Color cover_color(1, 0, 0, 0.2); @@ -719,6 +721,8 @@ TEST_F(EntityTest, GaussianBlurFilter) { ImGui::Begin("Controls"); { + ImGui::Combo("Blur type", &selected_blur_type, blur_type_names, + sizeof(blur_type_names) / sizeof(char*)); ImGui::SliderFloat2("Blur", &blur_amount[0], 0, 200); ImGui::Combo("Blur style", &selected_blur_style, blur_style_names, sizeof(blur_style_names) / sizeof(char*)); @@ -742,6 +746,11 @@ TEST_F(EntityTest, GaussianBlurFilter) { FilterContents::Sigma{blur_amount[1]}, blur_styles[selected_blur_style]); + auto mask_blur = FilterContents::MakeBorderMaskBlur( + FilterInput::Make(boston), FilterContents::Sigma{blur_amount[0]}, + FilterContents::Sigma{blur_amount[1]}, + blur_styles[selected_blur_style]); + ISize input_size = boston->GetSize(); auto rect = Rect(-Point(input_size) / 2, Size(input_size)); auto ctm = Matrix::MakeTranslation(Vector3(offset[0], offset[1])) * @@ -749,7 +758,7 @@ TEST_F(EntityTest, GaussianBlurFilter) { Matrix::MakeScale(Vector2(scale[0], scale[1])) * Matrix::MakeSkew(skew[0], skew[1]); - auto target_contents = blur; + auto target_contents = selected_blur_type == 0 ? blur : mask_blur; Entity entity; entity.SetPath(PathBuilder{}.AddRect(rect).TakePath()); diff --git a/impeller/entity/shaders/border_mask_blur.frag b/impeller/entity/shaders/border_mask_blur.frag new file mode 100644 index 0000000000000..365d9eaa10284 --- /dev/null +++ b/impeller/entity/shaders/border_mask_blur.frag @@ -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. + +// Constant time mask blur for image borders. +// +// This mask blur extends the geometry of the source image (with clamp border +// sampling) and applies a Gaussian blur to the alpha mask at the edges. +// +// The blur itself works by mapping the Gaussian distribution's indefinite +// integral (using an erf approximation) to the 4 edges of the UV rectangle and +// multiplying them. + +uniform sampler2D texture_sampler; + +in vec2 v_texture_coords; +in vec2 v_sigma_uv; +in float v_src_factor; +in float v_inner_blur_factor; +in float v_outer_blur_factor; + +out vec4 frag_color; + +// Abramowitz and Stegun erf approximation. +float erf(float x) { + float a = abs(x); + // 0.278393*x + 0.230389*x^2 + 0.078108*x^4 + 1 + float b = (0.278393 + (0.230389 + 0.078108 * a * a) * a) * a + 1.0; + return sign(x) * (1 - 1 / (b * b * b * b)); +} + +const float kHalfSqrtTwo = 0.70710678118; + +// Indefinite integral of the Gaussian function (with constant range 0->1). +float GaussianIntegral(float x, float sigma) { + return 0.5 + 0.5 * erf(x * (kHalfSqrtTwo / sigma)); +} + +float BoxBlurMask(vec2 uv) { + // LTRB + return GaussianIntegral(uv.x, v_sigma_uv.x) * // + GaussianIntegral(uv.y, v_sigma_uv.y) * // + GaussianIntegral(1 - uv.x, v_sigma_uv.x) * // + GaussianIntegral(1 - uv.y, v_sigma_uv.y); +} + +void main() { + vec4 image_color = texture(texture_sampler, v_texture_coords); + float blur_factor = BoxBlurMask(v_texture_coords); + + float within_bounds = + float(v_texture_coords.x >= 0 && v_texture_coords.y >= 0 && + v_texture_coords.x < 1 && v_texture_coords.y < 1); + float inner_factor = + (v_inner_blur_factor * blur_factor + v_src_factor) * within_bounds; + float outer_factor = v_outer_blur_factor * blur_factor * (1 - within_bounds); + + float mask_factor = inner_factor + outer_factor; + frag_color = image_color * mask_factor; +} diff --git a/impeller/entity/shaders/border_mask_blur.vert b/impeller/entity/shaders/border_mask_blur.vert new file mode 100644 index 0000000000000..3851a60aeb7fe --- /dev/null +++ b/impeller/entity/shaders/border_mask_blur.vert @@ -0,0 +1,32 @@ +// 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 sigma_uv; + + float src_factor; + float inner_blur_factor; + float outer_blur_factor; +} +frame_info; + +in vec2 vertices; +in vec2 texture_coords; + +out vec2 v_texture_coords; +out vec2 v_sigma_uv; +out float v_src_factor; +out float v_inner_blur_factor; +out float v_outer_blur_factor; + +void main() { + gl_Position = frame_info.mvp * vec4(vertices, 0.0, 1.0); + v_texture_coords = texture_coords; + v_sigma_uv = frame_info.sigma_uv; + v_src_factor = frame_info.src_factor; + v_inner_blur_factor = frame_info.inner_blur_factor; + v_outer_blur_factor = frame_info.outer_blur_factor; +} diff --git a/impeller/geometry/matrix.h b/impeller/geometry/matrix.h index 8938816fa9c72..0284507e58d8e 100644 --- a/impeller/geometry/matrix.h +++ b/impeller/geometry/matrix.h @@ -236,6 +236,12 @@ struct Matrix { Scalar GetMaxBasisLength() const; + constexpr Vector3 GetScale() const { + return Vector3(Vector3(m[0], m[1], m[2]).Length(), + Vector3(m[4], m[5], m[6]).Length(), + Vector3(m[8], m[9], m[10]).Length()); + } + constexpr bool IsAffine() const { return (m[2] == 0 && m[3] == 0 && m[6] == 0 && m[7] == 0 && m[8] == 0 && m[9] == 0 && m[10] == 1 && m[11] == 0 && m[14] == 0 && m[15] == 1);