Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit bc4abcd

Browse files
authored
[Impeller] Add mask blur style support to the RRect blur fast path. (#51250)
This improves performance by avoiding the 2-pass Gaussian blur in more shadow drawing situations. The new golden also serves as a good reference for how mask blurs are supposed to work alongside various other paint properties such as color filters, color opacity, image filters, and blending. ![Screenshot 2024-03-06 at 11 47 04�PM](https://github.com/flutter/engine/assets/919017/dee1b56c-d2e4-40bd-9f50-aeba0cfd69f2) The top 5 shapes are various RRect cases and are rendering correctly via the new blur style implementation in this patch. The two bottom rows (the triangles and arcs) are non-rrect paths, so they're falling back to rendering using the 2-pass Gaussian blur. Rendering errors are circled in red below: ![Screenshot 2024-03-06 at 11 47 04�PM](https://github.com/flutter/engine/assets/919017/5ac27d71-95e6-4e6e-99d8-7436476e1aee) * Cases 1, 2, 7, and 9 all appear to rendering fine. * Cases 3, 4, 5, and 6 all have mask blur styles set to `BlurStyle::kSolid`. After the first clipped overlay has been drawn, subsequent clipped overlays aren't drawing. * Case 6 is also has the blend mode set to `BlendMode::kExclusion`. * Cases 8 and 10 are rendering with `BlurStyle::kInner` and `BlurStyle::kOuter` respectfully, but with a blur ImageFilter also set on the paint state. The ImageFilter needs to be applied to the rasterized mask blurred content.
1 parent 68f1d69 commit bc4abcd

File tree

4 files changed

+291
-17
lines changed

4 files changed

+291
-17
lines changed

impeller/aiks/aiks_blur_unittests.cc

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,172 @@ TEST_P(AiksTest, MaskBlurWithZeroSigmaIsSkipped) {
282282
ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
283283
}
284284

285+
struct MaskBlurTestConfig {
286+
FilterContents::BlurStyle style = FilterContents::BlurStyle::kNormal;
287+
Scalar sigma = 1.0f;
288+
Scalar alpha = 1.0f;
289+
std::shared_ptr<ImageFilter> image_filter;
290+
bool invert_colors = false;
291+
BlendMode blend_mode = BlendMode::kSourceOver;
292+
};
293+
294+
static Picture MaskBlurVariantTest(const AiksTest& test_context,
295+
const MaskBlurTestConfig& config) {
296+
Canvas canvas;
297+
canvas.Scale(test_context.GetContentScale());
298+
canvas.Scale(Vector2{0.8f, 0.8f});
299+
Paint paint;
300+
paint.mask_blur_descriptor = Paint::MaskBlurDescriptor{
301+
.style = FilterContents::BlurStyle::kNormal,
302+
.sigma = Sigma{1},
303+
};
304+
305+
canvas.DrawPaint({.color = Color::AntiqueWhite()});
306+
307+
paint.mask_blur_descriptor->style = config.style;
308+
paint.mask_blur_descriptor->sigma = Sigma{config.sigma};
309+
paint.image_filter = config.image_filter;
310+
paint.invert_colors = config.invert_colors;
311+
paint.blend_mode = config.blend_mode;
312+
313+
const Scalar x = 50;
314+
const Scalar radius = 20.0f;
315+
const Scalar y_spacing = 100.0f;
316+
317+
Scalar y = 50;
318+
paint.color = Color::Crimson().WithAlpha(config.alpha);
319+
canvas.DrawRect(Rect::MakeXYWH(x + 25 - radius / 2, y + radius / 2, //
320+
radius, 60.0f - radius),
321+
paint);
322+
323+
y += y_spacing;
324+
paint.color = Color::Blue().WithAlpha(config.alpha);
325+
canvas.DrawCircle({x + 25, y + 25}, radius, paint);
326+
327+
y += y_spacing;
328+
paint.color = Color::Green().WithAlpha(config.alpha);
329+
canvas.DrawOval(Rect::MakeXYWH(x + 25 - radius / 2, y + radius / 2, //
330+
radius, 60.0f - radius),
331+
paint);
332+
333+
y += y_spacing;
334+
paint.color = Color::Purple().WithAlpha(config.alpha);
335+
canvas.DrawRRect(Rect::MakeXYWH(x, y, 60.0f, 60.0f), //
336+
{radius, radius}, //
337+
paint);
338+
339+
y += y_spacing;
340+
paint.color = Color::Orange().WithAlpha(config.alpha);
341+
canvas.DrawRRect(Rect::MakeXYWH(x, y, 60.0f, 60.0f), //
342+
{radius, 5.0f}, paint);
343+
344+
y += y_spacing;
345+
paint.color = Color::Maroon().WithAlpha(config.alpha);
346+
canvas.DrawPath(PathBuilder{}
347+
.MoveTo({x + 0, y + 60})
348+
.LineTo({x + 30, y + 0})
349+
.LineTo({x + 60, y + 60})
350+
.Close()
351+
.TakePath(),
352+
paint);
353+
354+
y += y_spacing;
355+
paint.color = Color::Maroon().WithAlpha(config.alpha);
356+
canvas.DrawPath(PathBuilder{}
357+
.AddArc(Rect::MakeXYWH(x + 5, y, 50, 50),
358+
Radians{kPi / 2}, Radians{kPi})
359+
.AddArc(Rect::MakeXYWH(x + 25, y, 50, 50),
360+
Radians{kPi / 2}, Radians{kPi})
361+
.Close()
362+
.TakePath(),
363+
paint);
364+
365+
return canvas.EndRecordingAsPicture();
366+
}
367+
368+
static const std::map<std::string, MaskBlurTestConfig> kPaintVariations = {
369+
// 1. Normal style, translucent, zero sigma.
370+
{"NormalTranslucentZeroSigma",
371+
{.style = FilterContents::BlurStyle::kNormal,
372+
.sigma = 0.0f,
373+
.alpha = 0.5f}},
374+
// 2. Normal style, translucent.
375+
{"NormalTranslucent",
376+
{.style = FilterContents::BlurStyle::kNormal,
377+
.sigma = 8.0f,
378+
.alpha = 0.5f}},
379+
// 3. Solid style, translucent.
380+
{"SolidTranslucent",
381+
{.style = FilterContents::BlurStyle::kSolid,
382+
.sigma = 8.0f,
383+
.alpha = 0.5f}},
384+
// 4. Solid style, opaque.
385+
{"SolidOpaque",
386+
{.style = FilterContents::BlurStyle::kSolid, .sigma = 8.0f}},
387+
// 5. Solid style, translucent, color & image filtered.
388+
{"SolidTranslucentWithFilters",
389+
{.style = FilterContents::BlurStyle::kSolid,
390+
.sigma = 8.0f,
391+
.alpha = 0.5f,
392+
.image_filter = ImageFilter::MakeBlur(Sigma{3},
393+
Sigma{3},
394+
FilterContents::BlurStyle::kNormal,
395+
Entity::TileMode::kClamp),
396+
.invert_colors = true}},
397+
// 6. Solid style, translucent, exclusion blended.
398+
{"SolidTranslucentExclusionBlend",
399+
{.style = FilterContents::BlurStyle::kSolid,
400+
.sigma = 8.0f,
401+
.alpha = 0.5f,
402+
.blend_mode = BlendMode::kExclusion}},
403+
// 7. Inner style, translucent.
404+
{"InnerTranslucent",
405+
{.style = FilterContents::BlurStyle::kInner,
406+
.sigma = 8.0f,
407+
.alpha = 0.5f}},
408+
// 8. Inner style, translucent, blurred.
409+
{"InnerTranslucentWithBlurImageFilter",
410+
{.style = FilterContents::BlurStyle::kInner,
411+
.sigma = 8.0f,
412+
.alpha = 0.5f,
413+
.image_filter = ImageFilter::MakeBlur(Sigma{3},
414+
Sigma{3},
415+
FilterContents::BlurStyle::kNormal,
416+
Entity::TileMode::kClamp)}},
417+
// 9. Outer style, translucent.
418+
{"OuterTranslucent",
419+
{.style = FilterContents::BlurStyle::kOuter,
420+
.sigma = 8.0f,
421+
.alpha = 0.5f}},
422+
// 10. Outer style, opaque, image filtered.
423+
{"OuterOpaqueWithBlurImageFilter",
424+
{.style = FilterContents::BlurStyle::kOuter,
425+
.sigma = 8.0f,
426+
.image_filter = ImageFilter::MakeBlur(Sigma{3},
427+
Sigma{3},
428+
FilterContents::BlurStyle::kNormal,
429+
Entity::TileMode::kClamp)}},
430+
};
431+
432+
#define MASK_BLUR_VARIANT_TEST(config) \
433+
TEST_P(AiksTest, MaskBlurVariantTest##config) { \
434+
ASSERT_TRUE(OpenPlaygroundHere( \
435+
MaskBlurVariantTest(*this, kPaintVariations.at(#config)))); \
436+
}
437+
438+
MASK_BLUR_VARIANT_TEST(NormalTranslucentZeroSigma)
439+
MASK_BLUR_VARIANT_TEST(NormalTranslucent)
440+
MASK_BLUR_VARIANT_TEST(SolidTranslucent)
441+
MASK_BLUR_VARIANT_TEST(SolidOpaque)
442+
MASK_BLUR_VARIANT_TEST(SolidTranslucentWithFilters)
443+
MASK_BLUR_VARIANT_TEST(SolidTranslucentExclusionBlend)
444+
MASK_BLUR_VARIANT_TEST(InnerTranslucent)
445+
MASK_BLUR_VARIANT_TEST(InnerTranslucentWithBlurImageFilter)
446+
MASK_BLUR_VARIANT_TEST(OuterTranslucent)
447+
MASK_BLUR_VARIANT_TEST(OuterOpaqueWithBlurImageFilter)
448+
449+
#undef MASK_BLUR_VARIANT_TEST
450+
285451
TEST_P(AiksTest, GaussianBlurAtPeripheryVertical) {
286452
Canvas canvas;
287453

impeller/aiks/canvas.cc

Lines changed: 94 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -308,41 +308,119 @@ void Canvas::DrawPaint(const Paint& paint) {
308308
}
309309

310310
bool Canvas::AttemptDrawBlurredRRect(const Rect& rect,
311-
Size corner_radius,
311+
Size corner_radii,
312312
const Paint& paint) {
313313
if (paint.color_source.GetType() != ColorSource::Type::kColor ||
314314
paint.style != Paint::Style::kFill) {
315315
return false;
316316
}
317317

318-
if (!paint.mask_blur_descriptor.has_value() ||
319-
paint.mask_blur_descriptor->style != FilterContents::BlurStyle::kNormal) {
318+
if (!paint.mask_blur_descriptor.has_value()) {
320319
return false;
321320
}
321+
322322
// A blur sigma that is not positive enough should not result in a blur.
323323
if (paint.mask_blur_descriptor->sigma.sigma <= kEhCloseEnough) {
324324
return false;
325325
}
326326

327-
Paint new_paint = paint;
328-
329327
// For symmetrically mask blurred solid RRects, absorb the mask blur and use
330328
// a faster SDF approximation.
331329

332-
auto contents = std::make_shared<SolidRRectBlurContents>();
333-
contents->SetColor(new_paint.color);
334-
contents->SetSigma(new_paint.mask_blur_descriptor->sigma);
335-
contents->SetRRect(rect, corner_radius);
330+
Color rrect_color =
331+
paint.HasColorFilter()
332+
// Absorb the color filter, if any.
333+
? paint.GetColorFilter()->GetCPUColorFilterProc()(paint.color)
334+
: paint.color;
335+
336+
Paint rrect_paint = {.mask_blur_descriptor = paint.mask_blur_descriptor};
337+
338+
// In some cases, we need to render the mask blur to a separate layer.
339+
//
340+
// 1. If the blur style is normal, we'll be drawing using one draw call and
341+
// no clips. And so we can just wrap the RRect contents with the
342+
// ImageFilter, which will get applied to the result as per usual.
343+
//
344+
// 2. If the blur style is solid, we combine the non-blurred RRect with the
345+
// blurred RRect via two separate draw calls, and so we need to defer any
346+
// fancy blending, translucency, or image filtering until after these two
347+
// draws have been combined in a separate layer.
348+
//
349+
// 3. If the blur style is outer or inner, we apply the blur style via a
350+
// clip. The ImageFilter needs to be applied to the mask blurred result.
351+
// And so if there's an ImageFilter, we need to defer applying it until
352+
// after the clipped RRect blur has been drawn to a separate texture.
353+
// However, since there's only one draw call that produces color, we
354+
// don't need to worry about the blend mode or translucency (unlike with
355+
// BlurStyle::kSolid).
356+
//
357+
if ((paint.mask_blur_descriptor->style !=
358+
FilterContents::BlurStyle::kNormal &&
359+
paint.image_filter) ||
360+
(paint.mask_blur_descriptor->style == FilterContents::BlurStyle::kSolid &&
361+
(!rrect_color.IsOpaque() ||
362+
paint.blend_mode != BlendMode::kSourceOver))) {
363+
// Defer the alpha, blend mode, and image filter to a separate layer.
364+
SaveLayer({.color = Color::White().WithAlpha(rrect_color.alpha),
365+
.blend_mode = paint.blend_mode,
366+
.image_filter = paint.image_filter});
367+
rrect_paint.color = rrect_color.WithAlpha(1);
368+
} else {
369+
rrect_paint.color = rrect_color;
370+
rrect_paint.blend_mode = paint.blend_mode;
371+
rrect_paint.image_filter = paint.image_filter;
372+
Save();
373+
}
336374

337-
new_paint.mask_blur_descriptor = std::nullopt;
375+
auto draw_blurred_rrect = [this, &rect, &corner_radii, &rrect_paint]() {
376+
auto contents = std::make_shared<SolidRRectBlurContents>();
338377

339-
Entity entity;
340-
entity.SetTransform(GetCurrentTransform());
341-
entity.SetClipDepth(GetClipDepth());
342-
entity.SetBlendMode(new_paint.blend_mode);
343-
entity.SetContents(new_paint.WithFilters(std::move(contents)));
378+
contents->SetColor(rrect_paint.color);
379+
contents->SetSigma(rrect_paint.mask_blur_descriptor->sigma);
380+
contents->SetRRect(rect, corner_radii);
344381

345-
AddEntityToCurrentPass(std::move(entity));
382+
Entity blurred_rrect_entity;
383+
blurred_rrect_entity.SetTransform(GetCurrentTransform());
384+
blurred_rrect_entity.SetClipDepth(GetClipDepth());
385+
blurred_rrect_entity.SetBlendMode(rrect_paint.blend_mode);
386+
387+
rrect_paint.mask_blur_descriptor = std::nullopt;
388+
blurred_rrect_entity.SetContents(
389+
rrect_paint.WithFilters(std::move(contents)));
390+
AddEntityToCurrentPass(std::move(blurred_rrect_entity));
391+
};
392+
393+
switch (rrect_paint.mask_blur_descriptor->style) {
394+
case FilterContents::BlurStyle::kNormal: {
395+
draw_blurred_rrect();
396+
break;
397+
}
398+
case FilterContents::BlurStyle::kSolid: {
399+
// First, draw the blurred RRect.
400+
draw_blurred_rrect();
401+
// Then, draw the non-blurred RRect on top.
402+
Entity entity;
403+
entity.SetTransform(GetCurrentTransform());
404+
entity.SetClipDepth(GetClipDepth());
405+
entity.SetBlendMode(rrect_paint.blend_mode);
406+
entity.SetContents(CreateContentsForGeometryWithFilters(
407+
rrect_paint, Geometry::MakeRoundRect(rect, corner_radii)));
408+
AddEntityToCurrentPass(std::move(entity));
409+
break;
410+
}
411+
case FilterContents::BlurStyle::kOuter: {
412+
ClipRRect(rect, corner_radii, Entity::ClipOperation::kDifference);
413+
draw_blurred_rrect();
414+
break;
415+
}
416+
case FilterContents::BlurStyle::kInner: {
417+
ClipRRect(rect, corner_radii, Entity::ClipOperation::kIntersect);
418+
draw_blurred_rrect();
419+
break;
420+
}
421+
}
422+
423+
Restore();
346424

347425
return true;
348426
}

impeller/aiks/canvas.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,7 @@ class Canvas {
207207
void RestoreClip();
208208

209209
bool AttemptDrawBlurredRRect(const Rect& rect,
210-
Size corner_radius,
210+
Size corner_radii,
211211
const Paint& paint);
212212

213213
Canvas(const Canvas&) = delete;

testing/impeller_golden_tests_output.txt

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -516,6 +516,36 @@ impeller_Play_AiksTest_ImageFilteredUnboundedSaveLayerWithUnboundedContents_Vulk
516516
impeller_Play_AiksTest_LinearToSrgbFilterSubpassCollapseOptimization_Metal.png
517517
impeller_Play_AiksTest_LinearToSrgbFilterSubpassCollapseOptimization_OpenGLES.png
518518
impeller_Play_AiksTest_LinearToSrgbFilterSubpassCollapseOptimization_Vulkan.png
519+
impeller_Play_AiksTest_MaskBlurVariantTestInnerTranslucentWithBlurImageFilter_Metal.png
520+
impeller_Play_AiksTest_MaskBlurVariantTestInnerTranslucentWithBlurImageFilter_OpenGLES.png
521+
impeller_Play_AiksTest_MaskBlurVariantTestInnerTranslucentWithBlurImageFilter_Vulkan.png
522+
impeller_Play_AiksTest_MaskBlurVariantTestInnerTranslucent_Metal.png
523+
impeller_Play_AiksTest_MaskBlurVariantTestInnerTranslucent_OpenGLES.png
524+
impeller_Play_AiksTest_MaskBlurVariantTestInnerTranslucent_Vulkan.png
525+
impeller_Play_AiksTest_MaskBlurVariantTestNormalTranslucentZeroSigma_Metal.png
526+
impeller_Play_AiksTest_MaskBlurVariantTestNormalTranslucentZeroSigma_OpenGLES.png
527+
impeller_Play_AiksTest_MaskBlurVariantTestNormalTranslucentZeroSigma_Vulkan.png
528+
impeller_Play_AiksTest_MaskBlurVariantTestNormalTranslucent_Metal.png
529+
impeller_Play_AiksTest_MaskBlurVariantTestNormalTranslucent_OpenGLES.png
530+
impeller_Play_AiksTest_MaskBlurVariantTestNormalTranslucent_Vulkan.png
531+
impeller_Play_AiksTest_MaskBlurVariantTestOuterOpaqueWithBlurImageFilter_Metal.png
532+
impeller_Play_AiksTest_MaskBlurVariantTestOuterOpaqueWithBlurImageFilter_OpenGLES.png
533+
impeller_Play_AiksTest_MaskBlurVariantTestOuterOpaqueWithBlurImageFilter_Vulkan.png
534+
impeller_Play_AiksTest_MaskBlurVariantTestOuterTranslucent_Metal.png
535+
impeller_Play_AiksTest_MaskBlurVariantTestOuterTranslucent_OpenGLES.png
536+
impeller_Play_AiksTest_MaskBlurVariantTestOuterTranslucent_Vulkan.png
537+
impeller_Play_AiksTest_MaskBlurVariantTestSolidOpaque_Metal.png
538+
impeller_Play_AiksTest_MaskBlurVariantTestSolidOpaque_OpenGLES.png
539+
impeller_Play_AiksTest_MaskBlurVariantTestSolidOpaque_Vulkan.png
540+
impeller_Play_AiksTest_MaskBlurVariantTestSolidTranslucentExclusionBlend_Metal.png
541+
impeller_Play_AiksTest_MaskBlurVariantTestSolidTranslucentExclusionBlend_OpenGLES.png
542+
impeller_Play_AiksTest_MaskBlurVariantTestSolidTranslucentExclusionBlend_Vulkan.png
543+
impeller_Play_AiksTest_MaskBlurVariantTestSolidTranslucentWithFilters_Metal.png
544+
impeller_Play_AiksTest_MaskBlurVariantTestSolidTranslucentWithFilters_OpenGLES.png
545+
impeller_Play_AiksTest_MaskBlurVariantTestSolidTranslucentWithFilters_Vulkan.png
546+
impeller_Play_AiksTest_MaskBlurVariantTestSolidTranslucent_Metal.png
547+
impeller_Play_AiksTest_MaskBlurVariantTestSolidTranslucent_OpenGLES.png
548+
impeller_Play_AiksTest_MaskBlurVariantTestSolidTranslucent_Vulkan.png
519549
impeller_Play_AiksTest_MaskBlurWithZeroSigmaIsSkipped_Metal.png
520550
impeller_Play_AiksTest_MaskBlurWithZeroSigmaIsSkipped_OpenGLES.png
521551
impeller_Play_AiksTest_MaskBlurWithZeroSigmaIsSkipped_Vulkan.png

0 commit comments

Comments
 (0)