blob: 0840679de597f6300f05a0aa36528ad96b9466ca [file] [log] [blame]
// 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/gaussian_blur_filter_contents.h"
#include <cmath>
#include "flutter/fml/make_copyable.h"
#include "impeller/entity/contents/clip_contents.h"
#include "impeller/entity/contents/color_source_contents.h"
#include "impeller/entity/contents/content_context.h"
#include "impeller/entity/texture_fill.frag.h"
#include "impeller/entity/texture_fill.vert.h"
#include "impeller/renderer/command.h"
#include "impeller/renderer/render_pass.h"
#include "impeller/renderer/texture_mipmap.h"
#include "impeller/renderer/vertex_buffer_builder.h"
namespace impeller {
using GaussianBlurVertexShader = KernelPipeline::VertexShader;
using GaussianBlurFragmentShader = KernelPipeline::FragmentShader;
const int32_t GaussianBlurFilterContents::kBlurFilterRequiredMipCount = 4;
namespace {
// 48 comes from kernel.glsl.
const int32_t kMaxKernelSize = 48;
SamplerDescriptor MakeSamplerDescriptor(MinMagFilter filter,
SamplerAddressMode address_mode) {
SamplerDescriptor sampler_desc;
sampler_desc.min_filter = filter;
sampler_desc.mag_filter = filter;
sampler_desc.width_address_mode = address_mode;
sampler_desc.height_address_mode = address_mode;
return sampler_desc;
}
template <typename T>
void BindVertices(RenderPass& pass,
HostBuffer& host_buffer,
std::initializer_list<typename T::PerVertexData>&& vertices) {
VertexBufferBuilder<typename T::PerVertexData> vtx_builder;
vtx_builder.AddVertices(vertices);
pass.SetVertexBuffer(vtx_builder.CreateVertexBuffer(host_buffer));
}
void SetTileMode(SamplerDescriptor* descriptor,
const ContentContext& renderer,
Entity::TileMode tile_mode) {
switch (tile_mode) {
case Entity::TileMode::kDecal:
if (renderer.GetDeviceCapabilities().SupportsDecalSamplerAddressMode()) {
descriptor->width_address_mode = SamplerAddressMode::kDecal;
descriptor->height_address_mode = SamplerAddressMode::kDecal;
}
break;
case Entity::TileMode::kClamp:
descriptor->width_address_mode = SamplerAddressMode::kClampToEdge;
descriptor->height_address_mode = SamplerAddressMode::kClampToEdge;
break;
case Entity::TileMode::kMirror:
descriptor->width_address_mode = SamplerAddressMode::kMirror;
descriptor->height_address_mode = SamplerAddressMode::kMirror;
break;
case Entity::TileMode::kRepeat:
descriptor->width_address_mode = SamplerAddressMode::kRepeat;
descriptor->height_address_mode = SamplerAddressMode::kRepeat;
break;
}
}
/// Makes a subpass that will render the scaled down input and add the
/// transparent gutter required for the blur halo.
fml::StatusOr<RenderTarget> MakeDownsampleSubpass(
const ContentContext& renderer,
const std::shared_ptr<CommandBuffer>& command_buffer,
std::shared_ptr<Texture> input_texture,
const SamplerDescriptor& sampler_descriptor,
const Quad& uvs,
const ISize& subpass_size,
Entity::TileMode tile_mode) {
ContentContext::SubpassCallback subpass_callback =
[&](const ContentContext& renderer, RenderPass& pass) {
HostBuffer& host_buffer = renderer.GetTransientsBuffer();
pass.SetCommandLabel("Gaussian blur downsample");
auto pipeline_options = OptionsFromPass(pass);
pipeline_options.primitive_type = PrimitiveType::kTriangleStrip;
pass.SetPipeline(renderer.GetTexturePipeline(pipeline_options));
TextureFillVertexShader::FrameInfo frame_info;
frame_info.mvp = Matrix::MakeOrthographic(ISize(1, 1));
frame_info.texture_sampler_y_coord_scale = 1.0;
frame_info.alpha = 1.0;
BindVertices<TextureFillVertexShader>(pass, host_buffer,
{
{Point(0, 0), uvs[0]},
{Point(1, 0), uvs[1]},
{Point(0, 1), uvs[2]},
{Point(1, 1), uvs[3]},
});
SamplerDescriptor linear_sampler_descriptor = sampler_descriptor;
SetTileMode(&linear_sampler_descriptor, renderer, tile_mode);
linear_sampler_descriptor.mag_filter = MinMagFilter::kLinear;
linear_sampler_descriptor.min_filter = MinMagFilter::kLinear;
TextureFillVertexShader::BindFrameInfo(
pass, host_buffer.EmplaceUniform(frame_info));
TextureFillFragmentShader::BindTextureSampler(
pass, input_texture,
renderer.GetContext()->GetSamplerLibrary()->GetSampler(
linear_sampler_descriptor));
return pass.Draw().ok();
};
fml::StatusOr<RenderTarget> render_target = renderer.MakeSubpass(
"Gaussian Blur Filter", subpass_size, command_buffer, subpass_callback);
return render_target;
}
fml::StatusOr<RenderTarget> MakeBlurSubpass(
const ContentContext& renderer,
const std::shared_ptr<CommandBuffer>& command_buffer,
const RenderTarget& input_pass,
const SamplerDescriptor& sampler_descriptor,
Entity::TileMode tile_mode,
const BlurParameters& blur_info,
std::optional<RenderTarget> destination_target,
const Quad& blur_uvs) {
if (blur_info.blur_sigma < kEhCloseEnough) {
return input_pass;
}
std::shared_ptr<Texture> input_texture = input_pass.GetRenderTargetTexture();
// TODO(gaaclarke): This blurs the whole image, but because we know the clip
// region we could focus on just blurring that.
ISize subpass_size = input_texture->GetSize();
ContentContext::SubpassCallback subpass_callback =
[&](const ContentContext& renderer, RenderPass& pass) {
GaussianBlurVertexShader::FrameInfo frame_info{
.mvp = Matrix::MakeOrthographic(ISize(1, 1)),
.texture_sampler_y_coord_scale = 1.0};
HostBuffer& host_buffer = renderer.GetTransientsBuffer();
ContentContextOptions options = OptionsFromPass(pass);
options.primitive_type = PrimitiveType::kTriangleStrip;
if (tile_mode == Entity::TileMode::kDecal &&
!renderer.GetDeviceCapabilities()
.SupportsDecalSamplerAddressMode()) {
pass.SetPipeline(renderer.GetKernelDecalPipeline(options));
} else {
pass.SetPipeline(renderer.GetKernelPipeline(options));
}
BindVertices<GaussianBlurVertexShader>(pass, host_buffer,
{
{blur_uvs[0], blur_uvs[0]},
{blur_uvs[1], blur_uvs[1]},
{blur_uvs[2], blur_uvs[2]},
{blur_uvs[3], blur_uvs[3]},
});
SamplerDescriptor linear_sampler_descriptor = sampler_descriptor;
linear_sampler_descriptor.mag_filter = MinMagFilter::kLinear;
linear_sampler_descriptor.min_filter = MinMagFilter::kLinear;
GaussianBlurFragmentShader::BindTextureSampler(
pass, input_texture,
renderer.GetContext()->GetSamplerLibrary()->GetSampler(
linear_sampler_descriptor));
GaussianBlurVertexShader::BindFrameInfo(
pass, host_buffer.EmplaceUniform(frame_info));
KernelPipeline::FragmentShader::KernelSamples kernel_samples =
LerpHackKernelSamples(GenerateBlurInfo(blur_info));
FML_CHECK(kernel_samples.sample_count < kMaxKernelSize);
GaussianBlurFragmentShader::BindKernelSamples(
pass, host_buffer.EmplaceUniform(kernel_samples));
return pass.Draw().ok();
};
if (destination_target.has_value()) {
return renderer.MakeSubpass("Gaussian Blur Filter",
destination_target.value(), command_buffer,
subpass_callback);
} else {
return renderer.MakeSubpass("Gaussian Blur Filter", subpass_size,
command_buffer, subpass_callback);
}
}
/// Returns `rect` relative to `reference`, where Rect::MakeXYWH(0,0,1,1) will
/// be returned when `rect` == `reference`.
Rect MakeReferenceUVs(const Rect& reference, const Rect& rect) {
Rect result = Rect::MakeOriginSize(rect.GetOrigin() - reference.GetOrigin(),
rect.GetSize());
return result.Scale(1.0f / Vector2(reference.GetSize()));
}
int ScaleBlurRadius(Scalar radius, Scalar scalar) {
return static_cast<int>(std::round(radius * scalar));
}
Entity ApplyClippedBlurStyle(Entity::ClipOperation clip_operation,
const Entity& entity,
const std::shared_ptr<FilterInput>& input,
const Snapshot& input_snapshot,
Entity blur_entity,
const std::shared_ptr<Geometry>& geometry) {
auto clip_contents = std::make_shared<ClipContents>();
clip_contents->SetClipOperation(clip_operation);
clip_contents->SetGeometry(geometry);
Entity clipper;
clipper.SetContents(clip_contents);
auto restore = std::make_unique<ClipRestoreContents>();
Matrix entity_transform = entity.GetTransform();
Matrix blur_transform = blur_entity.GetTransform();
auto renderer = fml::MakeCopyable(
[blur_entity = blur_entity.Clone(), clipper = std::move(clipper),
restore = std::move(restore), entity_transform,
blur_transform](const ContentContext& renderer, const Entity& entity,
RenderPass& pass) mutable {
bool result = true;
clipper.SetNewClipDepth(entity.GetNewClipDepth());
clipper.SetTransform(entity.GetTransform() * entity_transform);
result = clipper.Render(renderer, pass) && result;
blur_entity.SetNewClipDepth(entity.GetNewClipDepth());
blur_entity.SetTransform(entity.GetTransform() * blur_transform);
result = blur_entity.Render(renderer, pass) && result;
return result;
});
auto coverage =
fml::MakeCopyable([blur_entity = std::move(blur_entity),
blur_transform](const Entity& entity) mutable {
blur_entity.SetTransform(entity.GetTransform() * blur_transform);
return blur_entity.GetCoverage();
});
Entity result;
result.SetContents(Contents::MakeAnonymous(renderer, coverage));
return result;
}
Entity ApplyBlurStyle(FilterContents::BlurStyle blur_style,
const Entity& entity,
const std::shared_ptr<FilterInput>& input,
const Snapshot& input_snapshot,
Entity blur_entity,
const std::shared_ptr<Geometry>& geometry) {
switch (blur_style) {
case FilterContents::BlurStyle::kNormal:
return blur_entity;
case FilterContents::BlurStyle::kInner:
return ApplyClippedBlurStyle(Entity::ClipOperation::kIntersect, entity,
input, input_snapshot,
std::move(blur_entity), geometry);
break;
case FilterContents::BlurStyle::kOuter:
return ApplyClippedBlurStyle(Entity::ClipOperation::kDifference, entity,
input, input_snapshot,
std::move(blur_entity), geometry);
case FilterContents::BlurStyle::kSolid: {
Entity snapshot_entity =
Entity::FromSnapshot(input_snapshot, entity.GetBlendMode());
Entity result;
Matrix blurred_transform = blur_entity.GetTransform();
Matrix snapshot_transform = snapshot_entity.GetTransform();
result.SetContents(Contents::MakeAnonymous(
fml::MakeCopyable([blur_entity = blur_entity.Clone(),
blurred_transform, snapshot_transform,
snapshot_entity = std::move(snapshot_entity)](
const ContentContext& renderer,
const Entity& entity,
RenderPass& pass) mutable {
bool result = true;
blur_entity.SetNewClipDepth(entity.GetNewClipDepth());
blur_entity.SetTransform(entity.GetTransform() * blurred_transform);
result = result && blur_entity.Render(renderer, pass);
snapshot_entity.SetTransform(entity.GetTransform() *
snapshot_transform);
snapshot_entity.SetNewClipDepth(entity.GetNewClipDepth());
result = result && snapshot_entity.Render(renderer, pass);
return result;
}),
fml::MakeCopyable([blur_entity = blur_entity.Clone(),
blurred_transform](const Entity& entity) mutable {
blur_entity.SetTransform(entity.GetTransform() * blurred_transform);
return blur_entity.GetCoverage();
})));
return result;
}
}
}
} // namespace
std::string_view GaussianBlurFilterContents::kNoMipsError =
"Applying gaussian blur without mipmap.";
GaussianBlurFilterContents::GaussianBlurFilterContents(
Scalar sigma_x,
Scalar sigma_y,
Entity::TileMode tile_mode,
BlurStyle mask_blur_style,
const std::shared_ptr<Geometry>& mask_geometry)
: sigma_x_(sigma_x),
sigma_y_(sigma_y),
tile_mode_(tile_mode),
mask_blur_style_(mask_blur_style),
mask_geometry_(mask_geometry) {
// This is supposed to be enforced at a higher level.
FML_DCHECK(mask_blur_style == BlurStyle::kNormal || mask_geometry);
}
// This value was extracted from Skia, see:
// * https://github.com/google/skia/blob/d29cc3fe182f6e8a8539004a6a4ee8251677a6fd/src/gpu/ganesh/GrBlurUtils.cpp#L2561-L2576
// * https://github.com/google/skia/blob/d29cc3fe182f6e8a8539004a6a4ee8251677a6fd/src/gpu/BlurUtils.h#L57
Scalar GaussianBlurFilterContents::CalculateScale(Scalar sigma) {
if (sigma <= 4) {
return 1.0;
}
Scalar raw_result = 4.0 / sigma;
// Round to the nearest 1/(2^n) to get the best quality down scaling.
Scalar exponent = round(log2f(raw_result));
// Don't scale down below 1/16th to preserve signal.
exponent = std::max(-4.0f, exponent);
Scalar rounded = powf(2.0f, exponent);
Scalar result = rounded;
// Extend the range of the 1/8th downsample based on the effective kernel size
// for the blur.
if (rounded < 0.125f) {
Scalar rounded_plus = powf(2.0f, exponent + 1);
Scalar blur_radius = CalculateBlurRadius(sigma);
int kernel_size_plus = (ScaleBlurRadius(blur_radius, rounded_plus) * 2) + 1;
// This constant was picked by looking at the results to make sure no
// shimmering was introduced at the highest sigma values that downscale to
// 1/16th.
static constexpr int32_t kEighthDownsampleKernalWidthMax = 41;
result = kernel_size_plus <= kEighthDownsampleKernalWidthMax ? rounded_plus
: rounded;
}
return result;
};
std::optional<Rect> GaussianBlurFilterContents::GetFilterSourceCoverage(
const Matrix& effect_transform,
const Rect& output_limit) const {
Vector2 scaled_sigma = {ScaleSigma(sigma_x_), ScaleSigma(sigma_y_)};
Vector2 blur_radius = {CalculateBlurRadius(scaled_sigma.x),
CalculateBlurRadius(scaled_sigma.y)};
Vector3 blur_radii =
effect_transform.Basis() * Vector3{blur_radius.x, blur_radius.y, 0.0};
return output_limit.Expand(Point(blur_radii.x, blur_radii.y));
}
std::optional<Rect> GaussianBlurFilterContents::GetFilterCoverage(
const FilterInput::Vector& inputs,
const Entity& entity,
const Matrix& effect_transform) const {
if (inputs.empty()) {
return {};
}
std::optional<Rect> input_coverage = inputs[0]->GetCoverage(entity);
if (!input_coverage.has_value()) {
return {};
}
Vector2 scaled_sigma = (effect_transform.Basis() *
Vector2(ScaleSigma(sigma_x_), ScaleSigma(sigma_y_)))
.Abs();
Vector2 blur_radius = Vector2(CalculateBlurRadius(scaled_sigma.x),
CalculateBlurRadius(scaled_sigma.y));
Vector2 padding(ceil(blur_radius.x), ceil(blur_radius.y));
Vector2 local_padding = (entity.GetTransform().Basis() * padding).Abs();
return input_coverage.value().Expand(Point(local_padding.x, local_padding.y));
}
std::optional<Entity> GaussianBlurFilterContents::RenderFilter(
const FilterInput::Vector& inputs,
const ContentContext& renderer,
const Entity& entity,
const Matrix& effect_transform,
const Rect& coverage,
const std::optional<Rect>& coverage_hint) const {
if (inputs.empty()) {
return std::nullopt;
}
Vector2 scaled_sigma = (effect_transform.Basis() *
Vector2(ScaleSigma(sigma_x_), ScaleSigma(sigma_y_)))
.Abs();
Vector2 blur_radius = Vector2(CalculateBlurRadius(scaled_sigma.x),
CalculateBlurRadius(scaled_sigma.y));
Vector2 padding(ceil(blur_radius.x), ceil(blur_radius.y));
Vector2 local_padding = (entity.GetTransform().Basis() * padding).Abs();
// Apply as much of the desired padding as possible from the source. This may
// be ignored so must be accounted for in the downsample pass by adding a
// transparent gutter.
std::optional<Rect> expanded_coverage_hint;
if (coverage_hint.has_value()) {
expanded_coverage_hint = coverage_hint->Expand(local_padding);
}
int32_t mip_count = kBlurFilterRequiredMipCount;
if (renderer.GetContext()->GetBackendType() ==
Context::BackendType::kOpenGLES) {
// TODO(https://github.com/flutter/flutter/issues/141732): Implement mip map
// generation on opengles.
mip_count = 1;
}
std::optional<Snapshot> input_snapshot =
inputs[0]->GetSnapshot("GaussianBlur", renderer, entity,
/*coverage_limit=*/expanded_coverage_hint,
/*mip_count=*/mip_count);
if (!input_snapshot.has_value()) {
return std::nullopt;
}
if (scaled_sigma.x < kEhCloseEnough && scaled_sigma.y < kEhCloseEnough) {
return Entity::FromSnapshot(input_snapshot.value(),
entity.GetBlendMode()); // No blur to render.
}
// In order to avoid shimmering in downsampling step, we should have mips.
if (input_snapshot->texture->GetMipCount() <= 1) {
FML_DLOG(ERROR) << kNoMipsError;
}
FML_DCHECK(!input_snapshot->texture->NeedsMipmapGeneration());
Scalar desired_scalar =
std::min(CalculateScale(scaled_sigma.x), CalculateScale(scaled_sigma.y));
// TODO(jonahwilliams): If desired_scalar is 1.0 and we fully acquired the
// gutter from the expanded_coverage_hint, we can skip the downsample pass.
// pass.
Vector2 downsample_scalar(desired_scalar, desired_scalar);
Rect source_rect = Rect::MakeSize(input_snapshot->texture->GetSize());
Rect source_rect_padded = source_rect.Expand(padding);
Matrix padding_snapshot_adjustment = Matrix::MakeTranslation(-padding);
// TODO(gaaclarke): The padding could be removed if we know it's not needed or
// resized to account for the expanded_clip_coverage. There doesn't appear
// to be the math to make those calculations though. The following
// optimization works, but causes a shimmer as a result of
// https://github.com/flutter/flutter/issues/140193 so it isn't applied.
//
// !input_snapshot->GetCoverage()->Expand(-local_padding)
// .Contains(coverage_hint.value()))
Vector2 downsampled_size = source_rect_padded.GetSize() * downsample_scalar;
ISize subpass_size =
ISize(round(downsampled_size.x), round(downsampled_size.y));
Vector2 effective_scalar =
Vector2(subpass_size) / source_rect_padded.GetSize();
Quad uvs = CalculateUVs(inputs[0], entity, source_rect_padded,
input_snapshot->texture->GetSize());
std::shared_ptr<CommandBuffer> command_buffer =
renderer.GetContext()->CreateCommandBuffer();
if (!command_buffer) {
return std::nullopt;
}
fml::StatusOr<RenderTarget> pass1_out = MakeDownsampleSubpass(
renderer, command_buffer, input_snapshot->texture,
input_snapshot->sampler_descriptor, uvs, subpass_size, tile_mode_);
if (!pass1_out.ok()) {
return std::nullopt;
}
Vector2 pass1_pixel_size =
1.0 / Vector2(pass1_out.value().GetRenderTargetTexture()->GetSize());
std::optional<Rect> input_snapshot_coverage = input_snapshot->GetCoverage();
Quad blur_uvs = {Point(0, 0), Point(1, 0), Point(0, 1), Point(1, 1)};
if (expanded_coverage_hint.has_value() &&
input_snapshot_coverage.has_value() &&
// TODO(https://github.com/flutter/flutter/issues/140890): Remove this
// condition. There is some flaw in coverage stopping us from using this
// today. I attempted to use source coordinates to calculate the uvs,
// but that didn't work either.
input_snapshot.has_value() &&
input_snapshot.value().transform.IsTranslationScaleOnly()) {
// Only process the uvs where the blur is happening, not the whole texture.
std::optional<Rect> uvs = MakeReferenceUVs(input_snapshot_coverage.value(),
expanded_coverage_hint.value())
.Intersection(Rect::MakeSize(Size(1, 1)));
FML_DCHECK(uvs.has_value());
if (uvs.has_value()) {
blur_uvs[0] = uvs->GetLeftTop();
blur_uvs[1] = uvs->GetRightTop();
blur_uvs[2] = uvs->GetLeftBottom();
blur_uvs[3] = uvs->GetRightBottom();
}
}
fml::StatusOr<RenderTarget> pass2_out = MakeBlurSubpass(
renderer, command_buffer, /*input_pass=*/pass1_out.value(),
input_snapshot->sampler_descriptor, tile_mode_,
BlurParameters{
.blur_uv_offset = Point(0.0, pass1_pixel_size.y),
.blur_sigma = scaled_sigma.y * effective_scalar.y,
.blur_radius = ScaleBlurRadius(blur_radius.y, effective_scalar.y),
.step_size = 1,
},
/*destination_target=*/std::nullopt, blur_uvs);
if (!pass2_out.ok()) {
return std::nullopt;
}
// Only ping pong if the first pass actually created a render target.
auto pass3_destination = pass2_out.value().GetRenderTargetTexture() !=
pass1_out.value().GetRenderTargetTexture()
? std::optional<RenderTarget>(pass1_out.value())
: std::optional<RenderTarget>(std::nullopt);
fml::StatusOr<RenderTarget> pass3_out = MakeBlurSubpass(
renderer, command_buffer, /*input_pass=*/pass2_out.value(),
input_snapshot->sampler_descriptor, tile_mode_,
BlurParameters{
.blur_uv_offset = Point(pass1_pixel_size.x, 0.0),
.blur_sigma = scaled_sigma.x * effective_scalar.x,
.blur_radius = ScaleBlurRadius(blur_radius.x, effective_scalar.x),
.step_size = 1,
},
pass3_destination, blur_uvs);
if (!pass3_out.ok()) {
return std::nullopt;
}
if (!renderer.GetContext()
->GetCommandQueue()
->Submit(/*buffers=*/{command_buffer})
.ok()) {
return std::nullopt;
}
// The ping-pong approach requires that each render pass output has the same
// size.
FML_DCHECK((pass1_out.value().GetRenderTargetSize() ==
pass2_out.value().GetRenderTargetSize()) &&
(pass2_out.value().GetRenderTargetSize() ==
pass3_out.value().GetRenderTargetSize()));
SamplerDescriptor sampler_desc = MakeSamplerDescriptor(
MinMagFilter::kLinear, SamplerAddressMode::kClampToEdge);
Entity blur_output_entity = Entity::FromSnapshot(
Snapshot{.texture = pass3_out.value().GetRenderTargetTexture(),
.transform = input_snapshot->transform *
padding_snapshot_adjustment *
Matrix::MakeScale(1 / effective_scalar),
.sampler_descriptor = sampler_desc,
.opacity = input_snapshot->opacity},
entity.GetBlendMode());
return ApplyBlurStyle(mask_blur_style_, entity, inputs[0],
input_snapshot.value(), std::move(blur_output_entity),
mask_geometry_);
}
Scalar GaussianBlurFilterContents::CalculateBlurRadius(Scalar sigma) {
return static_cast<Radius>(Sigma(sigma)).radius;
}
Quad GaussianBlurFilterContents::CalculateUVs(
const std::shared_ptr<FilterInput>& filter_input,
const Entity& entity,
const Rect& source_rect,
const ISize& texture_size) {
Matrix input_transform = filter_input->GetLocalTransform(entity);
Quad coverage_quad = source_rect.GetTransformedPoints(input_transform);
Matrix uv_transform = Matrix::MakeScale(
{1.0f / texture_size.width, 1.0f / texture_size.height, 1.0f});
return uv_transform.Transform(coverage_quad);
}
// This function was calculated by observing Skia's behavior. Its blur at 500
// seemed to be 0.15. Since we clamp at 500 I solved the quadratic equation
// that puts the minima there and a f(0)=1.
Scalar GaussianBlurFilterContents::ScaleSigma(Scalar sigma) {
// Limit the kernel size to 1000x1000 pixels, like Skia does.
Scalar clamped = std::min(sigma, 500.0f);
constexpr Scalar a = 3.4e-06;
constexpr Scalar b = -3.4e-3;
constexpr Scalar c = 1.f;
Scalar scalar = c + b * clamped + a * clamped * clamped;
return clamped * scalar;
}
KernelPipeline::FragmentShader::KernelSamples GenerateBlurInfo(
BlurParameters parameters) {
KernelPipeline::FragmentShader::KernelSamples result;
result.sample_count =
((2 * parameters.blur_radius) / parameters.step_size) + 1;
// Chop off the last samples if the radius >= 3 where they account for < 1.56%
// of the result.
int x_offset = 0;
if (parameters.blur_radius >= 3) {
result.sample_count -= 2;
x_offset = 1;
}
Scalar tally = 0.0f;
for (int i = 0; i < result.sample_count; ++i) {
int x = x_offset + (i * parameters.step_size) - parameters.blur_radius;
result.samples[i] = KernelPipeline::FragmentShader::KernelSample{
.uv_offset = parameters.blur_uv_offset * x,
.coefficient = expf(-0.5f * (x * x) /
(parameters.blur_sigma * parameters.blur_sigma)) /
(sqrtf(2.0f * M_PI) * parameters.blur_sigma),
};
tally += result.samples[i].coefficient;
}
// Make sure everything adds up to 1.
for (auto& sample : result.samples) {
sample.coefficient /= tally;
}
return result;
}
// This works by shrinking the kernel size by 2 and relying on lerp to read
// between the samples.
KernelPipeline::FragmentShader::KernelSamples LerpHackKernelSamples(
KernelPipeline::FragmentShader::KernelSamples parameters) {
KernelPipeline::FragmentShader::KernelSamples result;
result.sample_count = ((parameters.sample_count - 1) / 2) + 1;
int32_t middle = result.sample_count / 2;
int32_t j = 0;
for (int i = 0; i < result.sample_count; i++) {
if (i == middle) {
result.samples[i] = parameters.samples[j++];
} else {
KernelPipeline::FragmentShader::KernelSample left = parameters.samples[j];
KernelPipeline::FragmentShader::KernelSample right =
parameters.samples[j + 1];
result.samples[i] = KernelPipeline::FragmentShader::KernelSample{
.uv_offset = (left.uv_offset * left.coefficient +
right.uv_offset * right.coefficient) /
(left.coefficient + right.coefficient),
.coefficient = left.coefficient + right.coefficient,
};
j += 2;
}
}
return result;
}
} // namespace impeller