Skip to content

Commit

Permalink
Add blur styles to gaussian blur (flutter#107)
Browse files Browse the repository at this point in the history
  • Loading branch information
bdero authored and dnfield committed Apr 27, 2022
1 parent 31812de commit 78eb5d6
Show file tree
Hide file tree
Showing 7 changed files with 168 additions and 48 deletions.
16 changes: 11 additions & 5 deletions impeller/entity/contents/filters/filter_contents.cc
Original file line number Diff line number Diff line change
Expand Up @@ -61,20 +61,26 @@ std::shared_ptr<FilterContents> FilterContents::MakeBlend(

std::shared_ptr<FilterContents> FilterContents::MakeDirectionalGaussianBlur(
FilterInput::Ref input,
Vector2 blur_vector) {
Vector2 blur_vector,
BlurStyle blur_style,
FilterInput::Ref source_override) {
auto blur = std::make_shared<DirectionalGaussianBlurFilterContents>();
blur->SetInputs({input});
blur->SetBlurVector(blur_vector);
blur->SetBlurStyle(blur_style);
blur->SetSourceOverride(source_override);
return blur;
}

std::shared_ptr<FilterContents> FilterContents::MakeGaussianBlur(
FilterInput::Ref input,
Scalar sigma_x,
Scalar sigma_y) {
auto x_blur = MakeDirectionalGaussianBlur(input, Point(sigma_x, 0));
auto y_blur =
MakeDirectionalGaussianBlur(FilterInput::Make(x_blur), Point(0, sigma_y));
Scalar sigma_y,
BlurStyle blur_style) {
auto x_blur =
MakeDirectionalGaussianBlur(input, Point(sigma_x, 0), BlurStyle::kNormal);
auto y_blur = MakeDirectionalGaussianBlur(
FilterInput::Make(x_blur), Point(0, sigma_y), blur_style, input);
return y_blur;
}

Expand Down
22 changes: 19 additions & 3 deletions impeller/entity/contents/filters/filter_contents.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,31 @@ class Pipeline;

class FilterContents : public Contents {
public:
enum class BlurStyle {
/// Blurred inside and outside.
kNormal,
/// Solid inside, blurred outside.
kSolid,
/// Nothing inside, blurred outside.
kOuter,
/// Blurred inside, nothing outside.
kInner,
};

static std::shared_ptr<FilterContents> MakeBlend(Entity::BlendMode blend_mode,
FilterInput::Vector inputs);

static std::shared_ptr<FilterContents> MakeDirectionalGaussianBlur(
FilterInput::Ref input,
Vector2 blur_vector);
Vector2 blur_vector,
BlurStyle blur_style = BlurStyle::kNormal,
FilterInput::Ref alpha_mask = nullptr);

static std::shared_ptr<FilterContents>
MakeGaussianBlur(FilterInput::Ref input, Scalar sigma_x, Scalar sigma_y);
static std::shared_ptr<FilterContents> MakeGaussianBlur(
FilterInput::Ref input,
Scalar sigma_x,
Scalar sigma_y,
BlurStyle blur_style = BlurStyle::kNormal);

FilterContents();

Expand Down
91 changes: 70 additions & 21 deletions impeller/entity/contents/filters/gaussian_blur_filter_contents.cc
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,38 @@ void DirectionalGaussianBlurFilterContents::SetBlurVector(Vector2 blur_vector) {
blur_vector_ = blur_vector;
}

void DirectionalGaussianBlurFilterContents::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;
}
}

void DirectionalGaussianBlurFilterContents::SetSourceOverride(
FilterInput::Ref alpha_mask) {
source_override_ = alpha_mask;
}

bool DirectionalGaussianBlurFilterContents::RenderFilter(
const FilterInput::Vector& inputs,
const ContentContext& renderer,
Expand All @@ -45,43 +77,59 @@ bool DirectionalGaussianBlurFilterContents::RenderFilter(

auto& host_buffer = pass.GetTransientsBuffer();

// 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 the destination rect (like we do in
// BlendFilterContents).

auto input = inputs[0]->GetSnapshot(renderer, entity);
auto input_size = input->texture->GetSize();
auto input_bounds = inputs[0]->GetBounds(entity);
auto filter_bounds = GetBounds(entity);

auto transformed_blur =
entity.GetTransformation().TransformDirection(blur_vector_);

auto uv_offset = transformed_blur.Abs() / input_size;
// LTRB
Scalar uv[4] = {
-uv_offset.x,
-uv_offset.y,
1 + uv_offset.x,
1 + uv_offset.y,
(filter_bounds.GetLeft() - input_bounds.GetLeft()) /
input_bounds.size.width,
(filter_bounds.GetTop() - input_bounds.GetTop()) /
input_bounds.size.height,
1 + (filter_bounds.GetRight() - input_bounds.GetRight()) /
input_bounds.size.width,
1 + (filter_bounds.GetBottom() - input_bounds.GetBottom()) /
input_bounds.size.height,
};

auto source = source_override_ ? source_override_ : inputs[0];
auto source_texture = source->GetSnapshot(renderer, entity);
auto source_bounds = source->GetBounds(entity);

// LTRB
Scalar uv_src[4] = {
(filter_bounds.GetLeft() - source_bounds.GetLeft()) /
source_bounds.size.width,
(filter_bounds.GetTop() - source_bounds.GetTop()) /
source_bounds.size.height,
1 + (filter_bounds.GetRight() - source_bounds.GetRight()) /
source_bounds.size.width,
1 + (filter_bounds.GetBottom() - source_bounds.GetBottom()) /
source_bounds.size.height,
};

VertexBufferBuilder<VS::PerVertexData> vtx_builder;
auto size = pass.GetRenderTargetSize();
vtx_builder.AddVertices({
{Point(0, 0), Point(uv[0], uv[1])},
{Point(size.width, 0), Point(uv[2], uv[1])},
{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])},
{Point(0, 0), Point(uv[0], uv[1]), Point(uv_src[0], uv_src[1])},
{Point(1, 0), Point(uv[2], uv[1]), Point(uv_src[2], uv_src[1])},
{Point(1, 1), Point(uv[2], uv[3]), Point(uv_src[2], uv_src[3])},
{Point(0, 0), Point(uv[0], uv[1]), Point(uv_src[0], uv_src[1])},
{Point(1, 1), Point(uv[2], uv[3]), Point(uv_src[2], uv_src[3])},
{Point(0, 1), Point(uv[0], uv[3]), Point(uv_src[0], uv_src[3])},
});
auto vtx_buffer = vtx_builder.CreateVertexBuffer(host_buffer);

VS::FrameInfo frame_info;
frame_info.texture_size = Point(input_size);
frame_info.texture_size = Point(input_bounds.size);
frame_info.blur_radius = transformed_blur.GetLength();
frame_info.blur_direction = transformed_blur.Normalize();
frame_info.src_factor = src_color_factor_;
frame_info.inner_blur_factor = inner_blur_factor_;
frame_info.outer_blur_factor = outer_blur_factor_;

auto sampler = renderer.GetContext()->GetSamplerLibrary()->GetSampler({});

Expand All @@ -93,8 +141,9 @@ bool DirectionalGaussianBlurFilterContents::RenderFilter(
cmd.BindVertices(vtx_buffer);

FS::BindTextureSampler(cmd, input->texture, sampler);
FS::BindAlphaMaskSampler(cmd, source_texture->texture, sampler);

frame_info.mvp = Matrix::MakeOrthographic(size);
frame_info.mvp = Matrix::MakeOrthographic(ISize(1, 1));
auto uniform_view = host_buffer.EmplaceUniform(frame_info);
VS::BindFrameInfo(cmd, uniform_view);

Expand Down
11 changes: 11 additions & 0 deletions impeller/entity/contents/filters/gaussian_blur_filter_contents.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

#pragma once

#include <memory>
#include <optional>
#include "impeller/entity/contents/filters/filter_contents.h"
#include "impeller/entity/contents/filters/filter_input.h"
#include "impeller/geometry/matrix.h"
Expand All @@ -18,6 +20,10 @@ class DirectionalGaussianBlurFilterContents final : public FilterContents {

void SetBlurVector(Vector2 blur_vector);

void SetBlurStyle(BlurStyle blur_style);

void SetSourceOverride(FilterInput::Ref alpha_mask);

// |Contents|
Rect GetBounds(const Entity& entity) const override;

Expand All @@ -30,6 +36,11 @@ class DirectionalGaussianBlurFilterContents final : public FilterContents {
const Rect& bounds) const override;

Vector2 blur_vector_;
BlurStyle blur_style_ = BlurStyle::kNormal;
bool src_color_factor_ = false;
bool inner_blur_factor_ = true;
bool outer_blur_factor_ = true;
FilterInput::Ref source_override_;

FML_DISALLOW_COPY_AND_ASSIGN(DirectionalGaussianBlurFilterContents);
};
Expand Down
33 changes: 23 additions & 10 deletions impeller/entity/entity_unittests.cc
Original file line number Diff line number Diff line change
Expand Up @@ -689,11 +689,18 @@ TEST_F(EntityTest, GaussianBlurFilter) {
auto callback = [&](ContentContext& context, RenderPass& pass) -> bool {
if (first_frame) {
first_frame = false;
ImGui::SetNextWindowSize({500, 190});
ImGui::SetNextWindowSize({500, 220});
ImGui::SetNextWindowPos({300, 550});
}

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 float blur_amount[2] = {20, 20};
static int selected_blur_style = 0;
static Color cover_color(1, 0, 0, 0.2);
static Color bounds_color(0, 1, 0, 0.1);
static float offset[2] = {500, 400};
Expand All @@ -702,21 +709,27 @@ TEST_F(EntityTest, GaussianBlurFilter) {
static float skew[2] = {0, 0};

ImGui::Begin("Controls");
ImGui::SliderFloat2("Blur", &blur_amount[0], 0, 200);
ImGui::ColorEdit4("Cover color", reinterpret_cast<float*>(&cover_color));
ImGui::ColorEdit4("Bounds color", reinterpret_cast<float*>(&bounds_color));
ImGui::SliderFloat2("Translation", &offset[0], 0,
pass.GetRenderTargetSize().width);
ImGui::SliderFloat("Rotation", &rotation, 0, kPi * 2);
ImGui::SliderFloat2("Scale", &scale[0], 0, 3);
ImGui::SliderFloat2("Skew", &skew[0], -3, 3);
{
ImGui::SliderFloat2("Blur", &blur_amount[0], 0, 200);
ImGui::Combo("Blur style", &selected_blur_style, blur_style_names,
sizeof(blur_style_names) / sizeof(char*));
ImGui::ColorEdit4("Cover color", reinterpret_cast<float*>(&cover_color));
ImGui::ColorEdit4("Bounds color",
reinterpret_cast<float*>(&bounds_color));
ImGui::SliderFloat2("Translation", &offset[0], 0,
pass.GetRenderTargetSize().width);
ImGui::SliderFloat("Rotation", &rotation, 0, kPi * 2);
ImGui::SliderFloat2("Scale", &scale[0], 0, 3);
ImGui::SliderFloat2("Skew", &skew[0], -3, 3);
}
ImGui::End();

auto blend = FilterContents::MakeBlend(
Entity::BlendMode::kPlus, FilterInput::Make({boston, bridge, bridge}));

auto blur = FilterContents::MakeGaussianBlur(
FilterInput::Make(blend), blur_amount[0], blur_amount[1]);
FilterInput::Make(blend), blur_amount[0], blur_amount[1],
blur_styles[selected_blur_style]);

ISize input_size = boston->GetSize();
auto rect = Rect(-Point(input_size) / 2, Size(input_size));
Expand Down
29 changes: 20 additions & 9 deletions impeller/entity/shaders/gaussian_blur.frag
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,21 @@
// 1D (directional) gaussian blur.
//
// Paths for future optimization:
// * Remove the uv bounds check branch in SampleColor by adding optional
// * Remove the uv bounds multiplier 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;
uniform sampler2D alpha_mask_sampler;

in vec2 v_texture_coords;
in vec2 v_src_texture_coords;
in vec2 v_texture_size;
in vec2 v_blur_direction;
in float v_blur_radius;
in float v_src_factor;
in float v_inner_blur_factor;
in float v_outer_blur_factor;

out vec4 frag_color;

Expand All @@ -27,21 +32,27 @@ float Gaussian(float x) {
}

// 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);
vec4 SampleWithBorder(sampler2D tex, vec2 uv) {
float within_bounds = float(uv.x >= 0 && uv.y >= 0 && uv.x < 1 && uv.y < 1);
return texture(tex, uv) * within_bounds;
}

void main() {
vec4 total = vec4(0);
float total_gaussian = 0;
vec2 blur_uv_offset = v_blur_direction / v_texture_size;
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);
total += gaussian * SampleWithBorder(texture_sampler,
v_texture_coords + blur_uv_offset * i);
}
frag_color = total / total_gaussian;

vec4 blur_color = total / total_gaussian;

vec4 src_color = SampleWithBorder(alpha_mask_sampler, v_src_texture_coords);
float blur_factor = v_inner_blur_factor * float(src_color.a > 0) +
v_outer_blur_factor * float(src_color.a == 0);

frag_color = blur_color * blur_factor + src_color * v_src_factor;
}
14 changes: 14 additions & 0 deletions impeller/entity/shaders/gaussian_blur.vert
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,37 @@
uniform FrameInfo {
mat4 mvp;
vec2 texture_size;

vec2 blur_direction;
float blur_radius;

float src_factor;
float inner_blur_factor;
float outer_blur_factor;
}
frame_info;

in vec2 vertices;
in vec2 texture_coords;
in vec2 src_texture_coords;

out vec2 v_texture_coords;
out vec2 v_src_texture_coords;
out vec2 v_texture_size;
out vec2 v_blur_direction;
out float v_blur_radius;
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_src_texture_coords = src_texture_coords;
v_texture_size = frame_info.texture_size;
v_blur_direction = frame_info.blur_direction;
v_blur_radius = frame_info.blur_radius;
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;
}

0 comments on commit 78eb5d6

Please sign in to comment.