diff --git a/impeller/aiks/aiks_blur_unittests.cc b/impeller/aiks/aiks_blur_unittests.cc index 370eee168332d..90492e95c8875 100644 --- a/impeller/aiks/aiks_blur_unittests.cc +++ b/impeller/aiks/aiks_blur_unittests.cc @@ -588,5 +588,61 @@ TEST_P(AiksTest, GaussianBlurAnimatedBackdrop) { ASSERT_TRUE(OpenPlaygroundHere(callback)); } +TEST_P(AiksTest, GaussianBlurStyleInner) { + Canvas canvas; + canvas.Scale(GetContentScale()); + + canvas.DrawPaint({.color = Color(0.1, 0.1, 0.1, 1.0)}); + + Paint paint; + paint.color = Color::Green(); + paint.mask_blur_descriptor = Paint::MaskBlurDescriptor{ + .style = FilterContents::BlurStyle::kInner, + .sigma = Sigma(30), + }; + canvas.DrawPath(PathBuilder() + .MoveTo({200, 200}) + .LineTo({300, 400}) + .LineTo({100, 400}) + .Close() + .TakePath(), + paint); + + // Draw another thing to make sure the clip area is reset. + Paint red; + red.color = Color::Red(); + canvas.DrawRect(Rect::MakeXYWH(0, 0, 200, 200), red); + + ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); +} + +TEST_P(AiksTest, GaussianBlurStyleOuter) { + Canvas canvas; + canvas.Scale(GetContentScale()); + + canvas.DrawPaint({.color = Color(0.1, 0.1, 0.1, 1.0)}); + + Paint paint; + paint.color = Color::Green(); + paint.mask_blur_descriptor = Paint::MaskBlurDescriptor{ + .style = FilterContents::BlurStyle::kOuter, + .sigma = Sigma(30), + }; + canvas.DrawPath(PathBuilder() + .MoveTo({200, 200}) + .LineTo({300, 400}) + .LineTo({100, 400}) + .Close() + .TakePath(), + paint); + + // Draw another thing to make sure the clip area is reset. + Paint red; + red.color = Color::Red(); + canvas.DrawRect(Rect::MakeXYWH(0, 0, 200, 200), red); + + ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); +} + } // namespace testing } // namespace impeller diff --git a/impeller/aiks/image_filter.cc b/impeller/aiks/image_filter.cc index 7eba48713c8a3..10e0d72cef676 100644 --- a/impeller/aiks/image_filter.cc +++ b/impeller/aiks/image_filter.cc @@ -81,8 +81,8 @@ BlurImageFilter::~BlurImageFilter() = default; std::shared_ptr BlurImageFilter::WrapInput( const FilterInput::Ref& input) const { - return FilterContents::MakeGaussianBlur(input, sigma_x_, sigma_y_, - blur_style_, tile_mode_); + return FilterContents::MakeGaussianBlur(input, sigma_x_, sigma_y_, tile_mode_, + blur_style_); } std::shared_ptr BlurImageFilter::Clone() const { diff --git a/impeller/aiks/paint.cc b/impeller/aiks/paint.cc index 2cf2d8c3db71a..94951236e6650 100644 --- a/impeller/aiks/paint.cc +++ b/impeller/aiks/paint.cc @@ -128,8 +128,8 @@ std::shared_ptr Paint::MaskBlurDescriptor::CreateMaskBlur( // 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); + FilterInput::Make(color_source_contents), sigma, sigma, + Entity::TileMode::kDecal, style, color_source_contents->GetGeometry()); } /// 1. Create an opaque white mask of the original geometry. @@ -141,7 +141,7 @@ std::shared_ptr Paint::MaskBlurDescriptor::CreateMaskBlur( /// 2. Blur the mask. auto blurred_mask = FilterContents::MakeGaussianBlur( - FilterInput::Make(mask), sigma, sigma, style, Entity::TileMode::kDecal); + FilterInput::Make(mask), sigma, sigma, Entity::TileMode::kDecal, style); /// 3. Replace the geometry of the original color source with a rectangle that /// covers the full region of the blurred mask. Note that geometry is in @@ -175,8 +175,8 @@ std::shared_ptr Paint::MaskBlurDescriptor::CreateMaskBlur( const FilterInput::Ref& input, bool is_solid_color) const { if (is_solid_color) { - return FilterContents::MakeGaussianBlur(input, sigma, sigma, style, - Entity::TileMode::kDecal); + return FilterContents::MakeGaussianBlur(input, sigma, sigma, + Entity::TileMode::kDecal, style); } return FilterContents::MakeBorderMaskBlur(input, sigma, sigma, style); } diff --git a/impeller/entity/contents/filters/filter_contents.cc b/impeller/entity/contents/filters/filter_contents.cc index 497f2fe7b2e16..4db42708a4b93 100644 --- a/impeller/entity/contents/filters/filter_contents.cc +++ b/impeller/entity/contents/filters/filter_contents.cc @@ -37,10 +37,11 @@ std::shared_ptr FilterContents::MakeGaussianBlur( const FilterInput::Ref& input, Sigma sigma_x, Sigma sigma_y, - BlurStyle blur_style, - Entity::TileMode tile_mode) { + Entity::TileMode tile_mode, + FilterContents::BlurStyle mask_blur_style, + const std::shared_ptr& mask_geometry) { auto blur = std::make_shared( - sigma_x.sigma, sigma_y.sigma, tile_mode); + sigma_x.sigma, sigma_y.sigma, tile_mode, mask_blur_style, mask_geometry); blur->SetInputs({input}); return blur; } diff --git a/impeller/entity/contents/filters/filter_contents.h b/impeller/entity/contents/filters/filter_contents.h index 58ec988635556..27ecd6c520ed2 100644 --- a/impeller/entity/contents/filters/filter_contents.h +++ b/impeller/entity/contents/filters/filter_contents.h @@ -13,6 +13,7 @@ #include "impeller/core/formats.h" #include "impeller/entity/contents/filters/inputs/filter_input.h" #include "impeller/entity/entity.h" +#include "impeller/entity/geometry/geometry.h" #include "impeller/geometry/matrix.h" #include "impeller/geometry/sigma.h" @@ -41,8 +42,9 @@ class FilterContents : public Contents { const FilterInput::Ref& input, Sigma sigma_x, Sigma sigma_y, - BlurStyle blur_style = BlurStyle::kNormal, - Entity::TileMode tile_mode = Entity::TileMode::kDecal); + Entity::TileMode tile_mode = Entity::TileMode::kDecal, + BlurStyle mask_blur_style = BlurStyle::kNormal, + const std::shared_ptr& mask_geometry = nullptr); static std::shared_ptr MakeBorderMaskBlur( FilterInput::Ref input, diff --git a/impeller/entity/contents/filters/gaussian_blur_filter_contents.cc b/impeller/entity/contents/filters/gaussian_blur_filter_contents.cc index 93d5dd760f3ed..62a2bd490baef 100644 --- a/impeller/entity/contents/filters/gaussian_blur_filter_contents.cc +++ b/impeller/entity/contents/filters/gaussian_blur_filter_contents.cc @@ -6,6 +6,9 @@ #include +#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" @@ -200,6 +203,58 @@ Rect MakeReferenceUVs(const Rect& reference, const Rect& rect) { int ScaleBlurRadius(Scalar radius, Scalar scalar) { return static_cast(std::round(radius * scalar)); } + +Entity ApplyBlurStyle(FilterContents::BlurStyle blur_style, + const Entity& entity, + const std::shared_ptr& input, + const Snapshot& input_snapshot, + Entity blur_entity, + const std::shared_ptr& geometry) { + if (blur_style == FilterContents::BlurStyle::kNormal) { + return blur_entity; + } + Entity::ClipOperation clip_operation; + switch (blur_style) { + case FilterContents::BlurStyle::kNormal: + FML_UNREACHABLE(); + break; + case FilterContents::BlurStyle::kInner: + clip_operation = Entity::ClipOperation::kIntersect; + break; + case FilterContents::BlurStyle::kOuter: + clip_operation = Entity::ClipOperation::kDifference; + break; + case FilterContents::BlurStyle::kSolid: + FML_DLOG(ERROR) << "Unimplemented blur style"; + return blur_entity; + } + + auto shared_blur_entity = std::make_shared(std::move(blur_entity)); + shared_blur_entity->SetNewClipDepth(entity.GetNewClipDepth()); + auto clipper = std::make_unique(); + clipper->SetClipOperation(clip_operation); + clipper->SetGeometry(geometry); + auto restore = std::make_unique(); + Entity result; + result.SetTransform(entity.GetTransform()); + result.SetContents(Contents::MakeAnonymous( + fml::MakeCopyable([shared_blur_entity, clipper = std::move(clipper), + restore = std::move(restore)]( + const ContentContext& renderer, + const Entity& entity, RenderPass& pass) mutable { + bool result = true; + result = clipper->Render(renderer, entity, pass) && result; + result = shared_blur_entity->Render(renderer, pass) && result; + if constexpr (!ContentContext::kEnableStencilThenCover) { + result = restore->Render(renderer, entity, pass) && result; + } + return result; + }), + [shared_blur_entity](const Entity& entity) { + return shared_blur_entity->GetCoverage(); + })); + return result; +} } // namespace std::string_view GaussianBlurFilterContents::kNoMipsError = @@ -208,8 +263,17 @@ std::string_view GaussianBlurFilterContents::kNoMipsError = GaussianBlurFilterContents::GaussianBlurFilterContents( Scalar sigma_x, Scalar sigma_y, - Entity::TileMode tile_mode) - : sigma_x_(sigma_x), sigma_y_(sigma_y), tile_mode_(tile_mode) {} + Entity::TileMode tile_mode, + BlurStyle mask_blur_style, + const std::shared_ptr& 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 @@ -435,7 +499,7 @@ std::optional GaussianBlurFilterContents::RenderFilter( SamplerDescriptor sampler_desc = MakeSamplerDescriptor( MinMagFilter::kLinear, SamplerAddressMode::kClampToEdge); - return Entity::FromSnapshot( + auto blur_output_entity = Entity::FromSnapshot( Snapshot{.texture = pass3_out.value().GetRenderTargetTexture(), .transform = input_snapshot->transform * padding_snapshot_adjustment * @@ -443,6 +507,14 @@ std::optional GaussianBlurFilterContents::RenderFilter( .sampler_descriptor = sampler_desc, .opacity = input_snapshot->opacity}, entity.GetBlendMode(), entity.GetClipDepth()); + + if (!blur_output_entity.has_value()) { + return std::nullopt; + } + + return ApplyBlurStyle(mask_blur_style_, entity, inputs[0], + input_snapshot.value(), + std::move(blur_output_entity.value()), mask_geometry_); } Scalar GaussianBlurFilterContents::CalculateBlurRadius(Scalar sigma) { diff --git a/impeller/entity/contents/filters/gaussian_blur_filter_contents.h b/impeller/entity/contents/filters/gaussian_blur_filter_contents.h index f28e7a22fdbe6..7f545c079a009 100644 --- a/impeller/entity/contents/filters/gaussian_blur_filter_contents.h +++ b/impeller/entity/contents/filters/gaussian_blur_filter_contents.h @@ -8,6 +8,7 @@ #include #include "impeller/entity/contents/content_context.h" #include "impeller/entity/contents/filters/filter_contents.h" +#include "impeller/entity/geometry/geometry.h" namespace impeller { @@ -35,9 +36,12 @@ class GaussianBlurFilterContents final : public FilterContents { static std::string_view kNoMipsError; static const int32_t kBlurFilterRequiredMipCount; - explicit GaussianBlurFilterContents(Scalar sigma_x, - Scalar sigma_y, - Entity::TileMode tile_mode); + explicit GaussianBlurFilterContents( + Scalar sigma_x, + Scalar sigma_y, + Entity::TileMode tile_mode, + BlurStyle mask_blur_style, + const std::shared_ptr& mask_geometry); Scalar GetSigmaX() const { return sigma_x_; } Scalar GetSigmaY() const { return sigma_y_; } @@ -94,6 +98,8 @@ class GaussianBlurFilterContents final : public FilterContents { const Scalar sigma_x_ = 0.0; const Scalar sigma_y_ = 0.0; const Entity::TileMode tile_mode_; + const BlurStyle mask_blur_style_; + std::shared_ptr mask_geometry_; }; } // namespace impeller diff --git a/impeller/entity/contents/filters/gaussian_blur_filter_contents_unittests.cc b/impeller/entity/contents/filters/gaussian_blur_filter_contents_unittests.cc index 7a7de2d62d79f..7dd6e840c2222 100644 --- a/impeller/entity/contents/filters/gaussian_blur_filter_contents_unittests.cc +++ b/impeller/entity/contents/filters/gaussian_blur_filter_contents_unittests.cc @@ -82,15 +82,17 @@ class GaussianBlurFilterContentsTest : public EntityPlayground { INSTANTIATE_PLAYGROUND_SUITE(GaussianBlurFilterContentsTest); TEST(GaussianBlurFilterContentsTest, Create) { - GaussianBlurFilterContents contents(/*sigma_x=*/0.0, /*sigma_y=*/0.0, - Entity::TileMode::kDecal); + GaussianBlurFilterContents contents( + /*sigma_x=*/0.0, /*sigma_y=*/0.0, Entity::TileMode::kDecal, + FilterContents::BlurStyle::kNormal, /*mask_geometry=*/nullptr); EXPECT_EQ(contents.GetSigmaX(), 0.0); EXPECT_EQ(contents.GetSigmaY(), 0.0); } TEST(GaussianBlurFilterContentsTest, CoverageEmpty) { - GaussianBlurFilterContents contents(/*sigma_x=*/0.0, /*sigma_y=*/0.0, - Entity::TileMode::kDecal); + GaussianBlurFilterContents contents( + /*sigma_x=*/0.0, /*sigma_y=*/0.0, Entity::TileMode::kDecal, + FilterContents::BlurStyle::kNormal, /*mask_geometry=*/nullptr); FilterInput::Vector inputs = {}; Entity entity; std::optional coverage = @@ -99,8 +101,9 @@ TEST(GaussianBlurFilterContentsTest, CoverageEmpty) { } TEST(GaussianBlurFilterContentsTest, CoverageSimple) { - GaussianBlurFilterContents contents(/*sigma_x=*/0.0, /*sigma_y=*/0.0, - Entity::TileMode::kDecal); + GaussianBlurFilterContents contents( + /*sigma_x=*/0.0, /*sigma_y=*/0.0, Entity::TileMode::kDecal, + FilterContents::BlurStyle::kNormal, /*mask_geometry=*/nullptr); FilterInput::Vector inputs = { FilterInput::Make(Rect::MakeLTRB(10, 10, 110, 110))}; Entity entity; @@ -113,9 +116,10 @@ TEST(GaussianBlurFilterContentsTest, CoverageWithSigma) { fml::StatusOr sigma_radius_1 = CalculateSigmaForBlurRadius(1.0, Matrix()); ASSERT_TRUE(sigma_radius_1.ok()); - GaussianBlurFilterContents contents(/*sigma_x=*/sigma_radius_1.value(), - /*sigma_y=*/sigma_radius_1.value(), - Entity::TileMode::kDecal); + GaussianBlurFilterContents contents( + /*sigma_x=*/sigma_radius_1.value(), + /*sigma_y=*/sigma_radius_1.value(), Entity::TileMode::kDecal, + FilterContents::BlurStyle::kNormal, /*mask_geometry=*/nullptr); FilterInput::Vector inputs = { FilterInput::Make(Rect::MakeLTRB(100, 100, 200, 200))}; Entity entity; @@ -136,9 +140,10 @@ TEST_P(GaussianBlurFilterContentsTest, CoverageWithTexture) { fml::StatusOr sigma_radius_1 = CalculateSigmaForBlurRadius(1.0, Matrix()); ASSERT_TRUE(sigma_radius_1.ok()); - GaussianBlurFilterContents contents(/*sigma_X=*/sigma_radius_1.value(), - /*sigma_y=*/sigma_radius_1.value(), - Entity::TileMode::kDecal); + GaussianBlurFilterContents contents( + /*sigma_X=*/sigma_radius_1.value(), + /*sigma_y=*/sigma_radius_1.value(), Entity::TileMode::kDecal, + FilterContents::BlurStyle::kNormal, /*mask_geometry=*/nullptr); std::shared_ptr texture = GetContentContext()->GetContext()->GetResourceAllocator()->CreateTexture( desc); @@ -163,9 +168,10 @@ TEST_P(GaussianBlurFilterContentsTest, CoverageWithEffectTransform) { fml::StatusOr sigma_radius_1 = CalculateSigmaForBlurRadius(1.0, effect_transform); ASSERT_TRUE(sigma_radius_1.ok()); - GaussianBlurFilterContents contents(/*sigma_x=*/sigma_radius_1.value(), - /*sigma_y=*/sigma_radius_1.value(), - Entity::TileMode::kDecal); + GaussianBlurFilterContents contents( + /*sigma_x=*/sigma_radius_1.value(), + /*sigma_y=*/sigma_radius_1.value(), Entity::TileMode::kDecal, + FilterContents::BlurStyle::kNormal, /*mask_geometry=*/nullptr); std::shared_ptr texture = GetContentContext()->GetContext()->GetResourceAllocator()->CreateTexture( desc); @@ -186,7 +192,8 @@ TEST(GaussianBlurFilterContentsTest, FilterSourceCoverage) { CalculateSigmaForBlurRadius(1.0, Matrix()); ASSERT_TRUE(sigma_radius_1.ok()); auto contents = std::make_unique( - sigma_radius_1.value(), sigma_radius_1.value(), Entity::TileMode::kDecal); + sigma_radius_1.value(), sigma_radius_1.value(), Entity::TileMode::kDecal, + FilterContents::BlurStyle::kNormal, /*mask_geometry=*/nullptr); std::optional coverage = contents->GetFilterSourceCoverage( /*effect_transform=*/Matrix::MakeScale({2.0, 2.0, 1.0}), /*output_limit=*/Rect::MakeLTRB(100, 100, 200, 200)); @@ -221,7 +228,8 @@ TEST_P(GaussianBlurFilterContentsTest, RenderCoverageMatchesGetCoverage) { CalculateSigmaForBlurRadius(1.0, Matrix()); ASSERT_TRUE(sigma_radius_1.ok()); auto contents = std::make_unique( - sigma_radius_1.value(), sigma_radius_1.value(), Entity::TileMode::kDecal); + sigma_radius_1.value(), sigma_radius_1.value(), Entity::TileMode::kDecal, + FilterContents::BlurStyle::kNormal, /*mask_geometry=*/nullptr); contents->SetInputs({FilterInput::Make(texture)}); std::shared_ptr renderer = GetContentContext(); @@ -256,7 +264,8 @@ TEST_P(GaussianBlurFilterContentsTest, CalculateSigmaForBlurRadius(1.0, Matrix()); ASSERT_TRUE(sigma_radius_1.ok()); auto contents = std::make_unique( - sigma_radius_1.value(), sigma_radius_1.value(), Entity::TileMode::kDecal); + sigma_radius_1.value(), sigma_radius_1.value(), Entity::TileMode::kDecal, + FilterContents::BlurStyle::kNormal, /*mask_geometry=*/nullptr); contents->SetInputs({FilterInput::Make(texture)}); std::shared_ptr renderer = GetContentContext(); @@ -292,7 +301,8 @@ TEST_P(GaussianBlurFilterContentsTest, fml::StatusOr sigma_radius_1 = CalculateSigmaForBlurRadius(1.0, Matrix()); auto contents = std::make_unique( - sigma_radius_1.value(), sigma_radius_1.value(), Entity::TileMode::kDecal); + sigma_radius_1.value(), sigma_radius_1.value(), Entity::TileMode::kDecal, + FilterContents::BlurStyle::kNormal, /*mask_geometry=*/nullptr); contents->SetInputs({FilterInput::Make(texture)}); std::shared_ptr renderer = GetContentContext(); @@ -353,7 +363,8 @@ TEST_P(GaussianBlurFilterContentsTest, TextureContentsWithDestinationRect) { fml::StatusOr sigma_radius_1 = CalculateSigmaForBlurRadius(1.0, Matrix()); auto contents = std::make_unique( - sigma_radius_1.value(), sigma_radius_1.value(), Entity::TileMode::kDecal); + sigma_radius_1.value(), sigma_radius_1.value(), Entity::TileMode::kDecal, + FilterContents::BlurStyle::kNormal, /*mask_geometry=*/nullptr); contents->SetInputs({FilterInput::Make(texture_contents)}); std::shared_ptr renderer = GetContentContext(); @@ -393,7 +404,8 @@ TEST_P(GaussianBlurFilterContentsTest, fml::StatusOr sigma_radius_1 = CalculateSigmaForBlurRadius(1.0, Matrix()); auto contents = std::make_unique( - sigma_radius_1.value(), sigma_radius_1.value(), Entity::TileMode::kDecal); + sigma_radius_1.value(), sigma_radius_1.value(), Entity::TileMode::kDecal, + FilterContents::BlurStyle::kNormal, /*mask_geometry=*/nullptr); contents->SetInputs({FilterInput::Make(texture_contents)}); std::shared_ptr renderer = GetContentContext(); @@ -435,7 +447,8 @@ TEST_P(GaussianBlurFilterContentsTest, TextureContentsWithEffectTransform) { CalculateSigmaForBlurRadius(1.0, effect_transform); ASSERT_TRUE(sigma_radius_1.ok()); auto contents = std::make_unique( - sigma_radius_1.value(), sigma_radius_1.value(), Entity::TileMode::kDecal); + sigma_radius_1.value(), sigma_radius_1.value(), Entity::TileMode::kDecal, + FilterContents::BlurStyle::kNormal, /*mask_geometry=*/nullptr); contents->SetInputs({FilterInput::Make(texture_contents)}); contents->SetEffectTransform(effect_transform); std::shared_ptr renderer = GetContentContext(); diff --git a/impeller/entity/entity_unittests.cc b/impeller/entity/entity_unittests.cc index e84d64853cfde..60debd2841f12 100644 --- a/impeller/entity/entity_unittests.cc +++ b/impeller/entity/entity_unittests.cc @@ -1122,13 +1122,14 @@ TEST_P(EntityTest, GaussianBlurFilter) { case 0: blur = std::make_shared( blur_sigma_x.sigma, blur_sigma_y.sigma, - tile_modes[selected_tile_mode]); + tile_modes[selected_tile_mode], blur_styles[selected_blur_style], + /*geometry=*/nullptr); blur->SetInputs({FilterInput::Make(input)}); break; case 1: blur = FilterContents::MakeGaussianBlur( FilterInput::Make(input), blur_sigma_x, blur_sigma_y, - blur_styles[selected_blur_style], tile_modes[selected_tile_mode]); + tile_modes[selected_tile_mode], blur_styles[selected_blur_style]); break; }; FML_CHECK(blur); diff --git a/testing/impeller_golden_tests_output.txt b/testing/impeller_golden_tests_output.txt index aa6c8488c8049..aeb18abee22e6 100644 --- a/testing/impeller_golden_tests_output.txt +++ b/testing/impeller_golden_tests_output.txt @@ -485,6 +485,12 @@ impeller_Play_AiksTest_GaussianBlurRotatedAndClipped_Vulkan.png impeller_Play_AiksTest_GaussianBlurScaledAndClipped_Metal.png impeller_Play_AiksTest_GaussianBlurScaledAndClipped_OpenGLES.png impeller_Play_AiksTest_GaussianBlurScaledAndClipped_Vulkan.png +impeller_Play_AiksTest_GaussianBlurStyleInner_Metal.png +impeller_Play_AiksTest_GaussianBlurStyleInner_OpenGLES.png +impeller_Play_AiksTest_GaussianBlurStyleInner_Vulkan.png +impeller_Play_AiksTest_GaussianBlurStyleOuter_Metal.png +impeller_Play_AiksTest_GaussianBlurStyleOuter_OpenGLES.png +impeller_Play_AiksTest_GaussianBlurStyleOuter_Vulkan.png impeller_Play_AiksTest_GaussianBlurWithoutDecalSupport_Metal.png impeller_Play_AiksTest_GradientStrokesRenderCorrectly_Metal.png impeller_Play_AiksTest_GradientStrokesRenderCorrectly_OpenGLES.png