Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Impeller] blur: hold on to 1/8 downsample until the kernel overflows #50363

Merged
merged 1 commit into from
Feb 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions impeller/aiks/aiks_blur_unittests.cc
Original file line number Diff line number Diff line change
Expand Up @@ -571,6 +571,10 @@ TEST_P(AiksTest, GaussianBlurAnimatedBackdrop) {
Point(1024 / 2 - boston->GetSize().width / 2,
(768 / 2 - boston->GetSize().height / 2) + y),
{});
auto [handle_a, handle_b] = IMPELLER_PLAYGROUND_LINE(
Point(100, 100), Point(900, 700), 20, Color::Red(), Color::Red());
canvas.ClipRect(
Rect::MakeLTRB(handle_a.x, handle_a.y, handle_b.x, handle_b.y));
canvas.ClipRect(Rect::MakeLTRB(100, 100, 900, 700));
canvas.SaveLayer({.blend_mode = BlendMode::kSource}, std::nullopt,
ImageFilter::MakeBlur(Sigma(sigma), Sigma(sigma),
Expand Down
30 changes: 21 additions & 9 deletions impeller/entity/contents/filters/gaussian_blur_filter_contents.cc
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ 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;
Expand Down Expand Up @@ -189,6 +192,10 @@ Rect MakeReferenceUVs(const Rect& reference, const Rect& rect) {
rect.GetSize());
return result.Scale(1.0f / Vector2(reference.GetSize()));
}

int ScaleBlurRadius(Scalar radius, Scalar scalar) {
return static_cast<int>(std::round(radius * scalar));
}
} // namespace

std::string_view GaussianBlurFilterContents::kNoMipsError =
Expand All @@ -207,13 +214,21 @@ Scalar GaussianBlurFilterContents::CalculateScale(Scalar sigma) {
if (sigma <= 4) {
return 1.0;
}
Scalar result = 4.0 / sigma;
Scalar raw_result = 4.0 / sigma;
// Round to the nearest 1/(2^n) to get the best quality down scaling.
Scalar exponent = round(log2f(result));
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);
return rounded;
Scalar result = rounded;
// Only drop below 1/8 if 1/8 would overflow our kernel.
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;
result = kernel_size_plus < kMaxKernelSize ? rounded_plus : rounded;
}
return result;
};

std::optional<Rect> GaussianBlurFilterContents::GetFilterSourceCoverage(
Expand Down Expand Up @@ -370,8 +385,7 @@ std::optional<Entity> GaussianBlurFilterContents::RenderFilter(
BlurParameters{
.blur_uv_offset = Point(0.0, pass1_pixel_size.y),
.blur_sigma = scaled_sigma.y * effective_scalar.y,
.blur_radius =
static_cast<int>(std::round(blur_radius.y * effective_scalar.y)),
.blur_radius = ScaleBlurRadius(blur_radius.y, effective_scalar.y),
.step_size = 1,
},
/*destination_target=*/std::nullopt, blur_uvs);
Expand All @@ -392,8 +406,7 @@ std::optional<Entity> GaussianBlurFilterContents::RenderFilter(
BlurParameters{
.blur_uv_offset = Point(pass1_pixel_size.x, 0.0),
.blur_sigma = scaled_sigma.x * effective_scalar.x,
.blur_radius =
static_cast<int>(std::round(blur_radius.x * effective_scalar.x)),
.blur_radius = ScaleBlurRadius(blur_radius.x, effective_scalar.x),
.step_size = 1,
},
pass3_destination, blur_uvs);
Expand Down Expand Up @@ -457,8 +470,7 @@ KernelPipeline::FragmentShader::KernelSamples GenerateBlurInfo(
KernelPipeline::FragmentShader::KernelSamples result;
result.sample_count =
((2 * parameters.blur_radius) / parameters.step_size) + 1;
// 48 comes from kernel.glsl.
FML_CHECK(result.sample_count < 48);
FML_CHECK(result.sample_count < kMaxKernelSize);

// Chop off the last samples if the radius >= 3 where they account for < 1.56%
// of the result.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,8 @@ TEST(GaussianBlurFilterContentsTest, CalculateSigmaValues) {
EXPECT_EQ(GaussianBlurFilterContents::CalculateScale(3.0f), 1);
EXPECT_EQ(GaussianBlurFilterContents::CalculateScale(4.0f), 1);
EXPECT_EQ(GaussianBlurFilterContents::CalculateScale(16.0f), 0.25);
// Hang on to 1/8 as long as possible.
EXPECT_EQ(GaussianBlurFilterContents::CalculateScale(100.0f), 0.125);
// Downsample clamped to 1/16th.
EXPECT_EQ(GaussianBlurFilterContents::CalculateScale(1024.0f), 0.0625);
}
Expand Down