| Flutter Impeller |

aiks_blur_unittests.cc
Go to the documentation of this file.
1 // Copyright 2013 The Flutter Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "flutter/impeller/aiks/aiks_unittests.h"
6
7 #include "impeller/aiks/canvas.h"
8 #include "impeller/entity/contents/filters/gaussian_blur_filter_contents.h"
9 #include "impeller/entity/render_target_cache.h"
10 #include "impeller/geometry/path_builder.h"
11 #include "impeller/playground/widgets.h"
12 #include "impeller/renderer/testing/mocks.h"
13 #include "third_party/imgui/imgui.h"
14
15 ////////////////////////////////////////////////////////////////////////////////
16 // This is for tests of Canvas that are interested the results of rendering
17 // blurs.
18 ////////////////////////////////////////////////////////////////////////////////
19
20 namespace impeller {
21 namespace testing {
22
23 TEST_P(AiksTest, CanRenderMaskBlurHugeSigma) {
24 Canvas canvas;
25 canvas.DrawCircle({400, 400}, 300,
26 {.color = Color::Green(),
27 .mask_blur_descriptor = Paint::MaskBlurDescriptor{
28 .style = FilterContents::BlurStyle::kNormal,
29 .sigma = Sigma(99999),
30 }});
31 canvas.Restore();
32
33 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
34 }
35
36 TEST_P(AiksTest, CanRenderForegroundBlendWithMaskBlur) {
37 // This case triggers the ForegroundPorterDuffBlend path. The color filter
38 // should apply to the color only, and respect the alpha mask.
39 Canvas canvas;
40 canvas.ClipRect(Rect::MakeXYWH(100, 150, 400, 400));
41 canvas.DrawCircle({400, 400}, 200,
42 {
43 .color = Color::White(),
44 .color_filter = ColorFilter::MakeBlend(
45 BlendMode::kSource, Color::Green()),
46 .mask_blur_descriptor =
47 Paint::MaskBlurDescriptor{
48 .style = FilterContents::BlurStyle::kNormal,
49 .sigma = Radius(20),
50 },
51 });
52 canvas.Restore();
53
54 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
55 }
56
57 TEST_P(AiksTest, CanRenderForegroundAdvancedBlendWithMaskBlur) {
58 // This case triggers the ForegroundAdvancedBlend path. The color filter
59 // should apply to the color only, and respect the alpha mask.
60 Canvas canvas;
61 canvas.ClipRect(Rect::MakeXYWH(100, 150, 400, 400));
62 canvas.DrawCircle({400, 400}, 200,
63 {
64 .color = Color::Grey(),
65 .color_filter = ColorFilter::MakeBlend(
66 BlendMode::kColor, Color::Green()),
67 .mask_blur_descriptor =
68 Paint::MaskBlurDescriptor{
69 .style = FilterContents::BlurStyle::kNormal,
70 .sigma = Radius(20),
71 },
72 });
73 canvas.Restore();
74
75 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
76 }
77
78 TEST_P(AiksTest, CanRenderBackdropBlurInteractive) {
79 auto callback = [&](AiksContext& renderer) -> std::optional<Picture> {
80 static PlaygroundPoint point_a(Point(50, 50), 30, Color::White());
81 static PlaygroundPoint point_b(Point(300, 200), 30, Color::White());
82 auto [a, b] = DrawPlaygroundLine(point_a, point_b);
83
84 Canvas canvas;
85 canvas.DrawCircle({100, 100}, 50, {.color = Color::CornflowerBlue()});
86 canvas.DrawCircle({300, 200}, 100, {.color = Color::GreenYellow()});
87 canvas.DrawCircle({140, 170}, 75, {.color = Color::DarkMagenta()});
88 canvas.DrawCircle({180, 120}, 100, {.color = Color::OrangeRed()});
89 canvas.ClipRRect(Rect::MakeLTRB(a.x, a.y, b.x, b.y), {20, 20});
90 canvas.SaveLayer({.blend_mode = BlendMode::kSource}, std::nullopt,
91 ImageFilter::MakeBlur(Sigma(20.0), Sigma(20.0),
92 FilterContents::BlurStyle::kNormal,
93 Entity::TileMode::kClamp));
94 canvas.Restore();
95
96 return canvas.EndRecordingAsPicture();
97 };
98
99 ASSERT_TRUE(OpenPlaygroundHere(callback));
100 }
101
102 TEST_P(AiksTest, CanRenderBackdropBlur) {
103 Canvas canvas;
104 canvas.DrawCircle({100, 100}, 50, {.color = Color::CornflowerBlue()});
105 canvas.DrawCircle({300, 200}, 100, {.color = Color::GreenYellow()});
106 canvas.DrawCircle({140, 170}, 75, {.color = Color::DarkMagenta()});
107 canvas.DrawCircle({180, 120}, 100, {.color = Color::OrangeRed()});
108 canvas.ClipRRect(Rect::MakeLTRB(75, 50, 375, 275), {20, 20});
109 canvas.SaveLayer({.blend_mode = BlendMode::kSource}, std::nullopt,
110 ImageFilter::MakeBlur(Sigma(30.0), Sigma(30.0),
111 FilterContents::BlurStyle::kNormal,
112 Entity::TileMode::kClamp));
113 canvas.Restore();
114
115 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
116 }
117
118 TEST_P(AiksTest, CanRenderBackdropBlurHugeSigma) {
119 Canvas canvas;
120 canvas.DrawCircle({400, 400}, 300, {.color = Color::Green()});
121 canvas.SaveLayer({.blend_mode = BlendMode::kSource}, std::nullopt,
122 ImageFilter::MakeBlur(Sigma(999999), Sigma(999999),
123 FilterContents::BlurStyle::kNormal,
124 Entity::TileMode::kClamp));
125 canvas.Restore();
126
127 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
128 }
129
130 TEST_P(AiksTest, CanRenderClippedBlur) {
131 Canvas canvas;
132 canvas.ClipRect(Rect::MakeXYWH(100, 150, 400, 400));
133 canvas.DrawCircle(
134 {400, 400}, 200,
135 {
136 .color = Color::Green(),
137 .image_filter = ImageFilter::MakeBlur(
138 Sigma(20.0), Sigma(20.0), FilterContents::BlurStyle::kNormal,
139 Entity::TileMode::kDecal),
140 });
141 canvas.Restore();
142
143 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
144 }
145
146 TEST_P(AiksTest, ClippedBlurFilterRendersCorrectlyInteractive) {
147 auto callback = [&](AiksContext& renderer) -> std::optional<Picture> {
148 static PlaygroundPoint playground_point(Point(400, 400), 20,
149 Color::Green());
150 auto point = DrawPlaygroundPoint(playground_point);
151
152 Canvas canvas;
153 canvas.Translate(point - Point(400, 400));
154 Paint paint;
155 paint.mask_blur_descriptor = Paint::MaskBlurDescriptor{
156 .style = FilterContents::BlurStyle::kNormal,
157 .sigma = Radius{120 * 3},
158 };
159 paint.color = Color::Red();
160 PathBuilder builder{};
161 builder.AddRect(Rect::MakeLTRB(0, 0, 800, 800));
162 canvas.DrawPath(builder.TakePath(), paint);
163 return canvas.EndRecordingAsPicture();
164 };
165 ASSERT_TRUE(OpenPlaygroundHere(callback));
166 }
167
168 TEST_P(AiksTest, ClippedBlurFilterRendersCorrectly) {
169 Canvas canvas;
170 canvas.Translate(Point(0, -400));
171 Paint paint;
172 paint.mask_blur_descriptor = Paint::MaskBlurDescriptor{
173 .style = FilterContents::BlurStyle::kNormal,
174 .sigma = Radius{120 * 3},
175 };
176 paint.color = Color::Red();
177 PathBuilder builder{};
178 builder.AddRect(Rect::MakeLTRB(0, 0, 800, 800));
179 canvas.DrawPath(builder.TakePath(), paint);
180 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
181 }
182
183 TEST_P(AiksTest, ClearBlendWithBlur) {
184 Canvas canvas;
185 Paint white;
186 white.color = Color::Blue();
187 canvas.DrawRect(Rect::MakeXYWH(0, 0, 600.0, 600.0), white);
188
189 Paint clear;
190 clear.blend_mode = BlendMode::kClear;
191 clear.mask_blur_descriptor = Paint::MaskBlurDescriptor{
192 .style = FilterContents::BlurStyle::kNormal,
193 .sigma = Sigma(20),
194 };
195
196 canvas.DrawCircle(Point::MakeXY(300.0, 300.0), 200.0, clear);
197
198 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
199 }
200
201 TEST_P(AiksTest, BlurHasNoEdge) {
202 Canvas canvas;
203 canvas.Scale(GetContentScale());
204 canvas.DrawPaint({});
205 Paint blur = {
206 .color = Color::Green(),
207 .mask_blur_descriptor =
208 Paint::MaskBlurDescriptor{
209 .style = FilterContents::BlurStyle::kNormal,
210 .sigma = Sigma(47.6),
211 },
212 };
213 canvas.DrawRect(Rect::MakeXYWH(300, 300, 200, 200), blur);
214 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
215 }
216
217 TEST_P(AiksTest, BlurredRectangleWithShader) {
218 Canvas canvas;
219 canvas.Scale(GetContentScale());
220
221 auto paint_lines = [&canvas](Scalar dx, Scalar dy, Paint paint) {
222 auto draw_line = [&canvas, &paint](Point a, Point b) {
223 canvas.DrawPath(PathBuilder{}.AddLine(a, b).TakePath(), paint);
224 };
225 paint.stroke_width = 5;
226 paint.style = Paint::Style::kStroke;
227 draw_line(Point(dx + 100, dy + 100), Point(dx + 200, dy + 200));
228 draw_line(Point(dx + 100, dy + 200), Point(dx + 200, dy + 100));
229 draw_line(Point(dx + 150, dy + 100), Point(dx + 200, dy + 150));
230 draw_line(Point(dx + 100, dy + 150), Point(dx + 150, dy + 200));
231 };
232
233 AiksContext renderer(GetContext(), nullptr);
234 Canvas recorder_canvas;
235 for (int x = 0; x < 5; ++x) {
236 for (int y = 0; y < 5; ++y) {
237 Rect rect = Rect::MakeXYWH(x * 20, y * 20, 20, 20);
238 Paint paint{.color =
239 ((x + y) & 1) == 0 ? Color::Yellow() : Color::Blue()};
240 recorder_canvas.DrawRect(rect, paint);
241 }
242 }
243 Picture picture = recorder_canvas.EndRecordingAsPicture();
244 std::shared_ptr<Texture> texture =
245 picture.ToImage(renderer, ISize{100, 100})->GetTexture();
246
247 ColorSource image_source = ColorSource::MakeImage(
248 texture, Entity::TileMode::kRepeat, Entity::TileMode::kRepeat, {}, {});
249 std::shared_ptr<ImageFilter> blur_filter = ImageFilter::MakeBlur(
250 Sigma(5), Sigma(5), FilterContents::BlurStyle::kNormal,
251 Entity::TileMode::kDecal);
252 canvas.DrawRect(Rect::MakeLTRB(0, 0, 300, 600),
253 Paint{.color = Color::DarkGreen()});
254 canvas.DrawRect(Rect::MakeLTRB(100, 100, 200, 200),
255 Paint{.color_source = image_source});
256 canvas.DrawRect(Rect::MakeLTRB(300, 0, 600, 600),
257 Paint{.color = Color::Red()});
258 canvas.DrawRect(
259 Rect::MakeLTRB(400, 100, 500, 200),
260 Paint{.color_source = image_source, .image_filter = blur_filter});
261 paint_lines(0, 300, Paint{.color_source = image_source});
262 paint_lines(300, 300,
263 Paint{.color_source = image_source, .image_filter = blur_filter});
264 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
265 }
266
267 TEST_P(AiksTest, MaskBlurWithZeroSigmaIsSkipped) {
268 Canvas canvas;
269
270 Paint paint = {
271 .color = Color::Blue(),
272 .mask_blur_descriptor =
273 Paint::MaskBlurDescriptor{
274 .style = FilterContents::BlurStyle::kNormal,
275 .sigma = Sigma(0),
276 },
277 };
278
279 canvas.DrawCircle({300, 300}, 200, paint);
280 canvas.DrawRect(Rect::MakeLTRB(100, 300, 500, 600), paint);
281
282 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
283 }
284
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
451 TEST_P(AiksTest, GaussianBlurAtPeripheryVertical) {
452 Canvas canvas;
453
454 canvas.Scale(GetContentScale());
455 canvas.DrawRRect(Rect::MakeLTRB(0, 0, GetWindowSize().width, 100),
456 Size(10, 10), Paint{.color = Color::LimeGreen()});
457 canvas.DrawRRect(Rect::MakeLTRB(0, 110, GetWindowSize().width, 210),
458 Size(10, 10), Paint{.color = Color::Magenta()});
459 canvas.ClipRect(Rect::MakeLTRB(100, 0, 200, GetWindowSize().height));
460 canvas.SaveLayer({.blend_mode = BlendMode::kSource}, std::nullopt,
461 ImageFilter::MakeBlur(Sigma(20.0), Sigma(20.0),
462 FilterContents::BlurStyle::kNormal,
463 Entity::TileMode::kClamp));
464 canvas.Restore();
465
466 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
467 }
468
469 TEST_P(AiksTest, GaussianBlurAtPeripheryHorizontal) {
470 Canvas canvas;
471
472 canvas.Scale(GetContentScale());
473 std::shared_ptr<Texture> boston = CreateTextureForFixture("boston.jpg");
474 canvas.DrawImageRect(
475 std::make_shared<Image>(boston),
476 Rect::MakeXYWH(0, 0, boston->GetSize().width, boston->GetSize().height),
477 Rect::MakeLTRB(0, 0, GetWindowSize().width, 100), Paint{});
478 canvas.DrawRRect(Rect::MakeLTRB(0, 110, GetWindowSize().width, 210),
479 Size(10, 10), Paint{.color = Color::Magenta()});
480 canvas.ClipRect(Rect::MakeLTRB(0, 50, GetWindowSize().width, 150));
481 canvas.SaveLayer({.blend_mode = BlendMode::kSource}, std::nullopt,
482 ImageFilter::MakeBlur(Sigma(20.0), Sigma(20.0),
483 FilterContents::BlurStyle::kNormal,
484 Entity::TileMode::kClamp));
485 canvas.Restore();
486 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
487 }
488
489 #define FLT_FORWARD(mock, real, method) \
490 EXPECT_CALL(*mock, method()) \
491 .WillRepeatedly(::testing::Return(real->method()));
492
493 TEST_P(AiksTest, GaussianBlurWithoutDecalSupport) {
494 if (GetParam() != PlaygroundBackend::kMetal) {
495 GTEST_SKIP_(
496 "This backend doesn't yet support setting device capabilities.");
497 }
498 if (!WillRenderSomething()) {
499 // Sometimes these tests are run without playgrounds enabled which is
500 // pointless for this test since we are asserting that
501 // `SupportsDecalSamplerAddressMode` is called.
502 GTEST_SKIP_("This test requires playgrounds.");
503 }
504
505 std::shared_ptr<const Capabilities> old_capabilities =
506 GetContext()->GetCapabilities();
507 auto mock_capabilities = std::make_shared<MockCapabilities>();
508 EXPECT_CALL(*mock_capabilities, SupportsDecalSamplerAddressMode())
509 .Times(::testing::AtLeast(1))
510 .WillRepeatedly(::testing::Return(false));
511 FLT_FORWARD(mock_capabilities, old_capabilities, GetDefaultColorFormat);
512 FLT_FORWARD(mock_capabilities, old_capabilities, GetDefaultStencilFormat);
513 FLT_FORWARD(mock_capabilities, old_capabilities,
514 GetDefaultDepthStencilFormat);
515 FLT_FORWARD(mock_capabilities, old_capabilities, SupportsOffscreenMSAA);
516 FLT_FORWARD(mock_capabilities, old_capabilities,
517 SupportsImplicitResolvingMSAA);
518 FLT_FORWARD(mock_capabilities, old_capabilities, SupportsReadFromResolve);
519 FLT_FORWARD(mock_capabilities, old_capabilities, SupportsFramebufferFetch);
520 FLT_FORWARD(mock_capabilities, old_capabilities, SupportsSSBO);
521 FLT_FORWARD(mock_capabilities, old_capabilities, SupportsCompute);
522 FLT_FORWARD(mock_capabilities, old_capabilities,
523 SupportsTextureToTextureBlits);
524 FLT_FORWARD(mock_capabilities, old_capabilities, GetDefaultGlyphAtlasFormat);
525 ASSERT_TRUE(SetCapabilities(mock_capabilities).ok());
526
527 auto texture = std::make_shared<Image>(CreateTextureForFixture("boston.jpg"));
528 Canvas canvas;
529 canvas.Scale(GetContentScale() * 0.5);
530 canvas.DrawPaint({.color = Color::Black()});
531 canvas.DrawImage(
532 texture, Point(200, 200),
533 {
534 .image_filter = ImageFilter::MakeBlur(
535 Sigma(20.0), Sigma(20.0), FilterContents::BlurStyle::kNormal,
536 Entity::TileMode::kDecal),
537 });
538 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
539 }
540
541 TEST_P(AiksTest, GaussianBlurOneDimension) {
542 Canvas canvas;
543
544 canvas.Scale(GetContentScale());
545 canvas.Scale({0.5, 0.5, 1.0});
546 std::shared_ptr<Texture> boston = CreateTextureForFixture("boston.jpg");
547 canvas.DrawImage(std::make_shared<Image>(boston), Point(100, 100), Paint{});
548 canvas.SaveLayer({.blend_mode = BlendMode::kSource}, std::nullopt,
549 ImageFilter::MakeBlur(Sigma(50.0), Sigma(0.0),
550 FilterContents::BlurStyle::kNormal,
551 Entity::TileMode::kClamp));
552 canvas.Restore();
553 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
554 }
555
556 // Smoketest to catch issues with the coverage hint.
557 // Draws a rotated blurred image within a rectangle clip. The center of the clip
558 // rectangle is the center of the rotated image. The entire area of the clip
559 // rectangle should be filled with opaque colors output by the blur.
560 TEST_P(AiksTest, GaussianBlurRotatedAndClipped) {
561 Canvas canvas;
562 std::shared_ptr<Texture> boston = CreateTextureForFixture("boston.jpg");
563 Rect bounds =
564 Rect::MakeXYWH(0, 0, boston->GetSize().width, boston->GetSize().height);
565 Vector2 image_center = Vector2(bounds.GetSize() / 2);
566 Paint paint = {.image_filter =
567 ImageFilter::MakeBlur(Sigma(20.0), Sigma(20.0),
568 FilterContents::BlurStyle::kNormal,
569 Entity::TileMode::kDecal)};
570 Vector2 clip_size = {150, 75};
571 Vector2 center = Vector2(1024, 768) / 2;
572 canvas.Scale(GetContentScale());
573 canvas.ClipRect(
574 Rect::MakeLTRB(center.x, center.y, center.x, center.y).Expand(clip_size));
575 canvas.Translate({center.x, center.y, 0});
576 canvas.Scale({0.6, 0.6, 1});
577 canvas.Rotate(Degrees(25));
578
579 canvas.DrawImageRect(std::make_shared<Image>(boston), /*source=*/bounds,
580 /*dest=*/bounds.Shift(-image_center), paint);
581
582 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
583 }
584
585 TEST_P(AiksTest, GaussianBlurScaledAndClipped) {
586 Canvas canvas;
587 std::shared_ptr<Texture> boston = CreateTextureForFixture("boston.jpg");
588 Rect bounds =
589 Rect::MakeXYWH(0, 0, boston->GetSize().width, boston->GetSize().height);
590 Vector2 image_center = Vector2(bounds.GetSize() / 2);
591 Paint paint = {.image_filter =
592 ImageFilter::MakeBlur(Sigma(20.0), Sigma(20.0),
593 FilterContents::BlurStyle::kNormal,
594 Entity::TileMode::kDecal)};
595 Vector2 clip_size = {150, 75};
596 Vector2 center = Vector2(1024, 768) / 2;
597 canvas.Scale(GetContentScale());
598 canvas.ClipRect(
599 Rect::MakeLTRB(center.x, center.y, center.x, center.y).Expand(clip_size));
600 canvas.Translate({center.x, center.y, 0});
601 canvas.Scale({0.6, 0.6, 1});
602
603 canvas.DrawImageRect(std::make_shared<Image>(boston), /*source=*/bounds,
604 /*dest=*/bounds.Shift(-image_center), paint);
605
606 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
607 }
608
609 TEST_P(AiksTest, GaussianBlurRotatedAndClippedInteractive) {
610 std::shared_ptr<Texture> boston = CreateTextureForFixture("boston.jpg");
611
612 auto callback = [&](AiksContext& renderer) -> std::optional<Picture> {
613 const char* tile_mode_names[] = {"Clamp", "Repeat", "Mirror", "Decal"};
614 const Entity::TileMode tile_modes[] = {
615 Entity::TileMode::kClamp, Entity::TileMode::kRepeat,
616 Entity::TileMode::kMirror, Entity::TileMode::kDecal};
617
618 static float rotation = 0;
619 static float scale = 0.6;
620 static int selected_tile_mode = 3;
621
622 if (AiksTest::ImGuiBegin("Controls", nullptr,
623 ImGuiWindowFlags_AlwaysAutoResize)) {
624 ImGui::SliderFloat("Rotation (degrees)", &rotation, -180, 180);
625 ImGui::SliderFloat("Scale", &scale, 0, 2.0);
626 ImGui::Combo("Tile mode", &selected_tile_mode, tile_mode_names,
627 sizeof(tile_mode_names) / sizeof(char*));
628 ImGui::End();
629 }
630
631 Canvas canvas;
632 Rect bounds =
633 Rect::MakeXYWH(0, 0, boston->GetSize().width, boston->GetSize().height);
634 Vector2 image_center = Vector2(bounds.GetSize() / 2);
635 Paint paint = {.image_filter =
636 ImageFilter::MakeBlur(Sigma(20.0), Sigma(20.0),
637 FilterContents::BlurStyle::kNormal,
638 tile_modes[selected_tile_mode])};
639 static PlaygroundPoint point_a(Point(362, 309), 20, Color::Red());
640 static PlaygroundPoint point_b(Point(662, 459), 20, Color::Red());
641 auto [handle_a, handle_b] = DrawPlaygroundLine(point_a, point_b);
642 Vector2 center = Vector2(1024, 768) / 2;
643 canvas.Scale(GetContentScale());
644 canvas.ClipRect(
645 Rect::MakeLTRB(handle_a.x, handle_a.y, handle_b.x, handle_b.y));
646 canvas.Translate({center.x, center.y, 0});
647 canvas.Scale({scale, scale, 1});
648 canvas.Rotate(Degrees(rotation));
649
650 canvas.DrawImageRect(std::make_shared<Image>(boston), /*source=*/bounds,
651 /*dest=*/bounds.Shift(-image_center), paint);
652 return canvas.EndRecordingAsPicture();
653 };
654
655 ASSERT_TRUE(OpenPlaygroundHere(callback));
656 }
657
658 // This addresses a bug where tiny blurs could result in mip maps that beyond
659 // the limits for the textures used for blurring.
660 // See also: b/323402168
661 TEST_P(AiksTest, GaussianBlurSolidColorTinyMipMap) {
662 for (int32_t i = 1; i < 5; ++i) {
663 Canvas canvas;
664 Scalar fi = i;
665 canvas.DrawPath(
666 PathBuilder{}
667 .MoveTo({100, 100})
668 .LineTo({100.f + fi, 100.f + fi})
669 .TakePath(),
670 {.color = Color::Chartreuse(),
671 .image_filter = ImageFilter::MakeBlur(
672 Sigma(0.1), Sigma(0.1), FilterContents::BlurStyle::kNormal,
673 Entity::TileMode::kClamp)});
674
675 Picture picture = canvas.EndRecordingAsPicture();
676 std::shared_ptr<RenderTargetCache> cache =
677 std::make_shared<RenderTargetCache>(
678 GetContext()->GetResourceAllocator());
679 AiksContext aiks_context(GetContext(), nullptr, cache);
680 std::shared_ptr<Image> image = picture.ToImage(aiks_context, {1024, 768});
681 EXPECT_TRUE(image) << " length " << i;
682 }
683 }
684
685 // This addresses a bug where tiny blurs could result in mip maps that beyond
686 // the limits for the textures used for blurring.
687 // See also: b/323402168
688 TEST_P(AiksTest, GaussianBlurBackdropTinyMipMap) {
689 for (int32_t i = 0; i < 5; ++i) {
690 Canvas canvas;
691 ISize clip_size = ISize(i, i);
692 canvas.ClipRect(
693 Rect::MakeXYWH(400, 400, clip_size.width, clip_size.height));
694 canvas.DrawCircle(
695 {400, 400}, 200,
696 {
697 .color = Color::Green(),
698 .image_filter = ImageFilter::MakeBlur(
699 Sigma(0.1), Sigma(0.1), FilterContents::BlurStyle::kNormal,
700 Entity::TileMode::kDecal),
701 });
702 canvas.Restore();
703
704 Picture picture = canvas.EndRecordingAsPicture();
705 std::shared_ptr<RenderTargetCache> cache =
706 std::make_shared<RenderTargetCache>(
707 GetContext()->GetResourceAllocator());
708 AiksContext aiks_context(GetContext(), nullptr, cache);
709 std::shared_ptr<Image> image = picture.ToImage(aiks_context, {1024, 768});
710 EXPECT_TRUE(image) << " clip rect " << i;
711 }
712 }
713
714 TEST_P(AiksTest, GaussianBlurAnimatedBackdrop) {
715 // This test is for checking out how stable rendering is when content is
716 // translated underneath a blur. Animating under a blur can cause
717 // *shimmering* to happen as a result of pixel alignment.
718 // See also: https://github.com/flutter/flutter/issues/140193
719 auto boston = std::make_shared<Image>(
720 CreateTextureForFixture("boston.jpg", /*enable_mipmapping=*/true));
721 ASSERT_TRUE(boston);
722 int64_t count = 0;
723 Scalar sigma = 20.0;
724 Scalar freq = 0.1;
725 Scalar amp = 50.0;
726 auto callback = [&](AiksContext& renderer) -> std::optional<Picture> {
727 if (AiksTest::ImGuiBegin("Controls", nullptr,
728 ImGuiWindowFlags_AlwaysAutoResize)) {
729 ImGui::SliderFloat("Sigma", &sigma, 0, 200);
730 ImGui::SliderFloat("Frequency", &freq, 0.01, 2.0);
731 ImGui::SliderFloat("Amplitude", &amp, 1, 100);
732 ImGui::End();
733 }
734
735 Canvas canvas;
736 canvas.Scale(GetContentScale());
737 Scalar y = amp * sin(freq * 2.0 * M_PI * count / 60);
738 canvas.DrawImage(boston,
739 Point(1024 / 2 - boston->GetSize().width / 2,
740 (768 / 2 - boston->GetSize().height / 2) + y),
741 {});
742 static PlaygroundPoint point_a(Point(100, 100), 20, Color::Red());
743 static PlaygroundPoint point_b(Point(900, 700), 20, Color::Red());
744 auto [handle_a, handle_b] = DrawPlaygroundLine(point_a, point_b);
745 canvas.ClipRect(
746 Rect::MakeLTRB(handle_a.x, handle_a.y, handle_b.x, handle_b.y));
747 canvas.ClipRect(Rect::MakeLTRB(100, 100, 900, 700));
748 canvas.SaveLayer({.blend_mode = BlendMode::kSource}, std::nullopt,
749 ImageFilter::MakeBlur(Sigma(sigma), Sigma(sigma),
750 FilterContents::BlurStyle::kNormal,
751 Entity::TileMode::kClamp));
752 count += 1;
753 return canvas.EndRecordingAsPicture();
754 };
755 ASSERT_TRUE(OpenPlaygroundHere(callback));
756 }
757
758 TEST_P(AiksTest, GaussianBlurStyleInnerGradient) {
759 Canvas canvas;
760 canvas.Scale(GetContentScale());
761
762 canvas.DrawPaint({.color = Color(0.1, 0.1, 0.1, 1.0)});
763
764 std::vector<Color> colors = {Color{0.9568, 0.2627, 0.2118, 1.0},
765 Color{0.7568, 0.2627, 0.2118, 1.0}};
766 std::vector<Scalar> stops = {0.0, 1.0};
767
768 Paint paint;
769 paint.color_source = ColorSource::MakeLinearGradient(
770 {0, 0}, {200, 200}, std::move(colors), std::move(stops),
771 Entity::TileMode::kMirror, {});
772 paint.mask_blur_descriptor = Paint::MaskBlurDescriptor{
773 .style = FilterContents::BlurStyle::kInner,
774 .sigma = Sigma(30),
775 };
776 canvas.DrawPath(PathBuilder()
777 .MoveTo({200, 200})
778 .LineTo({300, 400})
779 .LineTo({100, 400})
780 .Close()
781 .TakePath(),
782 paint);
783
784 // Draw another thing to make sure the clip area is reset.
785 Paint red;
786 red.color = Color::Red();
787 canvas.DrawRect(Rect::MakeXYWH(0, 0, 200, 200), red);
788 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
789 }
790
791 TEST_P(AiksTest, GaussianBlurStyleSolidGradient) {
792 Canvas canvas;
793 canvas.Scale(GetContentScale());
794
795 canvas.DrawPaint({.color = Color(0.1, 0.1, 0.1, 1.0)});
796
797 std::vector<Color> colors = {Color{0.9568, 0.2627, 0.2118, 1.0},
798 Color{0.7568, 0.2627, 0.2118, 1.0}};
799 std::vector<Scalar> stops = {0.0, 1.0};
800
801 Paint paint;
802 paint.color_source = ColorSource::MakeLinearGradient(
803 {0, 0}, {200, 200}, std::move(colors), std::move(stops),
804 Entity::TileMode::kMirror, {});
805 paint.mask_blur_descriptor = Paint::MaskBlurDescriptor{
806 .style = FilterContents::BlurStyle::kSolid,
807 .sigma = Sigma(30),
808 };
809 canvas.DrawPath(PathBuilder()
810 .MoveTo({200, 200})
811 .LineTo({300, 400})
812 .LineTo({100, 400})
813 .Close()
814 .TakePath(),
815 paint);
816
817 // Draw another thing to make sure the clip area is reset.
818 Paint red;
819 red.color = Color::Red();
820 canvas.DrawRect(Rect::MakeXYWH(0, 0, 200, 200), red);
821 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
822 }
823
824 TEST_P(AiksTest, GaussianBlurStyleOuterGradient) {
825 Canvas canvas;
826 canvas.Scale(GetContentScale());
827
828 canvas.DrawPaint({.color = Color(0.1, 0.1, 0.1, 1.0)});
829
830 std::vector<Color> colors = {Color{0.9568, 0.2627, 0.2118, 1.0},
831 Color{0.7568, 0.2627, 0.2118, 1.0}};
832 std::vector<Scalar> stops = {0.0, 1.0};
833
834 Paint paint;
835 paint.color_source = ColorSource::MakeLinearGradient(
836 {0, 0}, {200, 200}, std::move(colors), std::move(stops),
837 Entity::TileMode::kMirror, {});
838 paint.mask_blur_descriptor = Paint::MaskBlurDescriptor{
839 .style = FilterContents::BlurStyle::kOuter,
840 .sigma = Sigma(30),
841 };
842 canvas.DrawPath(PathBuilder()
843 .MoveTo({200, 200})
844 .LineTo({300, 400})
845 .LineTo({100, 400})
846 .Close()
847 .TakePath(),
848 paint);
849
850 // Draw another thing to make sure the clip area is reset.
851 Paint red;
852 red.color = Color::Red();
853 canvas.DrawRect(Rect::MakeXYWH(0, 0, 200, 200), red);
854 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
855 }
856
857 TEST_P(AiksTest, GaussianBlurStyleInner) {
858 Canvas canvas;
859 canvas.Scale(GetContentScale());
860
861 canvas.DrawPaint({.color = Color(0.1, 0.1, 0.1, 1.0)});
862
863 Paint paint;
864 paint.color = Color::Green();
865 paint.mask_blur_descriptor = Paint::MaskBlurDescriptor{
866 .style = FilterContents::BlurStyle::kInner,
867 .sigma = Sigma(30),
868 };
869 canvas.DrawPath(PathBuilder()
870 .MoveTo({200, 200})
871 .LineTo({300, 400})
872 .LineTo({100, 400})
873 .Close()
874 .TakePath(),
875 paint);
876
877 // Draw another thing to make sure the clip area is reset.
878 Paint red;
879 red.color = Color::Red();
880 canvas.DrawRect(Rect::MakeXYWH(0, 0, 200, 200), red);
881
882 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
883 }
884
885 TEST_P(AiksTest, GaussianBlurStyleOuter) {
886 Canvas canvas;
887 canvas.Scale(GetContentScale());
888
889 canvas.DrawPaint({.color = Color(0.1, 0.1, 0.1, 1.0)});
890
891 Paint paint;
892 paint.color = Color::Green();
893 paint.mask_blur_descriptor = Paint::MaskBlurDescriptor{
894 .style = FilterContents::BlurStyle::kOuter,
895 .sigma = Sigma(30),
896 };
897 canvas.DrawPath(PathBuilder()
898 .MoveTo({200, 200})
899 .LineTo({300, 400})
900 .LineTo({100, 400})
901 .Close()
902 .TakePath(),
903 paint);
904
905 // Draw another thing to make sure the clip area is reset.
906 Paint red;
907 red.color = Color::Red();
908 canvas.DrawRect(Rect::MakeXYWH(0, 0, 200, 200), red);
909
910 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
911 }
912
913 TEST_P(AiksTest, GaussianBlurStyleSolid) {
914 Canvas canvas;
915 canvas.Scale(GetContentScale());
916
917 canvas.DrawPaint({.color = Color(0.1, 0.1, 0.1, 1.0)});
918
919 Paint paint;
920 paint.color = Color::Green();
921 paint.mask_blur_descriptor = Paint::MaskBlurDescriptor{
922 .style = FilterContents::BlurStyle::kSolid,
923 .sigma = Sigma(30),
924 };
925 canvas.DrawPath(PathBuilder()
926 .MoveTo({200, 200})
927 .LineTo({300, 400})
928 .LineTo({100, 400})
929 .Close()
930 .TakePath(),
931 paint);
932
933 // Draw another thing to make sure the clip area is reset.
934 Paint red;
935 red.color = Color::Red();
936 canvas.DrawRect(Rect::MakeXYWH(0, 0, 200, 200), red);
937
938 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
939 }
940
941 TEST_P(AiksTest, MaskBlurTexture) {
942 Scalar sigma = 30;
943 auto callback = [&](AiksContext& renderer) -> std::optional<Picture> {
944 if (AiksTest::ImGuiBegin("Controls", nullptr,
945 ImGuiWindowFlags_AlwaysAutoResize)) {
946 ImGui::SliderFloat("Sigma", &sigma, 0, 500);
947 ImGui::End();
948 }
949 Canvas canvas;
950 canvas.Scale(GetContentScale());
951 Paint paint;
952 paint.color = Color::Green();
953 paint.mask_blur_descriptor = Paint::MaskBlurDescriptor{
954 .style = FilterContents::BlurStyle::kNormal,
955 .sigma = Sigma(sigma),
956 };
957 std::shared_ptr<Texture> boston = CreateTextureForFixture("boston.jpg");
958 canvas.DrawImage(std::make_shared<Image>(boston), {200, 200}, paint);
959 Paint red;
960 red.color = Color::Red();
961 canvas.DrawRect(Rect::MakeXYWH(0, 0, 200, 200), red);
962 return canvas.EndRecordingAsPicture();
963 };
964 ASSERT_TRUE(OpenPlaygroundHere(callback));
965 }
966
967 TEST_P(AiksTest, GuassianBlurUpdatesMipmapContents) {
968 // This makes sure if mip maps are recycled across invocations of blurs the
969 // contents get updated each frame correctly. If they aren't updated the color
970 // inside the blur and outside the blur will be different.
971 //
972 // If there is some change to render target caching this could display a false
973 // positive in the future. Also, if the LOD that is rendered is 1 it could
974 // present a false positive.
975 int32_t count = 0;
976 auto callback = [&](AiksContext& renderer) -> std::optional<Picture> {
977 Canvas canvas;
978 if (count++ == 0) {
979 canvas.DrawCircle({100, 100}, 50, {.color = Color::CornflowerBlue()});
980 } else {
981 canvas.DrawCircle({100, 100}, 50, {.color = Color::Chartreuse()});
982 }
983 canvas.ClipRRect(Rect::MakeLTRB(75, 50, 375, 275), {20, 20});
984 canvas.SaveLayer({.blend_mode = BlendMode::kSource}, std::nullopt,
985 ImageFilter::MakeBlur(Sigma(30.0), Sigma(30.0),
986 FilterContents::BlurStyle::kNormal,
987 Entity::TileMode::kClamp));
988 canvas.Restore();
989 return canvas.EndRecordingAsPicture();
990 };
991
992 ASSERT_TRUE(OpenPlaygroundHere(callback));
993 }
994
995 TEST_P(AiksTest, GaussianBlurSetsMipCountOnPass) {
996 Canvas canvas;
997 canvas.DrawCircle({100, 100}, 50, {.color = Color::CornflowerBlue()});
998 canvas.SaveLayer({}, std::nullopt,
999 ImageFilter::MakeBlur(Sigma(3), Sigma(3),
1000 FilterContents::BlurStyle::kNormal,
1001 Entity::TileMode::kClamp));
1002 canvas.Restore();
1003
1004 Picture picture = canvas.EndRecordingAsPicture();
1005 EXPECT_EQ(4, picture.pass->GetRequiredMipCount());
1006 }
1007
1008 TEST_P(AiksTest, GaussianBlurAllocatesCorrectMipCountRenderTarget) {
1009 size_t blur_required_mip_count =
1010 GetParam() == PlaygroundBackend::kOpenGLES ? 1 : 4;
1011
1012 Canvas canvas;
1013 canvas.DrawCircle({100, 100}, 50, {.color = Color::CornflowerBlue()});
1014 canvas.SaveLayer({}, std::nullopt,
1015 ImageFilter::MakeBlur(Sigma(3), Sigma(3),
1016 FilterContents::BlurStyle::kNormal,
1017 Entity::TileMode::kClamp));
1018 canvas.Restore();
1019
1020 Picture picture = canvas.EndRecordingAsPicture();
1021 std::shared_ptr<RenderTargetCache> cache =
1022 std::make_shared<RenderTargetCache>(GetContext()->GetResourceAllocator());
1023 AiksContext aiks_context(GetContext(), nullptr, cache);
1024 picture.ToImage(aiks_context, {100, 100});
1025
1026 size_t max_mip_count = 0;
1027 for (auto it = cache->GetRenderTargetDataBegin();
1028 it != cache->GetRenderTargetDataEnd(); ++it) {
1029 max_mip_count = std::max(it->config.mip_count, max_mip_count);
1030 }
1031 EXPECT_EQ(max_mip_count, blur_required_mip_count);
1032 }
1033
1034 TEST_P(AiksTest, GaussianBlurMipMapNestedLayer) {
1035 fml::testing::LogCapture log_capture;
1036 size_t blur_required_mip_count =
1037 GetParam() == PlaygroundBackend::kOpenGLES ? 1 : 4;
1038
1039 Canvas canvas;
1040 canvas.DrawPaint({.color = Color::Wheat()});
1041 canvas.SaveLayer({.blend_mode = BlendMode::kMultiply});
1042 canvas.DrawCircle({100, 100}, 50, {.color = Color::CornflowerBlue()});
1043 canvas.SaveLayer({}, std::nullopt,
1044 ImageFilter::MakeBlur(Sigma(30), Sigma(30),
1045 FilterContents::BlurStyle::kNormal,
1046 Entity::TileMode::kClamp));
1047 canvas.DrawCircle({200, 200}, 50, {.color = Color::Chartreuse()});
1048
1049 Picture picture = canvas.EndRecordingAsPicture();
1050 std::shared_ptr<RenderTargetCache> cache =
1051 std::make_shared<RenderTargetCache>(GetContext()->GetResourceAllocator());
1052 AiksContext aiks_context(GetContext(), nullptr, cache);
1053 picture.ToImage(aiks_context, {100, 100});
1054
1055 size_t max_mip_count = 0;
1056 for (auto it = cache->GetRenderTargetDataBegin();
1057 it != cache->GetRenderTargetDataEnd(); ++it) {
1058 max_mip_count = std::max(it->config.mip_count, max_mip_count);
1059 }
1060 EXPECT_EQ(max_mip_count, blur_required_mip_count);
1061 // The log is FML_DLOG, so only check in debug builds.
1062 #ifndef NDEBUG
1063 if (GetParam() != PlaygroundBackend::kOpenGLES) {
1064 EXPECT_EQ(log_capture.str().find(GaussianBlurFilterContents::kNoMipsError),
1065 std::string::npos);
1066 } else {
1067 EXPECT_NE(log_capture.str().find(GaussianBlurFilterContents::kNoMipsError),
1068 std::string::npos);
1069 }
1070 #endif
1071 }
1072
1073 TEST_P(AiksTest, GaussianBlurMipMapImageFilter) {
1074 size_t blur_required_mip_count =
1075 GetParam() == PlaygroundBackend::kOpenGLES ? 1 : 4;
1076 fml::testing::LogCapture log_capture;
1077 Canvas canvas;
1078 canvas.SaveLayer(
1079 {.image_filter = ImageFilter::MakeBlur(Sigma(30), Sigma(30),
1080 FilterContents::BlurStyle::kNormal,
1081 Entity::TileMode::kClamp)});
1082 canvas.DrawCircle({200, 200}, 50, {.color = Color::Chartreuse()});
1083
1084 Picture picture = canvas.EndRecordingAsPicture();
1085 std::shared_ptr<RenderTargetCache> cache =
1086 std::make_shared<RenderTargetCache>(GetContext()->GetResourceAllocator());
1087 AiksContext aiks_context(GetContext(), nullptr, cache);
1088 picture.ToImage(aiks_context, {1024, 768});
1089
1090 size_t max_mip_count = 0;
1091 for (auto it = cache->GetRenderTargetDataBegin();
1092 it != cache->GetRenderTargetDataEnd(); ++it) {
1093 max_mip_count = std::max(it->config.mip_count, max_mip_count);
1094 }
1095 EXPECT_EQ(max_mip_count, blur_required_mip_count);
1096 // The log is FML_DLOG, so only check in debug builds.
1097 #ifndef NDEBUG
1098 if (GetParam() != PlaygroundBackend::kOpenGLES) {
1099 EXPECT_EQ(log_capture.str().find(GaussianBlurFilterContents::kNoMipsError),
1100 std::string::npos);
1101 } else {
1102 EXPECT_NE(log_capture.str().find(GaussianBlurFilterContents::kNoMipsError),
1103 std::string::npos);
1104 }
1105 #endif
1106 }
1107
1108 TEST_P(AiksTest, GaussianBlurMipMapSolidColor) {
1109 size_t blur_required_mip_count =
1110 GetParam() == PlaygroundBackend::kOpenGLES ? 1 : 4;
1111 fml::testing::LogCapture log_capture;
1112 Canvas canvas;
1113 canvas.DrawPath(PathBuilder{}
1114 .MoveTo({100, 100})
1115 .LineTo({200, 100})
1116 .LineTo({150, 200})
1117 .LineTo({50, 200})
1118 .Close()
1119 .TakePath(),
1120 {.color = Color::Chartreuse(),
1121 .image_filter = ImageFilter::MakeBlur(
1122 Sigma(30), Sigma(30), FilterContents::BlurStyle::kNormal,
1123 Entity::TileMode::kClamp)});
1124
1125 Picture picture = canvas.EndRecordingAsPicture();
1126 std::shared_ptr<RenderTargetCache> cache =
1127 std::make_shared<RenderTargetCache>(GetContext()->GetResourceAllocator());
1128 AiksContext aiks_context(GetContext(), nullptr, cache);
1129 picture.ToImage(aiks_context, {1024, 768});
1130
1131 size_t max_mip_count = 0;
1132 for (auto it = cache->GetRenderTargetDataBegin();
1133 it != cache->GetRenderTargetDataEnd(); ++it) {
1134 max_mip_count = std::max(it->config.mip_count, max_mip_count);
1135 }
1136 EXPECT_EQ(max_mip_count, blur_required_mip_count);
1137 // The log is FML_DLOG, so only check in debug builds.
1138 #ifndef NDEBUG
1139 if (GetParam() != PlaygroundBackend::kOpenGLES) {
1140 EXPECT_EQ(log_capture.str().find(GaussianBlurFilterContents::kNoMipsError),
1141 std::string::npos);
1142 } else {
1143 EXPECT_NE(log_capture.str().find(GaussianBlurFilterContents::kNoMipsError),
1144 std::string::npos);
1145 }
1146 #endif
1147 }
1148
1149 TEST_P(AiksTest, MaskBlurDoesntStretchContents) {
1150 Scalar sigma = 70;
1151 auto callback = [&](AiksContext& renderer) -> std::optional<Picture> {
1152 if (AiksTest::ImGuiBegin("Controls", nullptr,
1153 ImGuiWindowFlags_AlwaysAutoResize)) {
1154 ImGui::SliderFloat("Sigma", &sigma, 0, 500);
1155 ImGui::End();
1156 }
1157 Canvas canvas;
1158 canvas.Scale(GetContentScale());
1159 canvas.DrawPaint({.color = Color(0.1, 0.1, 0.1, 1.0)});
1160
1161 std::shared_ptr<Texture> boston = CreateTextureForFixture("boston.jpg");
1162 ColorSource image_source = ColorSource::MakeImage(
1163 boston, Entity::TileMode::kRepeat, Entity::TileMode::kRepeat, {}, {});
1164
1165 canvas.Transform(Matrix::MakeTranslation({100, 100, 0}) *
1166 Matrix::MakeScale({0.5, 0.5, 1.0}));
1167 Paint paint = {
1168 .color_source = image_source,
1169 .mask_blur_descriptor =
1170 Paint::MaskBlurDescriptor{
1171 .style = FilterContents::BlurStyle::kNormal,
1172 .sigma = Sigma(sigma),
1173 },
1174 };
1175 canvas.DrawRect(
1176 Rect::MakeXYWH(0, 0, boston->GetSize().width, boston->GetSize().height),
1177 paint);
1178
1179 return canvas.EndRecordingAsPicture();
1180 };
1181 ASSERT_TRUE(OpenPlaygroundHere(callback));
1182 }
1183
1184 } // namespace testing
1185 } // namespace impeller
impeller::Color::DarkMagenta
static constexpr Color DarkMagenta()
Definition: color.h:378
impeller::Color::Blue
static constexpr Color Blue()
Definition: color.h:268
impeller::testing::MaskBlurTestConfig::alpha
Scalar alpha
Definition: aiks_blur_unittests.cc:288
impeller::AiksPlayground
Definition: aiks_playground.h:18
impeller::Entity::TileMode::kClamp
@ kClamp
impeller::Canvas::EndRecordingAsPicture
Picture EndRecordingAsPicture()
Definition: canvas.cc:756
impeller::Picture::pass
std::unique_ptr< EntityPass > pass
Definition: picture.h:21
impeller::Canvas::DrawRRect
void DrawRRect(const Rect &rect, const Size &corner_radii, const Paint &paint)
Definition: canvas.cc:488
impeller::TPoint::y
Type y
Definition: point.h:31
impeller::Scalar
float Scalar
Definition: scalar.h:18
impeller::AiksContext
Definition: aiks_context.h:20
impeller::Canvas::DrawImageRect
void DrawImageRect(const std::shared_ptr< Image > &image, Rect source, Rect dest, const Paint &paint, SamplerDescriptor sampler={}, SourceRectConstraint src_rect_constraint=SourceRectConstraint::kFast)
Definition: canvas.cc:717
impeller::testing::MaskBlurTestConfig::sigma
Scalar sigma
Definition: aiks_blur_unittests.cc:287
impeller::Color::Red
static constexpr Color Red()
Definition: color.h:264
impeller::Paint::Style::kStroke
@ kStroke
aiks_unittests.h
impeller::ColorSource::MakeLinearGradient
static ColorSource MakeLinearGradient(Point start_point, Point end_point, std::vector< Color > colors, std::vector< Scalar > stops, Entity::TileMode tile_mode, Matrix effect_transform)
Definition: color_source.cc:45
impeller::Paint
Definition: paint.h:23
impeller::TRect< Scalar >::MakeXYWH
constexpr static TRect MakeXYWH(Type x, Type y, Type width, Type height)
Definition: rect.h:136
impeller::BlendMode
BlendMode
Definition: color.h:59
impeller::FilterContents::BlurStyle
BlurStyle
Definition: filter_contents.h:26
impeller::Color
Definition: color.h:124
impeller::Paint::color
Color color
Definition: paint.h:55
impeller::Entity::TileMode::kDecal
@ kDecal
impeller::BlendMode::kSource
@ kSource
impeller::PlaygroundBackend::kMetal
@ kMetal
impeller::Color::Purple
static constexpr Color Purple()
Definition: color.h:734
impeller::Canvas
Definition: canvas.h:58
impeller::testing::MaskBlurTestConfig::blend_mode
BlendMode blend_mode
Definition: aiks_blur_unittests.cc:291
impeller::PathBuilder
Definition: path_builder.h:14
impeller::Paint::MaskBlurDescriptor::style
FilterContents::BlurStyle style
Definition: paint.h:40
impeller::Vector2
Point Vector2
Definition: point.h:320
impeller::BlendMode::kColor
@ kColor
impeller::kPi
constexpr float kPi
Definition: constants.h:26
impeller::ImageFilter::MakeBlur
static std::shared_ptr< ImageFilter > MakeBlur(Sigma sigma_x, Sigma sigma_y, FilterContents::BlurStyle blur_style, Entity::TileMode tile_mode)
Definition: image_filter.cc:20
impeller::Paint::MaskBlurDescriptor
Definition: paint.h:39
impeller::testing::MaskBlurVariantTest
static Picture MaskBlurVariantTest(const AiksTest &test_context, const MaskBlurTestConfig &config)
Definition: aiks_blur_unittests.cc:294
impeller::Color::CornflowerBlue
static constexpr Color CornflowerBlue()
Definition: color.h:334
impeller::testing::MaskBlurTestConfig
Definition: aiks_blur_unittests.cc:285
impeller::Color::Yellow
static constexpr Color Yellow()
Definition: color.h:834
gaussian_blur_filter_contents.h
impeller::ColorSource::MakeImage
static ColorSource MakeImage(std::shared_ptr< Texture > texture, Entity::TileMode x_tile_mode, Entity::TileMode y_tile_mode, SamplerDescriptor sampler_descriptor, Matrix effect_transform)
Definition: color_source.cc:163
impeller::Size
TSize< Scalar > Size
Definition: size.h:137
impeller::FilterContents::BlurStyle::kNormal
@ kNormal
Blurred inside and outside.
impeller::Entity::TileMode::kRepeat
@ kRepeat
impeller::Matrix::MakeTranslation
static constexpr Matrix MakeTranslation(const Vector3 &t)
Definition: matrix.h:95
impeller::Color::Wheat
static constexpr Color Wheat()
Definition: color.h:826
impeller::Paint::color_source
ColorSource color_source
Definition: paint.h:56
impeller::Canvas::DrawRect
void DrawRect(const Rect &rect, const Paint &paint)
Definition: canvas.cc:441
impeller::MoveTo
void MoveTo(PathBuilder *builder, Scalar x, Scalar y)
Definition: tessellator.cc:18
impeller::PathBuilder::AddRect
PathBuilder & AddRect(Rect rect)
Definition: path_builder.cc:112
impeller::Color::Maroon
static constexpr Color Maroon()
Definition: color.h:606
impeller::Canvas::SaveLayer
void SaveLayer(const Paint &paint, std::optional< Rect > bounds=std::nullopt, const std::shared_ptr< ImageFilter > &backdrop_filter=nullptr, ContentBoundsPromise bounds_promise=ContentBoundsPromise::kUnknown)
Definition: canvas.cc:786
impeller::TRect::Shift
constexpr TRect< T > Shift(T dx, T dy) const
Returns a new rectangle translated by the given offset.
Definition: rect.h:531
impeller::Canvas::DrawImage
void DrawImage(const std::shared_ptr< Image > &image, Point offset, const Paint &paint, SamplerDescriptor sampler={})
Definition: canvas.cc:703
impeller::Entity::TileMode::kMirror
@ kMirror
path_builder.h
impeller::Color::Magenta
static constexpr Color Magenta()
Definition: color.h:602
impeller::Picture
Definition: picture.h:20
impeller::TSize< int64_t >
impeller::Point
TPoint< Scalar > Point
Definition: point.h:316
impeller::testing::kPaintVariations
static const std::map< std::string, MaskBlurTestConfig > kPaintVariations
Definition: aiks_blur_unittests.cc:368
impeller::ColorSource
Definition: color_source.h:28
MASK_BLUR_VARIANT_TEST
#define MASK_BLUR_VARIANT_TEST(config)
Definition: aiks_blur_unittests.cc:432
impeller::Canvas::Scale
void Scale(const Vector2 &scale)
Definition: canvas.cc:264
impeller::Color::OrangeRed
static constexpr Color OrangeRed()
Definition: color.h:686
impeller::Radius
For convolution filters, the "radius" is the size of the convolution kernel to use on the local space...
Definition: sigma.h:48
impeller::FilterContents::BlurStyle::kSolid
@ kSolid
Solid inside, blurred outside.
impeller::testing::MaskBlurTestConfig::image_filter
std::shared_ptr< ImageFilter > image_filter
Definition: aiks_blur_unittests.cc:289
impeller::ColorFilter::MakeBlend
static std::shared_ptr< ColorFilter > MakeBlend(BlendMode blend_mode, Color color)
Definition: color_filter.cc:23
widgets.h
impeller::testing::MaskBlurTestConfig::style
FilterContents::BlurStyle style
Definition: aiks_blur_unittests.cc:286
impeller::BlendMode::kClear
@ kClear
impeller::Color::WithAlpha
constexpr Color WithAlpha(Scalar new_alpha) const
Definition: color.h:270
impeller::Canvas::DrawCircle
void DrawCircle(const Point &center, Scalar radius, const Paint &paint)
Definition: canvas.cc:515
impeller::Color::Grey
static constexpr Color Grey()
Definition: color.h:486
impeller::Color::White
static constexpr Color White()
Definition: color.h:256
impeller::Sigma
In filters that use Gaussian distributions, "sigma" is a size of one standard deviation in terms of t...
Definition: sigma.h:32
impeller::Canvas::Restore
bool Restore()
Definition: canvas.cc:208
impeller::Radians
Definition: scalar.h:38
impeller::Color::Chartreuse
static constexpr Color Chartreuse()
Definition: color.h:322
impeller::PathBuilder::AddLine
PathBuilder & AddLine(const Point &p1, const Point &p2)
Move to point p1, then insert a line from p1 to p2.
Definition: path_builder.cc:419
canvas.h
impeller::Color::Green
static constexpr Color Green()
Definition: color.h:266
impeller::Canvas::DrawPath
void DrawPath(const Path &path, const Paint &paint)
Definition: canvas.cc:292
impeller::PathBuilder::TakePath
Path TakePath(FillType fill=FillType::kNonZero)
Definition: path_builder.cc:22
impeller::Canvas::DrawPaint
void DrawPaint(const Paint &paint)
Definition: canvas.cc:302
impeller::FilterContents::BlurStyle::kInner
@ kInner
Blurred inside, nothing outside.
impeller::Entity::TileMode
TileMode
Definition: entity.h:42
impeller::TSize::width
Type width
Definition: size.h:22
impeller::TPoint::x
Type x
Definition: point.h:30
impeller::Canvas::ClipRRect
void ClipRRect(const Rect &rect, const Size &corner_radii, Entity::ClipOperation clip_op=Entity::ClipOperation::kIntersect)
Definition: canvas.cc:590
impeller::Close
void Close(PathBuilder *builder)
Definition: tessellator.cc:36
impeller::ISize
TSize< int64_t > ISize
Definition: size.h:138
impeller::Color::AntiqueWhite
static constexpr Color AntiqueWhite()
Definition: color.h:278
impeller::Picture::ToImage
std::shared_ptr< Image > ToImage(AiksContext &context, ISize size) const
Definition: picture.cc:31
impeller::Canvas::DrawOval
void DrawOval(const Rect &rect, const Paint &paint)
Definition: canvas.cc:461
impeller::BlendMode::kExclusion
@ kExclusion
impeller::Color::GreenYellow
static constexpr Color GreenYellow()
Definition: color.h:482
impeller::FilterContents::BlurStyle::kOuter
@ kOuter
Nothing inside, blurred outside.
impeller::TRect::GetSize
constexpr TSize< Type > GetSize() const
Returns the size of the rectangle which may be negative in either width or height and may have been c...
Definition: rect.h:294
impeller::PlaygroundBackend::kOpenGLES
@ kOpenGLES
impeller::Canvas::Rotate
void Rotate(Radians radians)
Definition: canvas.cc:276
impeller::LineTo
void LineTo(PathBuilder *builder, Scalar x, Scalar y)
Definition: tessellator.cc:22
impeller::Playground::GetContentScale
Point GetContentScale() const
Definition: playground.cc:186
impeller::DrawPlaygroundPoint
Point DrawPlaygroundPoint(PlaygroundPoint &point)
Definition: widgets.cc:9
impeller::Color::Orange
static constexpr Color Orange()
Definition: color.h:682
impeller::PlaygroundPoint
Definition: widgets.h:17
impeller::GaussianBlurFilterContents::kNoMipsError
static std::string_view kNoMipsError
Definition: gaussian_blur_filter_contents.h:36
impeller::testing::TEST_P
TEST_P(AiksTest, CanRenderMaskBlurHugeSigma)
Definition: aiks_blur_unittests.cc:23
FLT_FORWARD
#define FLT_FORWARD(mock, real, method)
Definition: aiks_blur_unittests.cc:489
impeller::TPoint< Scalar >
impeller::Canvas::Transform
void Transform(const Matrix &transform)
Definition: canvas.cc:243
impeller::saturated::b
SI b
Definition: saturated_math.h:87
impeller::PathBuilder::MoveTo
PathBuilder & MoveTo(Point point, bool relative=false)
Definition: path_builder.cc:33
impeller::Color::Black
static constexpr Color Black()
Definition: color.h:258
impeller::Paint::invert_colors
bool invert_colors
Definition: paint.h:65
scale
const Scalar scale
Definition: stroke_path_geometry.cc:297
impeller::AiksPlayground::ImGuiBegin
static bool ImGuiBegin(const char *name, bool *p_open, ImGuiWindowFlags flags)
Definition: aiks_playground.cc:59
impeller::Degrees
Definition: scalar.h:46
impeller::Color::LimeGreen
static constexpr Color LimeGreen()
Definition: color.h:594
impeller::TSize::height
Type height
Definition: size.h:23
impeller::Color::DarkGreen
static constexpr Color DarkGreen()
Definition: color.h:366
impeller::Canvas::ClipRect
void ClipRect(const Rect &rect, Entity::ClipOperation clip_op=Entity::ClipOperation::kIntersect)
Definition: canvas.cc:549
impeller::TRect< Scalar >::MakeLTRB
constexpr static TRect MakeLTRB(Type left, Type top, Type right, Type bottom)
Definition: rect.h:129
render_target_cache.h
impeller
Definition: aiks_blur_unittests.cc:20
impeller::Matrix::MakeScale
static constexpr Matrix MakeScale(const Vector3 &s)
Definition: matrix.h:104
impeller::Paint::mask_blur_descriptor
std::optional< MaskBlurDescriptor > mask_blur_descriptor
Definition: paint.h:69
impeller::BlendMode::kMultiply
@ kMultiply
impeller::TRect
Definition: rect.h:122
impeller::PathBuilder::AddArc
PathBuilder & AddArc(const Rect &oval_bounds, Radians start, Radians sweep, bool use_center=false)
Definition: path_builder.cc:313
impeller::BlendMode::kSourceOver
@ kSourceOver
impeller::Paint::blend_mode
BlendMode blend_mode
Definition: paint.h:64
impeller::testing::MaskBlurTestConfig::invert_colors
bool invert_colors
Definition: aiks_blur_unittests.cc:290
impeller::Color::Crimson
static constexpr Color Crimson()
Definition: color.h:342
impeller::TRect::Expand
constexpr TRect< T > Expand(T left, T top, T right, T bottom) const
Returns a rectangle with expanded edges. Negative expansion results in shrinking.
Definition: rect.h:547
impeller::Paint::image_filter
std::shared_ptr< ImageFilter > image_filter
Definition: paint.h:67
impeller::DrawPlaygroundLine
std::tuple< Point, Point > DrawPlaygroundLine(PlaygroundPoint &point_a, PlaygroundPoint &point_b)
Definition: widgets.cc:50
impeller::TPoint< Scalar >::MakeXY
static constexpr TPoint< Type > MakeXY(Type x, Type y)
Definition: point.h:46
impeller::Canvas::Translate
void Translate(const Vector3 &offset)
Definition: canvas.cc:260