Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.
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
35 changes: 35 additions & 0 deletions impeller/aiks/aiks_unittests.cc
Original file line number Diff line number Diff line change
Expand Up @@ -3273,5 +3273,40 @@ TEST_P(AiksTest, PipelineBlendSingleParameter) {
ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
}

TEST_P(AiksTest, ClippedBlurFilterRendersCorrectlyInteractive) {
auto callback = [&](AiksContext& renderer, RenderTarget& render_target) {
auto point = IMPELLER_PLAYGROUND_POINT(Point(400, 400), 20, Color::Green());

Canvas canvas;
canvas.Translate(point - Point(400, 400));
Paint paint;
paint.mask_blur_descriptor = Paint::MaskBlurDescriptor{
.style = FilterContents::BlurStyle::kNormal,
.sigma = Radius{120 * 3},
};
paint.color = Color::Red();
PathBuilder builder{};
builder.AddRect(Rect::MakeLTRB(0, 0, 800, 800));
canvas.DrawPath(builder.TakePath(), paint);
return renderer.Render(canvas.EndRecordingAsPicture(), render_target);
};
ASSERT_TRUE(OpenPlaygroundHere(callback));
}

TEST_P(AiksTest, ClippedBlurFilterRendersCorrectly) {
Canvas canvas;
canvas.Translate(Point(0, -400));
Paint paint;
paint.mask_blur_descriptor = Paint::MaskBlurDescriptor{
.style = FilterContents::BlurStyle::kNormal,
.sigma = Radius{120 * 3},
};
paint.color = Color::Red();
PathBuilder builder{};
builder.AddRect(Rect::MakeLTRB(0, 0, 800, 800));
canvas.DrawPath(builder.TakePath(), paint);
ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
}

} // namespace testing
} // namespace impeller
23 changes: 18 additions & 5 deletions impeller/aiks/canvas.cc
Original file line number Diff line number Diff line change
Expand Up @@ -492,7 +492,7 @@ void Canvas::DrawImageRect(const std::shared_ptr<Image>& image,
Entity entity;
entity.SetBlendMode(paint.blend_mode);
entity.SetStencilDepth(GetStencilDepth());
entity.SetContents(paint.WithFilters(contents, false));
entity.SetContents(paint.WithFilters(contents));
entity.SetTransformation(GetCurrentTransformation());

GetCurrentPass().AddEntity(entity);
Expand Down Expand Up @@ -561,8 +561,14 @@ void Canvas::DrawTextFrame(const TextFrame& text_frame,
color_text_contents->SetColorSourceContents(
paint.color_source.GetContents(paint));

entity.SetContents(
paint.WithFilters(std::move(color_text_contents), false));
// TODO(bdero): This mask blur application is a hack. It will always wind up
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we have bugs tracking these?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whoops, thanks for the reminder. Filed flutter/flutter#133297.

// doing a gaussian blur that affects the color source itself
// instead of just the mask. The color filter text support
// needs to be reworked in order to interact correctly with
// mask filters.
// https://github.com/flutter/flutter/issues/133297
entity.SetContents(paint.WithFilters(
paint.WithMaskBlur(std::move(color_text_contents), true)));

GetCurrentPass().AddEntity(entity);
return;
Expand All @@ -573,7 +579,14 @@ void Canvas::DrawTextFrame(const TextFrame& text_frame,
entity.SetTransformation(GetCurrentTransformation() *
Matrix::MakeTranslation(position));

entity.SetContents(paint.WithFilters(std::move(text_contents), true));
// TODO(bdero): This mask blur application is a hack. It will always wind up
// doing a gaussian blur that affects the color source itself
// instead of just the mask. The color filter text support
// needs to be reworked in order to interact correctly with
// mask filters.
// https://github.com/flutter/flutter/issues/133297
entity.SetContents(
paint.WithFilters(paint.WithMaskBlur(std::move(text_contents), true)));

GetCurrentPass().AddEntity(entity);
}
Expand Down Expand Up @@ -682,7 +695,7 @@ void Canvas::DrawAtlas(const std::shared_ptr<Image>& atlas,
entity.SetTransformation(GetCurrentTransformation());
entity.SetStencilDepth(GetStencilDepth());
entity.SetBlendMode(paint.blend_mode);
entity.SetContents(paint.WithFilters(contents, false));
entity.SetContents(paint.WithFilters(contents));

GetCurrentPass().AddEntity(entity);
}
Expand Down
49 changes: 39 additions & 10 deletions impeller/aiks/paint.cc
Original file line number Diff line number Diff line change
Expand Up @@ -34,21 +34,32 @@ std::shared_ptr<Contents> Paint::CreateContentsForEntity(const Path& path,
std::shared_ptr<Contents> Paint::CreateContentsForGeometry(
std::shared_ptr<Geometry> geometry) const {
auto contents = color_source.GetContents(*this);

// Attempt to apply the color filter on the CPU first.
// Note: This is not just an optimization; some color sources rely on
// CPU-applied color filters to behave properly.
bool needs_color_filter = !!color_filter;
if (color_filter &&
contents->ApplyColorFilter(color_filter->GetCPUColorFilterProc())) {
needs_color_filter = false;
}

contents->SetGeometry(std::move(geometry));
if (mask_blur_descriptor.has_value()) {
return mask_blur_descriptor->CreateMaskBlur(contents);
// If there's a mask blur and we need to apply the color filter on the GPU,
// we need to be careful to only apply the color filter to the source
// colors. CreateMaskBlur is able to handle this case.
return mask_blur_descriptor->CreateMaskBlur(
contents, needs_color_filter ? color_filter : nullptr);
}

return contents;
}

std::shared_ptr<Contents> Paint::WithFilters(
std::shared_ptr<Contents> input,
std::optional<bool> is_solid_color) const {
bool is_solid_color_val = is_solid_color.value_or(color_source.GetType() ==
ColorSource::Type::kColor);
std::shared_ptr<Contents> input) const {
input = WithColorFilter(input, /*absorb_opacity=*/true);
input = WithInvertFilter(input);
input = WithMaskBlur(input, is_solid_color_val);
input = WithImageFilter(input, Matrix(), /*is_subpass=*/false);
return input;
}
Expand Down Expand Up @@ -128,7 +139,16 @@ std::shared_ptr<Contents> Paint::WithInvertFilter(
}

std::shared_ptr<FilterContents> Paint::MaskBlurDescriptor::CreateMaskBlur(
std::shared_ptr<ColorSourceContents> color_source_contents) const {
std::shared_ptr<ColorSourceContents> color_source_contents,
const std::shared_ptr<ColorFilter>& color_filter) const {
// If it's a solid color and there is no color filter, then we can just get
// away with doing one Gaussian blur.
if (color_source_contents->IsSolidColor() && !color_filter) {
return FilterContents::MakeGaussianBlur(
FilterInput::Make(color_source_contents), sigma, sigma, style,
Entity::TileMode::kDecal, Matrix());
}

/// 1. Create an opaque white mask of the original geometry.

auto mask = std::make_shared<SolidColorContents>();
Expand All @@ -152,11 +172,20 @@ std::shared_ptr<FilterContents> Paint::MaskBlurDescriptor::CreateMaskBlur(
color_source_contents->SetGeometry(
Geometry::MakeRect(*expanded_local_bounds));

/// 4. Composite the color source and mask together.
std::shared_ptr<Contents> color_contents = color_source_contents;

/// 4. Apply the user set color filter on the GPU, if applicable.

if (color_filter) {
color_contents = color_filter->WrapWithGPUColorFilter(
FilterInput::Make(color_source_contents), true);
}

/// 5. Composite the color source with the blurred mask.

return ColorFilterContents::MakeBlend(
BlendMode::kSourceIn, {FilterInput::Make(blurred_mask),
FilterInput::Make(color_source_contents)});
BlendMode::kSourceIn,
{FilterInput::Make(blurred_mask), FilterInput::Make(color_contents)});
}

std::shared_ptr<FilterContents> Paint::MaskBlurDescriptor::CreateMaskBlur(
Expand Down
15 changes: 4 additions & 11 deletions impeller/aiks/paint.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ struct Paint {
Sigma sigma;

std::shared_ptr<FilterContents> CreateMaskBlur(
std::shared_ptr<ColorSourceContents> color_source_contents) const;
std::shared_ptr<ColorSourceContents> color_source_contents,
const std::shared_ptr<ColorFilter>& color_filter) const;

std::shared_ptr<FilterContents> CreateMaskBlur(
const FilterInput::Ref& input,
Expand All @@ -67,18 +68,10 @@ struct Paint {

/// @brief Wrap this paint's configured filters to the given contents.
/// @param[in] input The contents to wrap with paint's filters.
/// @param[in] is_solid_color Affects mask blurring behavior. If false, use
/// the image border for mask blurring. If true,
/// do a Gaussian blur to achieve the mask
/// blurring effect for arbitrary paths. If unset,
/// use the current paint configuration to infer
/// the result.
/// @return The filter-wrapped contents. If there are no filters that need
/// to be wrapped for the current paint configuration, the
/// original contents is returned.
std::shared_ptr<Contents> WithFilters(
std::shared_ptr<Contents> input,
std::optional<bool> is_solid_color = std::nullopt) const;
std::shared_ptr<Contents> WithFilters(std::shared_ptr<Contents> input) const;

/// @brief Wrap this paint's configured filters to the given contents of
/// subpass target.
Expand All @@ -101,10 +94,10 @@ struct Paint {
/// @brief Whether this paint has a color filter that can apply opacity
bool HasColorFilter() const;

private:
std::shared_ptr<Contents> WithMaskBlur(std::shared_ptr<Contents> input,
bool is_solid_color) const;

private:
std::shared_ptr<Contents> WithImageFilter(std::shared_ptr<Contents> input,
const Matrix& effect_transform,
bool is_subpass) const;
Expand Down
4 changes: 4 additions & 0 deletions impeller/entity/contents/color_source_contents.cc
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ const Matrix& ColorSourceContents::GetInverseEffectTransform() const {
return inverse_matrix_;
}

bool ColorSourceContents::IsSolidColor() const {
return false;
}

std::optional<Rect> ColorSourceContents::GetCoverage(
const Entity& entity) const {
return geometry_->GetCoverage(entity.GetTransformation());
Expand Down
2 changes: 2 additions & 0 deletions impeller/entity/contents/color_source_contents.h
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,8 @@ class ColorSourceContents : public Contents {
///
Scalar GetOpacityFactor() const;

virtual bool IsSolidColor() const;

// |Contents|
std::optional<Rect> GetCoverage(const Entity& entity) const override;

Expand Down
28 changes: 11 additions & 17 deletions impeller/entity/contents/filters/gaussian_blur_filter_contents.cc
Original file line number Diff line number Diff line change
Expand Up @@ -104,11 +104,19 @@ std::optional<Entity> DirectionalGaussianBlurFilterContents::RenderFilter(
// Limit the kernel size to 1000x1000 pixels, like Skia does.
auto radius = std::min(Radius{blur_sigma_}.radius, 500.0f);

auto transform = entity.GetTransformation() * effect_transform.Basis();
auto transformed_blur_radius =
transform.TransformDirection(blur_direction_ * radius);

auto transformed_blur_radius_length = transformed_blur_radius.GetLength();

// Input 0 snapshot.

std::optional<Rect> expanded_coverage_hint;
if (coverage_hint.has_value()) {
auto r = Size(radius, radius).Abs();
auto r =
Size(transformed_blur_radius_length, transformed_blur_radius_length)
.Abs();
expanded_coverage_hint =
is_first_pass ? Rect(coverage_hint.value().origin - r,
Size(coverage_hint.value().size + r * 2))
Expand All @@ -126,12 +134,6 @@ std::optional<Entity> DirectionalGaussianBlurFilterContents::RenderFilter(
entity.GetStencilDepth()); // No blur to render.
}

auto transform = entity.GetTransformation() * effect_transform.Basis();
auto transformed_blur_radius =
transform.TransformDirection(blur_direction_ * radius);

auto transformed_blur_radius_length = transformed_blur_radius.GetLength();

// If the radius length is < .5, the shader will take at most 1 sample,
// resulting in no blur.
if (transformed_blur_radius_length < .5) {
Expand All @@ -158,16 +160,6 @@ std::optional<Entity> DirectionalGaussianBlurFilterContents::RenderFilter(
pass_texture_rect.origin.x -= transformed_blur_radius_length;
pass_texture_rect.size.width += transformed_blur_radius_length * 2;

// Crop the pass texture with the rotated coverage hint if one was given.
if (expanded_coverage_hint.has_value()) {
auto maybe_pass_texture_rect = pass_texture_rect.Intersection(
expanded_coverage_hint->TransformBounds(texture_rotate));
if (!maybe_pass_texture_rect.has_value()) {
return std::nullopt;
}
pass_texture_rect = *maybe_pass_texture_rect;
}

// Source override snapshot.

auto source = source_override_ ? source_override_ : inputs[0];
Expand Down Expand Up @@ -335,6 +327,8 @@ std::optional<Entity> DirectionalGaussianBlurFilterContents::RenderFilter(
SamplerDescriptor sampler_desc;
sampler_desc.min_filter = MinMagFilter::kLinear;
sampler_desc.mag_filter = MinMagFilter::kLinear;
sampler_desc.width_address_mode = SamplerAddressMode::kClampToEdge;
sampler_desc.width_address_mode = SamplerAddressMode::kClampToEdge;

return Entity::FromSnapshot(
Snapshot{.texture = out_texture,
Expand Down
4 changes: 4 additions & 0 deletions impeller/entity/contents/solid_color_contents.cc
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ Color SolidColorContents::GetColor() const {
return color_.WithAlpha(color_.alpha * GetOpacityFactor());
}

bool SolidColorContents::IsSolidColor() const {
return true;
}

bool SolidColorContents::IsOpaque() const {
return GetColor().IsOpaque();
}
Expand Down
3 changes: 3 additions & 0 deletions impeller/entity/contents/solid_color_contents.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ class SolidColorContents final : public ColorSourceContents {

Color GetColor() const;

// |ColorSourceContents|
bool IsSolidColor() const override;

// |Contents|
bool IsOpaque() const override;

Expand Down
3 changes: 3 additions & 0 deletions impeller/golden_tests/golden_playground_test_mac.cc
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ static const std::vector<std::string> kSkipTests = {
"impeller_Play_AiksTest_CanRenderRadialGradientManyColors_Vulkan",
"impeller_Play_AiksTest_CanRenderBackdropBlurInteractive_Metal",
"impeller_Play_AiksTest_CanRenderBackdropBlurInteractive_Vulkan",
"impeller_Play_AiksTest_ClippedBlurFilterRendersCorrectlyInteractive_Metal",
"impeller_Play_AiksTest_ClippedBlurFilterRendersCorrectlyInteractive_"
"Vulkan",
"impeller_Play_AiksTest_TextFrameSubpixelAlignment_Metal",
"impeller_Play_AiksTest_TextFrameSubpixelAlignment_Vulkan",
"impeller_Play_AiksTest_ColorWheel_Metal",
Expand Down