Skip to content

Commit

Permalink
Add directional + 2D gaussian blur (flutter#84)
Browse files Browse the repository at this point in the history
  • Loading branch information
bdero authored and dnfield committed Apr 27, 2022
1 parent 396ef7d commit 503dff6
Show file tree
Hide file tree
Showing 9 changed files with 313 additions and 9 deletions.
2 changes: 2 additions & 0 deletions impeller/entity/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
2 changes: 2 additions & 0 deletions impeller/entity/contents/content_context.cc
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ ContentContext::ContentContext(std::shared_ptr<Context> context)
texture_blend_screen_pipelines_[{}] =
std::make_unique<TextureBlendScreenPipeline>(*context_);
texture_pipelines_[{}] = std::make_unique<TexturePipeline>(*context_);
gaussian_blur_pipelines_[{}] =
std::make_unique<GaussianBlurPipeline>(*context_);
solid_stroke_pipelines_[{}] =
std::make_unique<SolidStrokePipeline>(*context_);
glyph_atlas_pipelines_[{}] = std::make_unique<GlyphAtlasPipeline>(*context_);
Expand Down
18 changes: 14 additions & 4 deletions impeller/entity/contents/content_context.h
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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 {

Expand All @@ -40,6 +42,8 @@ using TextureBlendScreenPipeline =
PipelineT<TextureBlendScreenVertexShader, TextureBlendScreenFragmentShader>;
using TexturePipeline =
PipelineT<TextureFillVertexShader, TextureFillFragmentShader>;
using GaussianBlurPipeline =
PipelineT<GaussianBlurVertexShader, GaussianBlurFragmentShader>;
using SolidStrokePipeline =
PipelineT<SolidStrokeVertexShader, SolidStrokeFragmentShader>;
using GlyphAtlasPipeline =
Expand Down Expand Up @@ -100,6 +104,11 @@ class ContentContext {
return GetPipeline(texture_pipelines_, opts);
}

std::shared_ptr<Pipeline> GetGaussianBlurPipeline(
ContentContextOptions opts) const {
return GetPipeline(gaussian_blur_pipelines_, opts);
}

std::shared_ptr<Pipeline> GetSolidStrokePipeline(
ContentContextOptions opts) const {
return GetPipeline(solid_stroke_pipelines_, opts);
Expand Down Expand Up @@ -138,6 +147,7 @@ class ContentContext {
mutable Variants<TextureBlendPipeline> texture_blend_pipelines_;
mutable Variants<TextureBlendScreenPipeline> texture_blend_screen_pipelines_;
mutable Variants<TexturePipeline> texture_pipelines_;
mutable Variants<GaussianBlurPipeline> gaussian_blur_pipelines_;
mutable Variants<SolidStrokePipeline> solid_stroke_pipelines_;
mutable Variants<ClipPipeline> clip_pipelines_;
mutable Variants<ClipPipeline> clip_restoration_pipelines_;
Expand Down
129 changes: 125 additions & 4 deletions impeller/entity/contents/filter_contents.cc
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

#include "filter_contents.h"

#include <algorithm>
#include <cmath>
#include <memory>
#include <optional>
#include <variant>
Expand All @@ -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 {
Expand Down Expand Up @@ -57,6 +61,28 @@ std::shared_ptr<FilterContents> FilterContents::MakeBlend(
FML_UNREACHABLE();
}

std::shared_ptr<FilterContents> FilterContents::MakeDirectionalGaussianBlur(
InputVariant input_texture,
Scalar radius,
Vector2 direction,
bool clip_border) {
auto blur = std::make_shared<DirectionalGaussianBlurFilterContents>();
blur->SetInputTextures({input_texture});
blur->SetRadius(radius);
blur->SetDirection(direction);
blur->SetClipBorder(clip_border);
return blur;
}

std::shared_ptr<FilterContents> 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;
Expand Down Expand Up @@ -90,7 +116,7 @@ std::optional<std::shared_ptr<Texture>> 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;
}
Expand Down Expand Up @@ -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<std::shared_ptr<FilterContents>>(&input_textures_[0])) {
return filter->get()->GetOutputSize();
std::get_if<std::shared_ptr<FilterContents>>(&input_textures[0])) {
return filter->get()->GetOutputSize(input_textures);
}

if (auto texture =
std::get_if<std::shared_ptr<Texture>>(&input_textures_[0])) {
std::get_if<std::shared_ptr<Texture>>(&input_textures[0])) {
return texture->get()->GetSize();
}

Expand Down Expand Up @@ -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<std::shared_ptr<Texture>>& 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<VS::PerVertexData> 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<std::shared_ptr<FilterContents>>(&input_textures[0])) {
size = filter->get()->GetOutputSize();
} else if (auto texture =
std::get_if<std::shared_ptr<Texture>>(&input_textures[0])) {
size = texture->get()->GetSize();
} else {
FML_UNREACHABLE();
}

return size + (clip_ ? ISize(radius_ * 2, radius_ * 2) : ISize());
}

} // namespace impeller
50 changes: 49 additions & 1 deletion impeller/entity/contents/filter_contents.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,17 @@ class FilterContents : public Contents {
Entity::BlendMode blend_mode,
InputTextures input_textures);

static std::shared_ptr<FilterContents> MakeDirectionalGaussianBlur(
InputVariant input_texture,
Scalar radius,
Vector2 direction,
bool clip_border = false);

static std::shared_ptr<FilterContents> MakeGaussianBlur(
InputVariant input_texture,
Scalar radius,
bool clip_border = false);

FilterContents();

~FilterContents() override;
Expand All @@ -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.
Expand All @@ -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_;
Expand All @@ -89,6 +103,7 @@ class BlendFilterContents : public FilterContents {
void SetBlendMode(Entity::BlendMode blend_mode);

private:
// |FilterContents|
bool RenderFilter(const std::vector<std::shared_ptr<Texture>>& input_textures,
const ContentContext& renderer,
RenderPass& pass) const override;
Expand All @@ -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<std::shared_ptr<Texture>>& 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
44 changes: 44 additions & 0 deletions impeller/entity/entity_unittests.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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
Loading

0 comments on commit 503dff6

Please sign in to comment.