| 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 Scalar sigma = 47.6;
203 auto callback = [&](AiksContext& renderer) -> std::optional<Picture> {
204 if (AiksTest::ImGuiBegin("Controls", nullptr,
205 ImGuiWindowFlags_AlwaysAutoResize)) {
206 ImGui::SliderFloat("Sigma", &sigma, 0, 50);
207 ImGui::End();
208 }
209 Canvas canvas;
210 canvas.Scale(GetContentScale());
211 canvas.DrawPaint({});
212 Paint blur = {
213 .color = Color::Green(),
214 .mask_blur_descriptor =
215 Paint::MaskBlurDescriptor{
216 .style = FilterContents::BlurStyle::kNormal,
217 .sigma = Sigma(sigma),
218 },
219 };
220 canvas.DrawRect(Rect::MakeXYWH(300, 300, 200, 200), blur);
221 return canvas.EndRecordingAsPicture();
222 };
223
224 ASSERT_TRUE(OpenPlaygroundHere(callback));
225 }
226
227 TEST_P(AiksTest, BlurredRectangleWithShader) {
228 Canvas canvas;
229 canvas.Scale(GetContentScale());
230
231 auto paint_lines = [&canvas](Scalar dx, Scalar dy, Paint paint) {
232 auto draw_line = [&canvas, &paint](Point a, Point b) {
233 canvas.DrawPath(PathBuilder{}.AddLine(a, b).TakePath(), paint);
234 };
235 paint.stroke_width = 5;
236 paint.style = Paint::Style::kStroke;
237 draw_line(Point(dx + 100, dy + 100), Point(dx + 200, dy + 200));
238 draw_line(Point(dx + 100, dy + 200), Point(dx + 200, dy + 100));
239 draw_line(Point(dx + 150, dy + 100), Point(dx + 200, dy + 150));
240 draw_line(Point(dx + 100, dy + 150), Point(dx + 150, dy + 200));
241 };
242
243 AiksContext renderer(GetContext(), nullptr);
244 Canvas recorder_canvas;
245 for (int x = 0; x < 5; ++x) {
246 for (int y = 0; y < 5; ++y) {
247 Rect rect = Rect::MakeXYWH(x * 20, y * 20, 20, 20);
248 Paint paint{.color =
249 ((x + y) & 1) == 0 ? Color::Yellow() : Color::Blue()};
250 recorder_canvas.DrawRect(rect, paint);
251 }
252 }
253 Picture picture = recorder_canvas.EndRecordingAsPicture();
254 std::shared_ptr<Texture> texture =
255 picture.ToImage(renderer, ISize{100, 100})->GetTexture();
256
257 ColorSource image_source = ColorSource::MakeImage(
258 texture, Entity::TileMode::kRepeat, Entity::TileMode::kRepeat, {}, {});
259 std::shared_ptr<ImageFilter> blur_filter = ImageFilter::MakeBlur(
260 Sigma(5), Sigma(5), FilterContents::BlurStyle::kNormal,
261 Entity::TileMode::kDecal);
262 canvas.DrawRect(Rect::MakeLTRB(0, 0, 300, 600),
263 Paint{.color = Color::DarkGreen()});
264 canvas.DrawRect(Rect::MakeLTRB(100, 100, 200, 200),
265 Paint{.color_source = image_source});
266 canvas.DrawRect(Rect::MakeLTRB(300, 0, 600, 600),
267 Paint{.color = Color::Red()});
268 canvas.DrawRect(
269 Rect::MakeLTRB(400, 100, 500, 200),
270 Paint{.color_source = image_source, .image_filter = blur_filter});
271 paint_lines(0, 300, Paint{.color_source = image_source});
272 paint_lines(300, 300,
273 Paint{.color_source = image_source, .image_filter = blur_filter});
274 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
275 }
276
277 TEST_P(AiksTest, MaskBlurWithZeroSigmaIsSkipped) {
278 Canvas canvas;
279
280 Paint paint = {
281 .color = Color::Blue(),
282 .mask_blur_descriptor =
283 Paint::MaskBlurDescriptor{
284 .style = FilterContents::BlurStyle::kNormal,
285 .sigma = Sigma(0),
286 },
287 };
288
289 canvas.DrawCircle({300, 300}, 200, paint);
290 canvas.DrawRect(Rect::MakeLTRB(100, 300, 500, 600), paint);
291
292 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
293 }
294
295 struct MaskBlurTestConfig {
296 FilterContents::BlurStyle style = FilterContents::BlurStyle::kNormal;
297 Scalar sigma = 1.0f;
298 Scalar alpha = 1.0f;
299 std::shared_ptr<ImageFilter> image_filter;
300 bool invert_colors = false;
301 BlendMode blend_mode = BlendMode::kSourceOver;
302 };
303
304 static Picture MaskBlurVariantTest(const AiksTest& test_context,
305 const MaskBlurTestConfig& config) {
306 Canvas canvas;
307 canvas.Scale(test_context.GetContentScale());
308 canvas.Scale(Vector2{0.8f, 0.8f});
309 Paint paint;
310 paint.mask_blur_descriptor = Paint::MaskBlurDescriptor{
311 .style = FilterContents::BlurStyle::kNormal,
312 .sigma = Sigma{1},
313 };
314
315 canvas.DrawPaint({.color = Color::AntiqueWhite()});
316
317 paint.mask_blur_descriptor->style = config.style;
318 paint.mask_blur_descriptor->sigma = Sigma{config.sigma};
319 paint.image_filter = config.image_filter;
320 paint.invert_colors = config.invert_colors;
321 paint.blend_mode = config.blend_mode;
322
323 const Scalar x = 50;
324 const Scalar radius = 20.0f;
325 const Scalar y_spacing = 100.0f;
326
327 Scalar y = 50;
328 paint.color = Color::Crimson().WithAlpha(config.alpha);
329 canvas.DrawRect(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::Blue().WithAlpha(config.alpha);
335 canvas.DrawCircle({x + 25, y + 25}, radius, paint);
336
337 y += y_spacing;
338 paint.color = Color::Green().WithAlpha(config.alpha);
339 canvas.DrawOval(Rect::MakeXYWH(x + 25 - radius / 2, y + radius / 2, //
340 radius, 60.0f - radius),
341 paint);
342
343 y += y_spacing;
344 paint.color = Color::Purple().WithAlpha(config.alpha);
345 canvas.DrawRRect(Rect::MakeXYWH(x, y, 60.0f, 60.0f), //
346 {radius, radius}, //
347 paint);
348
349 y += y_spacing;
350 paint.color = Color::Orange().WithAlpha(config.alpha);
351 canvas.DrawRRect(Rect::MakeXYWH(x, y, 60.0f, 60.0f), //
352 {radius, 5.0f}, paint);
353
354 y += y_spacing;
355 paint.color = Color::Maroon().WithAlpha(config.alpha);
356 canvas.DrawPath(PathBuilder{}
357 .MoveTo({x + 0, y + 60})
358 .LineTo({x + 30, y + 0})
359 .LineTo({x + 60, y + 60})
360 .Close()
361 .TakePath(),
362 paint);
363
364 y += y_spacing;
365 paint.color = Color::Maroon().WithAlpha(config.alpha);
366 canvas.DrawPath(PathBuilder{}
367 .AddArc(Rect::MakeXYWH(x + 5, y, 50, 50),
368 Radians{kPi / 2}, Radians{kPi})
369 .AddArc(Rect::MakeXYWH(x + 25, y, 50, 50),
370 Radians{kPi / 2}, Radians{kPi})
371 .Close()
372 .TakePath(),
373 paint);
374
375 return canvas.EndRecordingAsPicture();
376 }
377
378 static const std::map<std::string, MaskBlurTestConfig> kPaintVariations = {
379 // 1. Normal style, translucent, zero sigma.
380 {"NormalTranslucentZeroSigma",
381 {.style = FilterContents::BlurStyle::kNormal,
382 .sigma = 0.0f,
383 .alpha = 0.5f}},
384 // 2. Normal style, translucent.
385 {"NormalTranslucent",
386 {.style = FilterContents::BlurStyle::kNormal,
387 .sigma = 8.0f,
388 .alpha = 0.5f}},
389 // 3. Solid style, translucent.
390 {"SolidTranslucent",
391 {.style = FilterContents::BlurStyle::kSolid,
392 .sigma = 8.0f,
393 .alpha = 0.5f}},
394 // 4. Solid style, opaque.
395 {"SolidOpaque",
396 {.style = FilterContents::BlurStyle::kSolid, .sigma = 8.0f}},
397 // 5. Solid style, translucent, color & image filtered.
398 {"SolidTranslucentWithFilters",
399 {.style = FilterContents::BlurStyle::kSolid,
400 .sigma = 8.0f,
401 .alpha = 0.5f,
402 .image_filter = ImageFilter::MakeBlur(Sigma{3},
403 Sigma{3},
404 FilterContents::BlurStyle::kNormal,
405 Entity::TileMode::kClamp),
406 .invert_colors = true}},
407 // 6. Solid style, translucent, exclusion blended.
408 {"SolidTranslucentExclusionBlend",
409 {.style = FilterContents::BlurStyle::kSolid,
410 .sigma = 8.0f,
411 .alpha = 0.5f,
412 .blend_mode = BlendMode::kExclusion}},
413 // 7. Inner style, translucent.
414 {"InnerTranslucent",
415 {.style = FilterContents::BlurStyle::kInner,
416 .sigma = 8.0f,
417 .alpha = 0.5f}},
418 // 8. Inner style, translucent, blurred.
419 {"InnerTranslucentWithBlurImageFilter",
420 {.style = FilterContents::BlurStyle::kInner,
421 .sigma = 8.0f,
422 .alpha = 0.5f,
423 .image_filter = ImageFilter::MakeBlur(Sigma{3},
424 Sigma{3},
425 FilterContents::BlurStyle::kNormal,
426 Entity::TileMode::kClamp)}},
427 // 9. Outer style, translucent.
428 {"OuterTranslucent",
429 {.style = FilterContents::BlurStyle::kOuter,
430 .sigma = 8.0f,
431 .alpha = 0.5f}},
432 // 10. Outer style, opaque, image filtered.
433 {"OuterOpaqueWithBlurImageFilter",
434 {.style = FilterContents::BlurStyle::kOuter,
435 .sigma = 8.0f,
436 .image_filter = ImageFilter::MakeBlur(Sigma{3},
437 Sigma{3},
438 FilterContents::BlurStyle::kNormal,
439 Entity::TileMode::kClamp)}},
440 };
441
442 #define MASK_BLUR_VARIANT_TEST(config) \
443 TEST_P(AiksTest, MaskBlurVariantTest##config) { \
444 ASSERT_TRUE(OpenPlaygroundHere( \
445 MaskBlurVariantTest(*this, kPaintVariations.at(#config)))); \
446 }
447
448 MASK_BLUR_VARIANT_TEST(NormalTranslucentZeroSigma)
449 MASK_BLUR_VARIANT_TEST(NormalTranslucent)
450 MASK_BLUR_VARIANT_TEST(SolidTranslucent)
451 MASK_BLUR_VARIANT_TEST(SolidOpaque)
452 MASK_BLUR_VARIANT_TEST(SolidTranslucentWithFilters)
453 MASK_BLUR_VARIANT_TEST(SolidTranslucentExclusionBlend)
454 MASK_BLUR_VARIANT_TEST(InnerTranslucent)
455 MASK_BLUR_VARIANT_TEST(InnerTranslucentWithBlurImageFilter)
456 MASK_BLUR_VARIANT_TEST(OuterTranslucent)
457 MASK_BLUR_VARIANT_TEST(OuterOpaqueWithBlurImageFilter)
458
459 #undef MASK_BLUR_VARIANT_TEST
460
461 TEST_P(AiksTest, GaussianBlurAtPeripheryVertical) {
462 Canvas canvas;
463
464 canvas.Scale(GetContentScale());
465 canvas.DrawRRect(Rect::MakeLTRB(0, 0, GetWindowSize().width, 100),
466 Size(10, 10), Paint{.color = Color::LimeGreen()});
467 canvas.DrawRRect(Rect::MakeLTRB(0, 110, GetWindowSize().width, 210),
468 Size(10, 10), Paint{.color = Color::Magenta()});
469 canvas.ClipRect(Rect::MakeLTRB(100, 0, 200, GetWindowSize().height));
470 canvas.SaveLayer({.blend_mode = BlendMode::kSource}, std::nullopt,
471 ImageFilter::MakeBlur(Sigma(20.0), Sigma(20.0),
472 FilterContents::BlurStyle::kNormal,
473 Entity::TileMode::kClamp));
474 canvas.Restore();
475
476 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
477 }
478
479 TEST_P(AiksTest, GaussianBlurAtPeripheryHorizontal) {
480 Canvas canvas;
481
482 canvas.Scale(GetContentScale());
483 std::shared_ptr<Texture> boston = CreateTextureForFixture("boston.jpg");
484 canvas.DrawImageRect(
485 std::make_shared<Image>(boston),
486 Rect::MakeXYWH(0, 0, boston->GetSize().width, boston->GetSize().height),
487 Rect::MakeLTRB(0, 0, GetWindowSize().width, 100), Paint{});
488 canvas.DrawRRect(Rect::MakeLTRB(0, 110, GetWindowSize().width, 210),
489 Size(10, 10), Paint{.color = Color::Magenta()});
490 canvas.ClipRect(Rect::MakeLTRB(0, 50, GetWindowSize().width, 150));
491 canvas.SaveLayer({.blend_mode = BlendMode::kSource}, std::nullopt,
492 ImageFilter::MakeBlur(Sigma(20.0), Sigma(20.0),
493 FilterContents::BlurStyle::kNormal,
494 Entity::TileMode::kClamp));
495 canvas.Restore();
496 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
497 }
498
499 #define FLT_FORWARD(mock, real, method) \
500 EXPECT_CALL(*mock, method()) \
501 .WillRepeatedly(::testing::Return(real->method()));
502
503 TEST_P(AiksTest, GaussianBlurWithoutDecalSupport) {
504 if (GetParam() != PlaygroundBackend::kMetal) {
505 GTEST_SKIP_(
506 "This backend doesn't yet support setting device capabilities.");
507 }
508 if (!WillRenderSomething()) {
509 // Sometimes these tests are run without playgrounds enabled which is
510 // pointless for this test since we are asserting that
511 // `SupportsDecalSamplerAddressMode` is called.
512 GTEST_SKIP_("This test requires playgrounds.");
513 }
514
515 std::shared_ptr<const Capabilities> old_capabilities =
516 GetContext()->GetCapabilities();
517 auto mock_capabilities = std::make_shared<MockCapabilities>();
518 EXPECT_CALL(*mock_capabilities, SupportsDecalSamplerAddressMode())
519 .Times(::testing::AtLeast(1))
520 .WillRepeatedly(::testing::Return(false));
521 FLT_FORWARD(mock_capabilities, old_capabilities, GetDefaultColorFormat);
522 FLT_FORWARD(mock_capabilities, old_capabilities, GetDefaultStencilFormat);
523 FLT_FORWARD(mock_capabilities, old_capabilities,
524 GetDefaultDepthStencilFormat);
525 FLT_FORWARD(mock_capabilities, old_capabilities, SupportsOffscreenMSAA);
526 FLT_FORWARD(mock_capabilities, old_capabilities,
527 SupportsImplicitResolvingMSAA);
528 FLT_FORWARD(mock_capabilities, old_capabilities, SupportsReadFromResolve);
529 FLT_FORWARD(mock_capabilities, old_capabilities, SupportsFramebufferFetch);
530 FLT_FORWARD(mock_capabilities, old_capabilities, SupportsSSBO);
531 FLT_FORWARD(mock_capabilities, old_capabilities, SupportsCompute);
532 FLT_FORWARD(mock_capabilities, old_capabilities,
533 SupportsTextureToTextureBlits);
534 FLT_FORWARD(mock_capabilities, old_capabilities, GetDefaultGlyphAtlasFormat);
535 ASSERT_TRUE(SetCapabilities(mock_capabilities).ok());
536
537 auto texture = std::make_shared<Image>(CreateTextureForFixture("boston.jpg"));
538 Canvas canvas;
539 canvas.Scale(GetContentScale() * 0.5);
540 canvas.DrawPaint({.color = Color::Black()});
541 canvas.DrawImage(
542 texture, Point(200, 200),
543 {
544 .image_filter = ImageFilter::MakeBlur(
545 Sigma(20.0), Sigma(20.0), FilterContents::BlurStyle::kNormal,
546 Entity::TileMode::kDecal),
547 });
548 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
549 }
550
551 TEST_P(AiksTest, GaussianBlurOneDimension) {
552 Canvas canvas;
553
554 canvas.Scale(GetContentScale());
555 canvas.Scale({0.5, 0.5, 1.0});
556 std::shared_ptr<Texture> boston = CreateTextureForFixture("boston.jpg");
557 canvas.DrawImage(std::make_shared<Image>(boston), Point(100, 100), Paint{});
558 canvas.SaveLayer({.blend_mode = BlendMode::kSource}, std::nullopt,
559 ImageFilter::MakeBlur(Sigma(50.0), Sigma(0.0),
560 FilterContents::BlurStyle::kNormal,
561 Entity::TileMode::kClamp));
562 canvas.Restore();
563 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
564 }
565
566 // Smoketest to catch issues with the coverage hint.
567 // Draws a rotated blurred image within a rectangle clip. The center of the clip
568 // rectangle is the center of the rotated image. The entire area of the clip
569 // rectangle should be filled with opaque colors output by the blur.
570 TEST_P(AiksTest, GaussianBlurRotatedAndClipped) {
571 Canvas canvas;
572 std::shared_ptr<Texture> boston = CreateTextureForFixture("boston.jpg");
573 Rect bounds =
574 Rect::MakeXYWH(0, 0, boston->GetSize().width, boston->GetSize().height);
575 Vector2 image_center = Vector2(bounds.GetSize() / 2);
576 Paint paint = {.image_filter =
577 ImageFilter::MakeBlur(Sigma(20.0), Sigma(20.0),
578 FilterContents::BlurStyle::kNormal,
579 Entity::TileMode::kDecal)};
580 Vector2 clip_size = {150, 75};
581 Vector2 center = Vector2(1024, 768) / 2;
582 canvas.Scale(GetContentScale());
583 canvas.ClipRect(
584 Rect::MakeLTRB(center.x, center.y, center.x, center.y).Expand(clip_size));
585 canvas.Translate({center.x, center.y, 0});
586 canvas.Scale({0.6, 0.6, 1});
587 canvas.Rotate(Degrees(25));
588
589 canvas.DrawImageRect(std::make_shared<Image>(boston), /*source=*/bounds,
590 /*dest=*/bounds.Shift(-image_center), paint);
591
592 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
593 }
594
595 TEST_P(AiksTest, GaussianBlurScaledAndClipped) {
596 Canvas canvas;
597 std::shared_ptr<Texture> boston = CreateTextureForFixture("boston.jpg");
598 Rect bounds =
599 Rect::MakeXYWH(0, 0, boston->GetSize().width, boston->GetSize().height);
600 Vector2 image_center = Vector2(bounds.GetSize() / 2);
601 Paint paint = {.image_filter =
602 ImageFilter::MakeBlur(Sigma(20.0), Sigma(20.0),
603 FilterContents::BlurStyle::kNormal,
604 Entity::TileMode::kDecal)};
605 Vector2 clip_size = {150, 75};
606 Vector2 center = Vector2(1024, 768) / 2;
607 canvas.Scale(GetContentScale());
608 canvas.ClipRect(
609 Rect::MakeLTRB(center.x, center.y, center.x, center.y).Expand(clip_size));
610 canvas.Translate({center.x, center.y, 0});
611 canvas.Scale({0.6, 0.6, 1});
612
613 canvas.DrawImageRect(std::make_shared<Image>(boston), /*source=*/bounds,
614 /*dest=*/bounds.Shift(-image_center), paint);
615
616 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
617 }
618
619 TEST_P(AiksTest, GaussianBlurRotatedAndClippedInteractive) {
620 std::shared_ptr<Texture> boston = CreateTextureForFixture("boston.jpg");
621
622 auto callback = [&](AiksContext& renderer) -> std::optional<Picture> {
623 const char* tile_mode_names[] = {"Clamp", "Repeat", "Mirror", "Decal"};
624 const Entity::TileMode tile_modes[] = {
625 Entity::TileMode::kClamp, Entity::TileMode::kRepeat,
626 Entity::TileMode::kMirror, Entity::TileMode::kDecal};
627
628 static float rotation = 0;
629 static float scale = 0.6;
630 static int selected_tile_mode = 3;
631
632 if (AiksTest::ImGuiBegin("Controls", nullptr,
633 ImGuiWindowFlags_AlwaysAutoResize)) {
634 ImGui::SliderFloat("Rotation (degrees)", &rotation, -180, 180);
635 ImGui::SliderFloat("Scale", &scale, 0, 2.0);
636 ImGui::Combo("Tile mode", &selected_tile_mode, tile_mode_names,
637 sizeof(tile_mode_names) / sizeof(char*));
638 ImGui::End();
639 }
640
641 Canvas canvas;
642 Rect bounds =
643 Rect::MakeXYWH(0, 0, boston->GetSize().width, boston->GetSize().height);
644 Vector2 image_center = Vector2(bounds.GetSize() / 2);
645 Paint paint = {.image_filter =
646 ImageFilter::MakeBlur(Sigma(20.0), Sigma(20.0),
647 FilterContents::BlurStyle::kNormal,
648 tile_modes[selected_tile_mode])};
649 static PlaygroundPoint point_a(Point(362, 309), 20, Color::Red());
650 static PlaygroundPoint point_b(Point(662, 459), 20, Color::Red());
651 auto [handle_a, handle_b] = DrawPlaygroundLine(point_a, point_b);
652 Vector2 center = Vector2(1024, 768) / 2;
653 canvas.Scale(GetContentScale());
654 canvas.ClipRect(
655 Rect::MakeLTRB(handle_a.x, handle_a.y, handle_b.x, handle_b.y));
656 canvas.Translate({center.x, center.y, 0});
657 canvas.Scale({scale, scale, 1});
658 canvas.Rotate(Degrees(rotation));
659
660 canvas.DrawImageRect(std::make_shared<Image>(boston), /*source=*/bounds,
661 /*dest=*/bounds.Shift(-image_center), paint);
662 return canvas.EndRecordingAsPicture();
663 };
664
665 ASSERT_TRUE(OpenPlaygroundHere(callback));
666 }
667
668 TEST_P(AiksTest, GaussianBlurRotatedNonUniform) {
669 auto callback = [&](AiksContext& renderer) -> std::optional<Picture> {
670 const char* tile_mode_names[] = {"Clamp", "Repeat", "Mirror", "Decal"};
671 const Entity::TileMode tile_modes[] = {
672 Entity::TileMode::kClamp, Entity::TileMode::kRepeat,
673 Entity::TileMode::kMirror, Entity::TileMode::kDecal};
674
675 static float rotation = 45;
676 static float scale = 0.6;
677 static int selected_tile_mode = 3;
678
679 if (AiksTest::ImGuiBegin("Controls", nullptr,
680 ImGuiWindowFlags_AlwaysAutoResize)) {
681 ImGui::SliderFloat("Rotation (degrees)", &rotation, -180, 180);
682 ImGui::SliderFloat("Scale", &scale, 0, 2.0);
683 ImGui::Combo("Tile mode", &selected_tile_mode, tile_mode_names,
684 sizeof(tile_mode_names) / sizeof(char*));
685 ImGui::End();
686 }
687
688 Canvas canvas;
689 Paint paint = {.color = Color::Green(),
690 .image_filter =
691 ImageFilter::MakeBlur(Sigma(50.0), Sigma(0.0),
692 FilterContents::BlurStyle::kNormal,
693 tile_modes[selected_tile_mode])};
694 Vector2 center = Vector2(1024, 768) / 2;
695 canvas.Scale(GetContentScale());
696 canvas.Translate({center.x, center.y, 0});
697 canvas.Scale({scale, scale, 1});
698 canvas.Rotate(Degrees(rotation));
699
700 canvas.DrawRRect(Rect::MakeXYWH(-100, -100, 200, 200), Size(10, 10), paint);
701 return canvas.EndRecordingAsPicture();
702 };
703
704 ASSERT_TRUE(OpenPlaygroundHere(callback));
705 }
706
707 // This addresses a bug where tiny blurs could result in mip maps that beyond
708 // the limits for the textures used for blurring.
709 // See also: b/323402168
710 TEST_P(AiksTest, GaussianBlurSolidColorTinyMipMap) {
711 for (int32_t i = 1; i < 5; ++i) {
712 Canvas canvas;
713 Scalar fi = i;
714 canvas.DrawPath(
715 PathBuilder{}
716 .MoveTo({100, 100})
717 .LineTo({100.f + fi, 100.f + fi})
718 .TakePath(),
719 {.color = Color::Chartreuse(),
720 .image_filter = ImageFilter::MakeBlur(
721 Sigma(0.1), Sigma(0.1), FilterContents::BlurStyle::kNormal,
722 Entity::TileMode::kClamp)});
723
724 Picture picture = canvas.EndRecordingAsPicture();
725 std::shared_ptr<RenderTargetCache> cache =
726 std::make_shared<RenderTargetCache>(
727 GetContext()->GetResourceAllocator());
728 AiksContext aiks_context(GetContext(), nullptr, cache);
729 std::shared_ptr<Image> image = picture.ToImage(aiks_context, {1024, 768});
730 EXPECT_TRUE(image) << " length " << i;
731 }
732 }
733
734 // This addresses a bug where tiny blurs could result in mip maps that beyond
735 // the limits for the textures used for blurring.
736 // See also: b/323402168
737 TEST_P(AiksTest, GaussianBlurBackdropTinyMipMap) {
738 for (int32_t i = 0; i < 5; ++i) {
739 Canvas canvas;
740 ISize clip_size = ISize(i, i);
741 canvas.ClipRect(
742 Rect::MakeXYWH(400, 400, clip_size.width, clip_size.height));
743 canvas.DrawCircle(
744 {400, 400}, 200,
745 {
746 .color = Color::Green(),
747 .image_filter = ImageFilter::MakeBlur(
748 Sigma(0.1), Sigma(0.1), FilterContents::BlurStyle::kNormal,
749 Entity::TileMode::kDecal),
750 });
751 canvas.Restore();
752
753 Picture picture = canvas.EndRecordingAsPicture();
754 std::shared_ptr<RenderTargetCache> cache =
755 std::make_shared<RenderTargetCache>(
756 GetContext()->GetResourceAllocator());
757 AiksContext aiks_context(GetContext(), nullptr, cache);
758 std::shared_ptr<Image> image = picture.ToImage(aiks_context, {1024, 768});
759 EXPECT_TRUE(image) << " clip rect " << i;
760 }
761 }
762
763 TEST_P(AiksTest, GaussianBlurAnimatedBackdrop) {
764 // This test is for checking out how stable rendering is when content is
765 // translated underneath a blur. Animating under a blur can cause
766 // *shimmering* to happen as a result of pixel alignment.
767 // See also: https://github.com/flutter/flutter/issues/140193
768 auto boston = std::make_shared<Image>(
769 CreateTextureForFixture("boston.jpg", /*enable_mipmapping=*/true));
770 ASSERT_TRUE(boston);
771 int64_t count = 0;
772 Scalar sigma = 20.0;
773 Scalar freq = 0.1;
774 Scalar amp = 50.0;
775 auto callback = [&](AiksContext& renderer) -> std::optional<Picture> {
776 if (AiksTest::ImGuiBegin("Controls", nullptr,
777 ImGuiWindowFlags_AlwaysAutoResize)) {
778 ImGui::SliderFloat("Sigma", &sigma, 0, 200);
779 ImGui::SliderFloat("Frequency", &freq, 0.01, 2.0);
780 ImGui::SliderFloat("Amplitude", &amp, 1, 100);
781 ImGui::End();
782 }
783
784 Canvas canvas;
785 canvas.Scale(GetContentScale());
786 Scalar y = amp * sin(freq * 2.0 * M_PI * count / 60);
787 canvas.DrawImage(boston,
788 Point(1024 / 2 - boston->GetSize().width / 2,
789 (768 / 2 - boston->GetSize().height / 2) + y),
790 {});
791 static PlaygroundPoint point_a(Point(100, 100), 20, Color::Red());
792 static PlaygroundPoint point_b(Point(900, 700), 20, Color::Red());
793 auto [handle_a, handle_b] = DrawPlaygroundLine(point_a, point_b);
794 canvas.ClipRect(
795 Rect::MakeLTRB(handle_a.x, handle_a.y, handle_b.x, handle_b.y));
796 canvas.ClipRect(Rect::MakeLTRB(100, 100, 900, 700));
797 canvas.SaveLayer({.blend_mode = BlendMode::kSource}, std::nullopt,
798 ImageFilter::MakeBlur(Sigma(sigma), Sigma(sigma),
799 FilterContents::BlurStyle::kNormal,
800 Entity::TileMode::kClamp));
801 count += 1;
802 return canvas.EndRecordingAsPicture();
803 };
804 ASSERT_TRUE(OpenPlaygroundHere(callback));
805 }
806
807 TEST_P(AiksTest, GaussianBlurStyleInnerGradient) {
808 Canvas canvas;
809 canvas.Scale(GetContentScale());
810
811 canvas.DrawPaint({.color = Color(0.1, 0.1, 0.1, 1.0)});
812
813 std::vector<Color> colors = {Color{0.9568, 0.2627, 0.2118, 1.0},
814 Color{0.7568, 0.2627, 0.2118, 1.0}};
815 std::vector<Scalar> stops = {0.0, 1.0};
816
817 Paint paint;
818 paint.color_source = ColorSource::MakeLinearGradient(
819 {0, 0}, {200, 200}, std::move(colors), std::move(stops),
820 Entity::TileMode::kMirror, {});
821 paint.mask_blur_descriptor = Paint::MaskBlurDescriptor{
822 .style = FilterContents::BlurStyle::kInner,
823 .sigma = Sigma(30),
824 };
825 canvas.DrawPath(PathBuilder()
826 .MoveTo({200, 200})
827 .LineTo({300, 400})
828 .LineTo({100, 400})
829 .Close()
830 .TakePath(),
831 paint);
832
833 // Draw another thing to make sure the clip area is reset.
834 Paint red;
835 red.color = Color::Red();
836 canvas.DrawRect(Rect::MakeXYWH(0, 0, 200, 200), red);
837 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
838 }
839
840 TEST_P(AiksTest, GaussianBlurStyleSolidGradient) {
841 Canvas canvas;
842 canvas.Scale(GetContentScale());
843
844 canvas.DrawPaint({.color = Color(0.1, 0.1, 0.1, 1.0)});
845
846 std::vector<Color> colors = {Color{0.9568, 0.2627, 0.2118, 1.0},
847 Color{0.7568, 0.2627, 0.2118, 1.0}};
848 std::vector<Scalar> stops = {0.0, 1.0};
849
850 Paint paint;
851 paint.color_source = ColorSource::MakeLinearGradient(
852 {0, 0}, {200, 200}, std::move(colors), std::move(stops),
853 Entity::TileMode::kMirror, {});
854 paint.mask_blur_descriptor = Paint::MaskBlurDescriptor{
855 .style = FilterContents::BlurStyle::kSolid,
856 .sigma = Sigma(30),
857 };
858 canvas.DrawPath(PathBuilder()
859 .MoveTo({200, 200})
860 .LineTo({300, 400})
861 .LineTo({100, 400})
862 .Close()
863 .TakePath(),
864 paint);
865
866 // Draw another thing to make sure the clip area is reset.
867 Paint red;
868 red.color = Color::Red();
869 canvas.DrawRect(Rect::MakeXYWH(0, 0, 200, 200), red);
870 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
871 }
872
873 TEST_P(AiksTest, GaussianBlurStyleOuterGradient) {
874 Canvas canvas;
875 canvas.Scale(GetContentScale());
876
877 canvas.DrawPaint({.color = Color(0.1, 0.1, 0.1, 1.0)});
878
879 std::vector<Color> colors = {Color{0.9568, 0.2627, 0.2118, 1.0},
880 Color{0.7568, 0.2627, 0.2118, 1.0}};
881 std::vector<Scalar> stops = {0.0, 1.0};
882
883 Paint paint;
884 paint.color_source = ColorSource::MakeLinearGradient(
885 {0, 0}, {200, 200}, std::move(colors), std::move(stops),
886 Entity::TileMode::kMirror, {});
887 paint.mask_blur_descriptor = Paint::MaskBlurDescriptor{
888 .style = FilterContents::BlurStyle::kOuter,
889 .sigma = Sigma(30),
890 };
891 canvas.DrawPath(PathBuilder()
892 .MoveTo({200, 200})
893 .LineTo({300, 400})
894 .LineTo({100, 400})
895 .Close()
896 .TakePath(),
897 paint);
898
899 // Draw another thing to make sure the clip area is reset.
900 Paint red;
901 red.color = Color::Red();
902 canvas.DrawRect(Rect::MakeXYWH(0, 0, 200, 200), red);
903 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
904 }
905
906 TEST_P(AiksTest, GaussianBlurStyleInner) {
907 Canvas canvas;
908 canvas.Scale(GetContentScale());
909
910 canvas.DrawPaint({.color = Color(0.1, 0.1, 0.1, 1.0)});
911
912 Paint paint;
913 paint.color = Color::Green();
914 paint.mask_blur_descriptor = Paint::MaskBlurDescriptor{
915 .style = FilterContents::BlurStyle::kInner,
916 .sigma = Sigma(30),
917 };
918 canvas.DrawPath(PathBuilder()
919 .MoveTo({200, 200})
920 .LineTo({300, 400})
921 .LineTo({100, 400})
922 .Close()
923 .TakePath(),
924 paint);
925
926 // Draw another thing to make sure the clip area is reset.
927 Paint red;
928 red.color = Color::Red();
929 canvas.DrawRect(Rect::MakeXYWH(0, 0, 200, 200), red);
930
931 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
932 }
933
934 TEST_P(AiksTest, GaussianBlurStyleOuter) {
935 Canvas canvas;
936 canvas.Scale(GetContentScale());
937
938 canvas.DrawPaint({.color = Color(0.1, 0.1, 0.1, 1.0)});
939
940 Paint paint;
941 paint.color = Color::Green();
942 paint.mask_blur_descriptor = Paint::MaskBlurDescriptor{
943 .style = FilterContents::BlurStyle::kOuter,
944 .sigma = Sigma(30),
945 };
946 canvas.DrawPath(PathBuilder()
947 .MoveTo({200, 200})
948 .LineTo({300, 400})
949 .LineTo({100, 400})
950 .Close()
951 .TakePath(),
952 paint);
953
954 // Draw another thing to make sure the clip area is reset.
955 Paint red;
956 red.color = Color::Red();
957 canvas.DrawRect(Rect::MakeXYWH(0, 0, 200, 200), red);
958
959 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
960 }
961
962 TEST_P(AiksTest, GaussianBlurStyleSolid) {
963 Canvas canvas;
964 canvas.Scale(GetContentScale());
965
966 canvas.DrawPaint({.color = Color(0.1, 0.1, 0.1, 1.0)});
967
968 Paint paint;
969 paint.color = Color::Green();
970 paint.mask_blur_descriptor = Paint::MaskBlurDescriptor{
971 .style = FilterContents::BlurStyle::kSolid,
972 .sigma = Sigma(30),
973 };
974 canvas.DrawPath(PathBuilder()
975 .MoveTo({200, 200})
976 .LineTo({300, 400})
977 .LineTo({100, 400})
978 .Close()
979 .TakePath(),
980 paint);
981
982 // Draw another thing to make sure the clip area is reset.
983 Paint red;
984 red.color = Color::Red();
985 canvas.DrawRect(Rect::MakeXYWH(0, 0, 200, 200), red);
986
987 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
988 }
989
990 TEST_P(AiksTest, MaskBlurTexture) {
991 Scalar sigma = 30;
992 auto callback = [&](AiksContext& renderer) -> std::optional<Picture> {
993 if (AiksTest::ImGuiBegin("Controls", nullptr,
994 ImGuiWindowFlags_AlwaysAutoResize)) {
995 ImGui::SliderFloat("Sigma", &sigma, 0, 500);
996 ImGui::End();
997 }
998 Canvas canvas;
999 canvas.Scale(GetContentScale());
1000 Paint paint;
1001 paint.color = Color::Green();
1002 paint.mask_blur_descriptor = Paint::MaskBlurDescriptor{
1003 .style = FilterContents::BlurStyle::kNormal,
1004 .sigma = Sigma(sigma),
1005 };
1006 std::shared_ptr<Texture> boston = CreateTextureForFixture("boston.jpg");
1007 canvas.DrawImage(std::make_shared<Image>(boston), {200, 200}, paint);
1008 Paint red;
1009 red.color = Color::Red();
1010 canvas.DrawRect(Rect::MakeXYWH(0, 0, 200, 200), red);
1011 return canvas.EndRecordingAsPicture();
1012 };
1013 ASSERT_TRUE(OpenPlaygroundHere(callback));
1014 }
1015
1016 TEST_P(AiksTest, GuassianBlurUpdatesMipmapContents) {
1017 // This makes sure if mip maps are recycled across invocations of blurs the
1018 // contents get updated each frame correctly. If they aren't updated the color
1019 // inside the blur and outside the blur will be different.
1020 //
1021 // If there is some change to render target caching this could display a false
1022 // positive in the future. Also, if the LOD that is rendered is 1 it could
1023 // present a false positive.
1024 int32_t count = 0;
1025 auto callback = [&](AiksContext& renderer) -> std::optional<Picture> {
1026 Canvas canvas;
1027 if (count++ == 0) {
1028 canvas.DrawCircle({100, 100}, 50, {.color = Color::CornflowerBlue()});
1029 } else {
1030 canvas.DrawCircle({100, 100}, 50, {.color = Color::Chartreuse()});
1031 }
1032 canvas.ClipRRect(Rect::MakeLTRB(75, 50, 375, 275), {20, 20});
1033 canvas.SaveLayer({.blend_mode = BlendMode::kSource}, std::nullopt,
1034 ImageFilter::MakeBlur(Sigma(30.0), Sigma(30.0),
1035 FilterContents::BlurStyle::kNormal,
1036 Entity::TileMode::kClamp));
1037 canvas.Restore();
1038 return canvas.EndRecordingAsPicture();
1039 };
1040
1041 ASSERT_TRUE(OpenPlaygroundHere(callback));
1042 }
1043
1044 TEST_P(AiksTest, GaussianBlurSetsMipCountOnPass) {
1045 Canvas canvas;
1046 canvas.DrawCircle({100, 100}, 50, {.color = Color::CornflowerBlue()});
1047 canvas.SaveLayer({}, std::nullopt,
1048 ImageFilter::MakeBlur(Sigma(3), Sigma(3),
1049 FilterContents::BlurStyle::kNormal,
1050 Entity::TileMode::kClamp));
1051 canvas.Restore();
1052
1053 Picture picture = canvas.EndRecordingAsPicture();
1054 EXPECT_EQ(4, picture.pass->GetRequiredMipCount());
1055 }
1056
1057 TEST_P(AiksTest, GaussianBlurAllocatesCorrectMipCountRenderTarget) {
1058 size_t blur_required_mip_count =
1059 GetParam() == PlaygroundBackend::kOpenGLES ? 1 : 4;
1060
1061 Canvas canvas;
1062 canvas.DrawCircle({100, 100}, 50, {.color = Color::CornflowerBlue()});
1063 canvas.SaveLayer({}, std::nullopt,
1064 ImageFilter::MakeBlur(Sigma(3), Sigma(3),
1065 FilterContents::BlurStyle::kNormal,
1066 Entity::TileMode::kClamp));
1067 canvas.Restore();
1068
1069 Picture picture = canvas.EndRecordingAsPicture();
1070 std::shared_ptr<RenderTargetCache> cache =
1071 std::make_shared<RenderTargetCache>(GetContext()->GetResourceAllocator());
1072 AiksContext aiks_context(GetContext(), nullptr, cache);
1073 picture.ToImage(aiks_context, {100, 100});
1074
1075 size_t max_mip_count = 0;
1076 for (auto it = cache->GetRenderTargetDataBegin();
1077 it != cache->GetRenderTargetDataEnd(); ++it) {
1078 max_mip_count = std::max(it->config.mip_count, max_mip_count);
1079 }
1080 EXPECT_EQ(max_mip_count, blur_required_mip_count);
1081 }
1082
1083 TEST_P(AiksTest, GaussianBlurMipMapNestedLayer) {
1084 fml::testing::LogCapture log_capture;
1085 size_t blur_required_mip_count =
1086 GetParam() == PlaygroundBackend::kOpenGLES ? 1 : 4;
1087
1088 Canvas canvas;
1089 canvas.DrawPaint({.color = Color::Wheat()});
1090 canvas.SaveLayer({.blend_mode = BlendMode::kMultiply});
1091 canvas.DrawCircle({100, 100}, 50, {.color = Color::CornflowerBlue()});
1092 canvas.SaveLayer({}, std::nullopt,
1093 ImageFilter::MakeBlur(Sigma(30), Sigma(30),
1094 FilterContents::BlurStyle::kNormal,
1095 Entity::TileMode::kClamp));
1096 canvas.DrawCircle({200, 200}, 50, {.color = Color::Chartreuse()});
1097
1098 Picture picture = canvas.EndRecordingAsPicture();
1099 std::shared_ptr<RenderTargetCache> cache =
1100 std::make_shared<RenderTargetCache>(GetContext()->GetResourceAllocator());
1101 AiksContext aiks_context(GetContext(), nullptr, cache);
1102 picture.ToImage(aiks_context, {100, 100});
1103
1104 size_t max_mip_count = 0;
1105 for (auto it = cache->GetRenderTargetDataBegin();
1106 it != cache->GetRenderTargetDataEnd(); ++it) {
1107 max_mip_count = std::max(it->config.mip_count, max_mip_count);
1108 }
1109 EXPECT_EQ(max_mip_count, blur_required_mip_count);
1110 // The log is FML_DLOG, so only check in debug builds.
1111 #ifndef NDEBUG
1112 if (GetParam() != PlaygroundBackend::kOpenGLES) {
1113 EXPECT_EQ(log_capture.str().find(GaussianBlurFilterContents::kNoMipsError),
1114 std::string::npos);
1115 } else {
1116 EXPECT_NE(log_capture.str().find(GaussianBlurFilterContents::kNoMipsError),
1117 std::string::npos);
1118 }
1119 #endif
1120 }
1121
1122 TEST_P(AiksTest, GaussianBlurMipMapImageFilter) {
1123 size_t blur_required_mip_count =
1124 GetParam() == PlaygroundBackend::kOpenGLES ? 1 : 4;
1125 fml::testing::LogCapture log_capture;
1126 Canvas canvas;
1127 canvas.SaveLayer(
1128 {.image_filter = ImageFilter::MakeBlur(Sigma(30), Sigma(30),
1129 FilterContents::BlurStyle::kNormal,
1130 Entity::TileMode::kClamp)});
1131 canvas.DrawCircle({200, 200}, 50, {.color = Color::Chartreuse()});
1132
1133 Picture picture = canvas.EndRecordingAsPicture();
1134 std::shared_ptr<RenderTargetCache> cache =
1135 std::make_shared<RenderTargetCache>(GetContext()->GetResourceAllocator());
1136 AiksContext aiks_context(GetContext(), nullptr, cache);
1137 picture.ToImage(aiks_context, {1024, 768});
1138
1139 size_t max_mip_count = 0;
1140 for (auto it = cache->GetRenderTargetDataBegin();
1141 it != cache->GetRenderTargetDataEnd(); ++it) {
1142 max_mip_count = std::max(it->config.mip_count, max_mip_count);
1143 }
1144 EXPECT_EQ(max_mip_count, blur_required_mip_count);
1145 // The log is FML_DLOG, so only check in debug builds.
1146 #ifndef NDEBUG
1147 if (GetParam() != PlaygroundBackend::kOpenGLES) {
1148 EXPECT_EQ(log_capture.str().find(GaussianBlurFilterContents::kNoMipsError),
1149 std::string::npos);
1150 } else {
1151 EXPECT_NE(log_capture.str().find(GaussianBlurFilterContents::kNoMipsError),
1152 std::string::npos);
1153 }
1154 #endif
1155 }
1156
1157 TEST_P(AiksTest, GaussianBlurMipMapSolidColor) {
1158 size_t blur_required_mip_count =
1159 GetParam() == PlaygroundBackend::kOpenGLES ? 1 : 4;
1160 fml::testing::LogCapture log_capture;
1161 Canvas canvas;
1162 canvas.DrawPath(PathBuilder{}
1163 .MoveTo({100, 100})
1164 .LineTo({200, 100})
1165 .LineTo({150, 200})
1166 .LineTo({50, 200})
1167 .Close()
1168 .TakePath(),
1169 {.color = Color::Chartreuse(),
1170 .image_filter = ImageFilter::MakeBlur(
1171 Sigma(30), Sigma(30), FilterContents::BlurStyle::kNormal,
1172 Entity::TileMode::kClamp)});
1173
1174 Picture picture = canvas.EndRecordingAsPicture();
1175 std::shared_ptr<RenderTargetCache> cache =
1176 std::make_shared<RenderTargetCache>(GetContext()->GetResourceAllocator());
1177 AiksContext aiks_context(GetContext(), nullptr, cache);
1178 picture.ToImage(aiks_context, {1024, 768});
1179
1180 size_t max_mip_count = 0;
1181 for (auto it = cache->GetRenderTargetDataBegin();
1182 it != cache->GetRenderTargetDataEnd(); ++it) {
1183 max_mip_count = std::max(it->config.mip_count, max_mip_count);
1184 }
1185 EXPECT_EQ(max_mip_count, blur_required_mip_count);
1186 // The log is FML_DLOG, so only check in debug builds.
1187 #ifndef NDEBUG
1188 if (GetParam() != PlaygroundBackend::kOpenGLES) {
1189 EXPECT_EQ(log_capture.str().find(GaussianBlurFilterContents::kNoMipsError),
1190 std::string::npos);
1191 } else {
1192 EXPECT_NE(log_capture.str().find(GaussianBlurFilterContents::kNoMipsError),
1193 std::string::npos);
1194 }
1195 #endif
1196 }
1197
1198 TEST_P(AiksTest, MaskBlurDoesntStretchContents) {
1199 Scalar sigma = 70;
1200 auto callback = [&](AiksContext& renderer) -> std::optional<Picture> {
1201 if (AiksTest::ImGuiBegin("Controls", nullptr,
1202 ImGuiWindowFlags_AlwaysAutoResize)) {
1203 ImGui::SliderFloat("Sigma", &sigma, 0, 500);
1204 ImGui::End();
1205 }
1206 Canvas canvas;
1207 canvas.Scale(GetContentScale());
1208 canvas.DrawPaint({.color = Color(0.1, 0.1, 0.1, 1.0)});
1209
1210 std::shared_ptr<Texture> boston = CreateTextureForFixture("boston.jpg");
1211 ColorSource image_source = ColorSource::MakeImage(
1212 boston, Entity::TileMode::kRepeat, Entity::TileMode::kRepeat, {}, {});
1213
1214 canvas.Transform(Matrix::MakeTranslation({100, 100, 0}) *
1215 Matrix::MakeScale({0.5, 0.5, 1.0}));
1216 Paint paint = {
1217 .color_source = image_source,
1218 .mask_blur_descriptor =
1219 Paint::MaskBlurDescriptor{
1220 .style = FilterContents::BlurStyle::kNormal,
1221 .sigma = Sigma(sigma),
1222 },
1223 };
1224 canvas.DrawRect(
1225 Rect::MakeXYWH(0, 0, boston->GetSize().width, boston->GetSize().height),
1226 paint);
1227
1228 return canvas.EndRecordingAsPicture();
1229 };
1230 ASSERT_TRUE(OpenPlaygroundHere(callback));
1231 }
1232
1233 } // namespace testing
1234 } // namespace impeller
impeller::Color::DarkMagenta
static constexpr Color DarkMagenta()
Definition: color.h:388
impeller::ISize
ISize64 ISize
Definition: size.h:140
impeller::Color::Blue
static constexpr Color Blue()
Definition: color.h:278
impeller::testing::MaskBlurTestConfig::alpha
Scalar alpha
Definition: aiks_blur_unittests.cc:298
impeller::AiksPlayground
Definition: aiks_playground.h:17
impeller::Entity::TileMode::kClamp
@ kClamp
impeller::Canvas::EndRecordingAsPicture
Picture EndRecordingAsPicture()
Definition: canvas.cc:804
impeller::Picture::pass
std::unique_ptr< EntityPass > pass
Definition: picture.h:18
impeller::Canvas::DrawRRect
void DrawRRect(const Rect &rect, const Size &corner_radii, const Paint &paint)
Definition: canvas.cc:540
impeller::TPoint::y
Type y
Definition: point.h:31
impeller::Scalar
float Scalar
Definition: scalar.h:18
impeller::AiksContext
Definition: aiks_context.h:19
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:766
impeller::testing::MaskBlurTestConfig::sigma
Scalar sigma
Definition: aiks_blur_unittests.cc:297
impeller::Color::Red
static constexpr Color Red()
Definition: color.h:274
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:166
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:68
impeller::Entity::TileMode::kDecal
@ kDecal
impeller::BlendMode::kSource
@ kSource
impeller::PlaygroundBackend::kMetal
@ kMetal
impeller::Color::Purple
static constexpr Color Purple()
Definition: color.h:744
impeller::Canvas
Definition: canvas.h:60
impeller::testing::MaskBlurTestConfig::blend_mode
BlendMode blend_mode
Definition: aiks_blur_unittests.cc:301
impeller::PathBuilder
Definition: path_builder.h:14
impeller::Paint::MaskBlurDescriptor::style
FilterContents::BlurStyle style
Definition: paint.h:49
impeller::Vector2
Point Vector2
Definition: point.h:326
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:48
impeller::testing::MaskBlurVariantTest
static Picture MaskBlurVariantTest(const AiksTest &test_context, const MaskBlurTestConfig &config)
Definition: aiks_blur_unittests.cc:304
impeller::Color::CornflowerBlue
static constexpr Color CornflowerBlue()
Definition: color.h:344
impeller::testing::MaskBlurTestConfig
Definition: aiks_blur_unittests.cc:295
impeller::Color::Yellow
static constexpr Color Yellow()
Definition: color.h:844
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:225
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:836
impeller::Paint::color_source
ColorSource color_source
Definition: paint.h:69
impeller::Canvas::DrawRect
void DrawRect(const Rect &rect, const Paint &paint)
Definition: canvas.cc:495
impeller::MoveTo
void MoveTo(PathBuilder *builder, Scalar x, Scalar y)
Definition: tessellator.cc:20
impeller::PathBuilder::AddRect
PathBuilder & AddRect(Rect rect)
Definition: path_builder.cc:117
impeller::Color::Maroon
static constexpr Color Maroon()
Definition: color.h:616
impeller::TRect::Shift
constexpr TRect< T > Shift(T dx, T dy) const
Returns a new rectangle translated by the given offset.
Definition: rect.h:589
impeller::Canvas::DrawImage
void DrawImage(const std::shared_ptr< Image > &image, Point offset, const Paint &paint, SamplerDescriptor sampler={})
Definition: canvas.cc:752
impeller::testing::TEST_P
TEST_P(AiksTest, CanRenderAdvancedBlendColorFilterWithSaveLayer)
Definition: aiks_blend_unittests.cc:21
impeller::Entity::TileMode::kMirror
@ kMirror
path_builder.h
impeller::Color::Magenta
static constexpr Color Magenta()
Definition: color.h:612
impeller::Picture
Definition: picture.h:17
impeller::TSize
Definition: size.h:19
impeller::Point
TPoint< Scalar > Point
Definition: point.h:322
impeller::testing::kPaintVariations
static const std::map< std::string, MaskBlurTestConfig > kPaintVariations
Definition: aiks_blur_unittests.cc:378
impeller::ColorSource
Definition: color_source.h:99
MASK_BLUR_VARIANT_TEST
#define MASK_BLUR_VARIANT_TEST(config)
Definition: aiks_blur_unittests.cc:442
impeller::Canvas::Scale
void Scale(const Vector2 &scale)
Definition: canvas.cc:315
impeller::Color::OrangeRed
static constexpr Color OrangeRed()
Definition: color.h:696
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:299
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:296
impeller::BlendMode::kClear
@ kClear
impeller::Color::WithAlpha
constexpr Color WithAlpha(Scalar new_alpha) const
Definition: color.h:280
impeller::Canvas::DrawCircle
void DrawCircle(const Point &center, Scalar radius, const Paint &paint)
Definition: canvas.cc:566
impeller::Color::Grey
static constexpr Color Grey()
Definition: color.h:496
impeller::Color::White
static constexpr Color White()
Definition: color.h:266
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
virtual bool Restore()
Definition: canvas.cc:257
impeller::Radians
Definition: scalar.h:38
impeller::Color::Chartreuse
static constexpr Color Chartreuse()
Definition: color.h:332
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:424
canvas.h
impeller::Color::Green
static constexpr Color Green()
Definition: color.h:276
impeller::Canvas::DrawPath
void DrawPath(const Path &path, const Paint &paint)
Definition: canvas.cc:343
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:352
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:640
impeller::Close
void Close(PathBuilder *builder)
Definition: tessellator.cc:38
impeller::Color::AntiqueWhite
static constexpr Color AntiqueWhite()
Definition: color.h:288
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:514
impeller::BlendMode::kExclusion
@ kExclusion
impeller::Color::GreenYellow
static constexpr Color GreenYellow()
Definition: color.h:492
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:317
impeller::PlaygroundBackend::kOpenGLES
@ kOpenGLES
impeller::Canvas::Rotate
void Rotate(Radians radians)
Definition: canvas.cc:327
impeller::LineTo
void LineTo(PathBuilder *builder, Scalar x, Scalar y)
Definition: tessellator.cc:24
impeller::Playground::GetContentScale
Point GetContentScale() const
Definition: playground.cc:192
impeller::DrawPlaygroundPoint
Point DrawPlaygroundPoint(PlaygroundPoint &point)
Definition: widgets.cc:9
impeller::Color::Orange
static constexpr Color Orange()
Definition: color.h:692
impeller::PlaygroundPoint
Definition: widgets.h:17
impeller::GaussianBlurFilterContents::kNoMipsError
static std::string_view kNoMipsError
Definition: gaussian_blur_filter_contents.h:49
FLT_FORWARD
#define FLT_FORWARD(mock, real, method)
Definition: aiks_blur_unittests.cc:499
impeller::TPoint< Scalar >
impeller::Canvas::Transform
void Transform(const Matrix &transform)
Definition: canvas.cc:294
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:268
scale
const Scalar scale
Definition: stroke_path_geometry.cc:308
paint
const Paint & paint
Definition: color_source.cc:38
impeller::AiksPlayground::ImGuiBegin
static bool ImGuiBegin(const char *name, bool *p_open, ImGuiWindowFlags flags)
Definition: aiks_playground.cc:69
impeller::Degrees
Definition: scalar.h:46
impeller::Color::LimeGreen
static constexpr Color LimeGreen()
Definition: color.h:604
impeller::TSize::height
Type height
Definition: size.h:23
impeller::Color::DarkGreen
static constexpr Color DarkGreen()
Definition: color.h:376
impeller::Canvas::ClipRect
void ClipRect(const Rect &rect, Entity::ClipOperation clip_op=Entity::ClipOperation::kIntersect)
Definition: canvas.cc:599
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_blend_unittests.cc:18
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:81
impeller::BlendMode::kMultiply
@ kMultiply
impeller::Canvas::SaveLayer
virtual void SaveLayer(const Paint &paint, std::optional< Rect > bounds=std::nullopt, const std::shared_ptr< ImageFilter > &backdrop_filter=nullptr, ContentBoundsPromise bounds_promise=ContentBoundsPromise::kUnknown, uint32_t total_content_depth=kMaxDepth, bool can_distribute_opacity=false)
Definition: canvas.cc:842
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:318
impeller::BlendMode::kSourceOver
@ kSourceOver
impeller::Paint::blend_mode
BlendMode blend_mode
Definition: paint.h:76
impeller::testing::MaskBlurTestConfig::invert_colors
bool invert_colors
Definition: aiks_blur_unittests.cc:300
impeller::Color::Crimson
static constexpr Color Crimson()
Definition: color.h:352
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:605
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:311