| Flutter Impeller |

aiks_dl_blend_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 <memory>
6
7 #include "display_list/display_list.h"
8 #include "display_list/dl_sampling_options.h"
9 #include "display_list/dl_tile_mode.h"
10 #include "display_list/effects/dl_color_filter.h"
11 #include "display_list/effects/dl_color_source.h"
12 #include "display_list/effects/dl_mask_filter.h"
13 #include "flutter/impeller/display_list/aiks_unittests.h"
14
15 #include "flutter/display_list/dl_blend_mode.h"
16 #include "flutter/display_list/dl_builder.h"
17 #include "flutter/display_list/dl_color.h"
18 #include "flutter/display_list/dl_paint.h"
19 #include "flutter/impeller/display_list/dl_image_impeller.h"
20 #include "flutter/impeller/geometry/scalar.h"
21 #include "impeller/display_list/aiks_context.h"
22 #include "impeller/display_list/dl_dispatcher.h"
23 #include "impeller/playground/playground.h"
24 #include "impeller/playground/playground_test.h"
25 #include "impeller/renderer/testing/mocks.h"
26 #include "include/core/SkMatrix.h"
27
28 ////////////////////////////////////////////////////////////////////////////////
29 // This is for tests of Canvas that are interested the results of rendering
30 // blends.
31 ////////////////////////////////////////////////////////////////////////////////
32
33 namespace impeller {
34 namespace testing {
35
36 using namespace flutter;
37
38 #define BLEND_MODE_TUPLE(blend_mode) {#blend_mode, BlendMode::k##blend_mode},
39
40 struct BlendModeSelection {
41 std::vector<const char*> blend_mode_names;
42 std::vector<BlendMode> blend_mode_values;
43 };
44
45 static BlendModeSelection GetBlendModeSelection() {
46 std::vector<const char*> blend_mode_names;
47 std::vector<BlendMode> blend_mode_values;
48 {
49 const std::vector<std::tuple<const char*, BlendMode>> blends = {
50 IMPELLER_FOR_EACH_BLEND_MODE(BLEND_MODE_TUPLE)};
51 assert(blends.size() ==
52 static_cast<size_t>(Entity::kLastAdvancedBlendMode) + 1);
53 for (const auto& [name, mode] : blends) {
54 blend_mode_names.push_back(name);
55 blend_mode_values.push_back(mode);
56 }
57 }
58
59 return {blend_mode_names, blend_mode_values};
60 }
61
62 TEST_P(AiksTest, CanRenderAdvancedBlendColorFilterWithSaveLayer) {
63 DisplayListBuilder builder;
64
65 SkRect layer_rect = SkRect::MakeXYWH(0, 0, 500, 500);
66 builder.ClipRect(layer_rect);
67
68 DlPaint save_paint;
69 save_paint.setColorFilter(DlColorFilter::MakeBlend(
70 DlColor::RGBA(0, 1, 0, 0.5), DlBlendMode::kDifference));
71 builder.SaveLayer(&layer_rect, &save_paint);
72
73 DlPaint paint;
74 paint.setColor(DlColor::kBlack());
75 builder.DrawPaint(paint);
76 paint.setColor(DlColor::kWhite());
77 builder.DrawRect(SkRect::MakeXYWH(100, 100, 300, 300), paint);
78 builder.Restore();
79
80 ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
81 }
82
83 TEST_P(AiksTest, BlendModeShouldCoverWholeScreen) {
84 DisplayListBuilder builder;
85 DlPaint paint;
86
87 paint.setColor(DlColor::kRed());
88 builder.DrawPaint(paint);
89
90 paint.setBlendMode(DlBlendMode::kSrcOver);
91 builder.SaveLayer(nullptr, &paint);
92
93 paint.setColor(DlColor::kWhite());
94 builder.DrawRect(SkRect::MakeXYWH(100, 100, 400, 400), paint);
95
96 paint.setBlendMode(DlBlendMode::kSrc);
97 builder.SaveLayer(nullptr, &paint);
98
99 paint.setColor(DlColor::kBlue());
100 builder.DrawRect(SkRect::MakeXYWH(200, 200, 200, 200), paint);
101
102 builder.Restore();
103 builder.Restore();
104
105 ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
106 }
107
108 TEST_P(AiksTest, CanDrawPaintWithAdvancedBlend) {
109 DisplayListBuilder builder;
110
111 builder.Scale(0.2, 0.2);
112 DlPaint paint;
113 paint.setColor(DlColor::RGBA(
114 Color::MediumTurquoise().red, Color::MediumTurquoise().green,
115 Color::MediumTurquoise().blue, Color::MediumTurquoise().alpha));
116 builder.DrawPaint(paint);
117
118 paint.setColor(DlColor::RGBA(Color::OrangeRed().red, Color::OrangeRed().green,
119 Color::OrangeRed().blue, 0.5));
120 paint.setBlendMode(DlBlendMode::kHue);
121 builder.DrawPaint(paint);
122
123 ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
124 }
125
126 TEST_P(AiksTest, DrawPaintWithAdvancedBlendOverFilter) {
127 DlPaint paint;
128 paint.setColor(DlColor::kBlack());
129 paint.setMaskFilter(DlBlurMaskFilter::Make(DlBlurStyle::kNormal, 60));
130
131 DisplayListBuilder builder;
132 paint.setColor(DlColor::kWhite());
133 builder.DrawPaint(paint);
134 paint.setColor(DlColor::kBlack());
135 builder.DrawCircle(SkPoint{300, 300}, 200, paint);
136 paint.setColor(DlColor::kGreen());
137 paint.setBlendMode(DlBlendMode::kScreen);
138 builder.DrawPaint(paint);
139
140 ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
141 }
142
143 TEST_P(AiksTest, DrawAdvancedBlendPartlyOffscreen) {
144 DisplayListBuilder builder;
145
146 DlPaint draw_paint;
147 draw_paint.setColor(DlColor::kBlue());
148 builder.DrawPaint(draw_paint);
149 builder.Scale(2, 2);
150 builder.ClipRect(SkRect::MakeLTRB(0, 0, 200, 200));
151
152 std::vector<DlColor> colors = {DlColor::RGBA(0.9568, 0.2627, 0.2118, 1.0),
153 DlColor::RGBA(0.1294, 0.5882, 0.9529, 1.0)};
154 std::vector<Scalar> stops = {0.0, 1.0};
155
156 DlPaint paint;
157 DlMatrix matrix = DlMatrix::MakeScale({0.3, 0.3, 1.0});
158 paint.setColorSource(DlColorSource::MakeLinear(
159 /*start_point=*/{0, 0}, //
160 /*end_point=*/{100, 100}, //
161 /*stop_count=*/colors.size(), //
162 /*colors=*/colors.data(), //
163 /*stops=*/stops.data(), //
164 /*tile_mode=*/DlTileMode::kRepeat, //
165 /*matrix=*/&matrix //
166 ));
167 paint.setBlendMode(DlBlendMode::kLighten);
168
169 builder.DrawCircle(SkPoint{100, 100}, 100, paint);
170 ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
171 }
172
173 TEST_P(AiksTest, PaintBlendModeIsRespected) {
174 DlPaint paint;
175 DisplayListBuilder builder;
176 // Default is kSourceOver.
177
178 paint.setColor(DlColor::RGBA(1, 0, 0, 0.5));
179 builder.DrawCircle(SkPoint{150, 200}, 100, paint);
180
181 paint.setColor(DlColor::RGBA(0, 1, 0, 0.5));
182 builder.DrawCircle(SkPoint{250, 200}, 100, paint);
183
184 paint.setBlendMode(DlBlendMode::kPlus);
185
186 paint.setColor(DlColor::kRed());
187 builder.DrawCircle(SkPoint{450, 250}, 100, paint);
188
189 paint.setColor(DlColor::kGreen());
190 builder.DrawCircle(SkPoint{550, 250}, 100, paint);
191
192 paint.setColor(DlColor::kBlue());
193 builder.DrawCircle(SkPoint{500, 150}, 100, paint);
194
195 ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
196 }
197
198 // Compare results with https://api.flutter.dev/flutter/dart-ui/BlendMode.html
199 TEST_P(AiksTest, ColorFilterBlend) {
200 bool has_color_filter = true;
201 auto callback = [&]() -> sk_sp<DisplayList> {
202 if (AiksTest::ImGuiBegin("Controls", nullptr,
203 ImGuiWindowFlags_AlwaysAutoResize)) {
204 ImGui::Checkbox("has color filter", &has_color_filter);
205 ImGui::End();
206 }
207
208 DisplayListBuilder builder;
209 builder.Scale(GetContentScale().x, GetContentScale().y);
210
211 auto src_image =
212 DlImageImpeller::Make(CreateTextureForFixture("blend_mode_src.png"));
213 auto dst_image =
214 DlImageImpeller::Make(CreateTextureForFixture("blend_mode_dst.png"));
215
216 std::vector<DlBlendMode> blend_modes = {
217 DlBlendMode::kSrc, DlBlendMode::kSrcATop, DlBlendMode::kSrcOver,
218 DlBlendMode::kSrcIn, DlBlendMode::kSrcOut, DlBlendMode::kDst,
219 DlBlendMode::kDstATop, DlBlendMode::kDstOver, DlBlendMode::kDstIn,
220 DlBlendMode::kDstOut, DlBlendMode::kClear, DlBlendMode::kXor};
221
222 for (uint32_t i = 0; i < blend_modes.size(); ++i) {
223 builder.Save();
224 builder.Translate((i % 5) * 200, (i / 5) * 200);
225 builder.Scale(0.4, 0.4);
226 {
227 DlPaint dstPaint;
228 builder.DrawImage(dst_image, SkPoint{0, 0},
229 DlImageSampling::kMipmapLinear, &dstPaint);
230 }
231 {
232 DlPaint srcPaint;
233 srcPaint.setBlendMode(blend_modes[i]);
234 if (has_color_filter) {
235 std::shared_ptr<const DlColorFilter> color_filter =
236 DlColorFilter::MakeBlend(DlColor::RGBA(0.9, 0.5, 0.0, 1.0),
237 DlBlendMode::kSrcIn);
238 srcPaint.setColorFilter(color_filter);
239 }
240 builder.DrawImage(src_image, SkPoint{0, 0},
241 DlImageSampling::kMipmapLinear, &srcPaint);
242 }
243 builder.Restore();
244 }
245 return builder.Build();
246 };
247 ASSERT_TRUE(OpenPlaygroundHere(callback));
248 }
249
250 // Verification for: https://github.com/flutter/flutter/issues/155691
251 TEST_P(AiksTest, ColorFilterAdvancedBlend) {
252 bool has_color_filter = true;
253 auto callback = [&]() -> sk_sp<DisplayList> {
254 if (AiksTest::ImGuiBegin("Controls", nullptr,
255 ImGuiWindowFlags_AlwaysAutoResize)) {
256 ImGui::Checkbox("has color filter", &has_color_filter);
257 ImGui::End();
258 }
259
260 DisplayListBuilder builder;
261 builder.Scale(GetContentScale().x, GetContentScale().y);
262
263 auto src_image =
264 DlImageImpeller::Make(CreateTextureForFixture("blend_mode_src.png"));
265 auto dst_image =
266 DlImageImpeller::Make(CreateTextureForFixture("blend_mode_dst.png"));
267
268 std::vector<DlBlendMode> blend_modes = {
269 DlBlendMode::kScreen, DlBlendMode::kOverlay,
270 DlBlendMode::kDarken, DlBlendMode::kLighten,
271 DlBlendMode::kColorDodge, DlBlendMode::kColorBurn,
272 DlBlendMode::kHardLight, DlBlendMode::kSoftLight,
273 DlBlendMode::kDifference, DlBlendMode::kExclusion,
274 DlBlendMode::kMultiply, DlBlendMode::kHue,
275 DlBlendMode::kSaturation, DlBlendMode::kColor,
276 DlBlendMode::kLuminosity,
277 };
278
279 for (uint32_t i = 0; i < blend_modes.size(); ++i) {
280 builder.Save();
281 builder.Translate((i % 5) * 200, (i / 5) * 200);
282 builder.Scale(0.4, 0.4);
283 {
284 DlPaint dstPaint;
285 builder.DrawImage(dst_image, SkPoint{0, 0},
286 DlImageSampling::kMipmapLinear, &dstPaint);
287 }
288 {
289 DlPaint srcPaint;
290 srcPaint.setBlendMode(blend_modes[i]);
291 if (has_color_filter) {
292 std::shared_ptr<const DlColorFilter> color_filter =
293 DlColorFilter::MakeBlend(DlColor::RGBA(0.9, 0.5, 0.0, 1.0),
294 DlBlendMode::kSrcIn);
295 srcPaint.setColorFilter(color_filter);
296 }
297 builder.DrawImage(src_image, SkPoint{0, 0},
298 DlImageSampling::kMipmapLinear, &srcPaint);
299 }
300 builder.Restore();
301 }
302 return builder.Build();
303 };
304 ASSERT_TRUE(OpenPlaygroundHere(callback));
305 }
306
307 // Variant of the https://github.com/flutter/flutter/issues/155691 test that
308 // uses an advanced blend in the color filter and disables framebuffer fetch
309 // to force usage of BlendFilterContents::CreateForegroundAdvancedBlend.
310 TEST_P(AiksTest, ColorFilterAdvancedBlendNoFbFetch) {
311 if (GetParam() != PlaygroundBackend::kMetal) {
312 GTEST_SKIP()
313 << "This backend doesn't yet support setting device capabilities.";
314 }
315 if (!WillRenderSomething()) {
316 GTEST_SKIP() << "This test requires playgrounds.";
317 }
318
319 std::shared_ptr<const Capabilities> old_capabilities =
320 GetContext()->GetCapabilities();
321 auto mock_capabilities = std::make_shared<MockCapabilities>();
322 EXPECT_CALL(*mock_capabilities, SupportsFramebufferFetch())
323 .Times(::testing::AtLeast(1))
324 .WillRepeatedly(::testing::Return(false));
325 FLT_FORWARD(mock_capabilities, old_capabilities, GetDefaultColorFormat);
326 FLT_FORWARD(mock_capabilities, old_capabilities, GetDefaultStencilFormat);
327 FLT_FORWARD(mock_capabilities, old_capabilities,
328 GetDefaultDepthStencilFormat);
329 FLT_FORWARD(mock_capabilities, old_capabilities, SupportsOffscreenMSAA);
330 FLT_FORWARD(mock_capabilities, old_capabilities,
331 SupportsImplicitResolvingMSAA);
332 FLT_FORWARD(mock_capabilities, old_capabilities, SupportsReadFromResolve);
333 FLT_FORWARD(mock_capabilities, old_capabilities, SupportsSSBO);
334 FLT_FORWARD(mock_capabilities, old_capabilities, SupportsCompute);
335 FLT_FORWARD(mock_capabilities, old_capabilities,
336 SupportsTextureToTextureBlits);
337 FLT_FORWARD(mock_capabilities, old_capabilities, GetDefaultGlyphAtlasFormat);
338 FLT_FORWARD(mock_capabilities, old_capabilities, SupportsTriangleFan);
339 FLT_FORWARD(mock_capabilities, old_capabilities,
340 SupportsDecalSamplerAddressMode);
341 FLT_FORWARD(mock_capabilities, old_capabilities, SupportsPrimitiveRestart);
342 ASSERT_TRUE(SetCapabilities(mock_capabilities).ok());
343
344 bool has_color_filter = true;
345 auto callback = [&]() -> sk_sp<DisplayList> {
346 if (AiksTest::ImGuiBegin("Controls", nullptr,
347 ImGuiWindowFlags_AlwaysAutoResize)) {
348 ImGui::Checkbox("has color filter", &has_color_filter);
349 ImGui::End();
350 }
351
352 DisplayListBuilder builder;
353 builder.Scale(GetContentScale().x, GetContentScale().y);
354
355 auto src_image =
356 DlImageImpeller::Make(CreateTextureForFixture("blend_mode_src.png"));
357 auto dst_image =
358 DlImageImpeller::Make(CreateTextureForFixture("blend_mode_dst.png"));
359
360 std::vector<DlBlendMode> blend_modes = {
361 DlBlendMode::kScreen, DlBlendMode::kOverlay,
362 DlBlendMode::kDarken, DlBlendMode::kLighten,
363 DlBlendMode::kColorDodge, DlBlendMode::kColorBurn,
364 DlBlendMode::kHardLight, DlBlendMode::kSoftLight,
365 DlBlendMode::kDifference, DlBlendMode::kExclusion,
366 DlBlendMode::kMultiply, DlBlendMode::kHue,
367 DlBlendMode::kSaturation, DlBlendMode::kColor,
368 DlBlendMode::kLuminosity,
369 };
370
371 for (uint32_t i = 0; i < blend_modes.size(); ++i) {
372 builder.Save();
373 builder.Translate((i % 5) * 200, (i / 5) * 200);
374 builder.Scale(0.4, 0.4);
375 {
376 DlPaint dstPaint;
377 builder.DrawImage(dst_image, SkPoint{0, 0},
378 DlImageSampling::kMipmapLinear, &dstPaint);
379 }
380 {
381 DlPaint srcPaint;
382 srcPaint.setBlendMode(blend_modes[i]);
383 if (has_color_filter) {
384 std::shared_ptr<const DlColorFilter> color_filter =
385 DlColorFilter::MakeBlend(DlColor::RGBA(0.9, 0.5, 0.0, 1.0),
386 DlBlendMode::kMultiply);
387 srcPaint.setColorFilter(color_filter);
388 }
389 builder.DrawImage(src_image, SkPoint{0, 0},
390 DlImageSampling::kMipmapLinear, &srcPaint);
391 }
392 builder.Restore();
393 }
394 return builder.Build();
395 };
396 ASSERT_TRUE(OpenPlaygroundHere(callback));
397 }
398
399 // Bug: https://github.com/flutter/flutter/issues/142549
400 TEST_P(AiksTest, BlendModePlusAlphaWideGamut) {
401 EXPECT_EQ(GetContext()->GetCapabilities()->GetDefaultColorFormat(),
402 PixelFormat::kB10G10R10A10XR);
403 auto texture = CreateTextureForFixture("airplane.jpg",
404 /*enable_mipmapping=*/true);
405
406 DisplayListBuilder builder;
407 DlPaint paint;
408 builder.Scale(GetContentScale().x, GetContentScale().y);
409
410 paint.setColor(DlColor::RGBA(0.9, 1, 0.9, 1.0));
411 builder.DrawPaint(paint);
412 builder.SaveLayer(nullptr);
413
414 paint.setBlendMode(DlBlendMode::kPlus);
415 paint.setColor(DlColor::kRed());
416
417 builder.DrawRect(SkRect::MakeXYWH(100, 100, 400, 400), paint);
418 paint.setColor(DlColor::kWhite());
419
420 auto rect = Rect::MakeXYWH(100, 100, 400, 400).Expand(-100, -100);
421 builder.DrawImageRect(
422 DlImageImpeller::Make(texture),
423 SkRect::MakeSize(
424 SkSize::Make(texture->GetSize().width, texture->GetSize().height)),
425 SkRect::MakeLTRB(rect.GetLeft(), rect.GetTop(), rect.GetRight(),
426 rect.GetBottom()),
427 DlImageSampling::kMipmapLinear, &paint);
428 builder.Restore();
429 ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
430 }
431
432 // Bug: https://github.com/flutter/flutter/issues/142549
433 TEST_P(AiksTest, BlendModePlusAlphaColorFilterWideGamut) {
434 EXPECT_EQ(GetContext()->GetCapabilities()->GetDefaultColorFormat(),
435 PixelFormat::kB10G10R10A10XR);
436 auto texture = CreateTextureForFixture("airplane.jpg",
437 /*enable_mipmapping=*/true);
438
439 DisplayListBuilder builder;
440 builder.Scale(GetContentScale().x, GetContentScale().y);
441
442 DlPaint paint;
443 paint.setColor(DlColor::RGBA(0.1, 0.2, 0.1, 1.0));
444 builder.DrawPaint(paint);
445
446 DlPaint save_paint;
447 save_paint.setColorFilter(
448 DlColorFilter::MakeBlend(DlColor::RGBA(1, 0, 0, 1), DlBlendMode::kPlus));
449 builder.SaveLayer(nullptr, &save_paint);
450
451 paint.setColor(DlColor::kRed());
452 builder.DrawRect(SkRect::MakeXYWH(100, 100, 400, 400), paint);
453
454 paint.setColor(DlColor::kWhite());
455
456 auto rect = Rect::MakeXYWH(100, 100, 400, 400).Expand(-100, -100);
457 builder.DrawImageRect(
458 DlImageImpeller::Make(texture),
459 SkRect::MakeSize(
460 SkSize::Make(texture->GetSize().width, texture->GetSize().height)),
461 SkRect::MakeLTRB(rect.GetLeft(), rect.GetTop(), rect.GetRight(),
462 rect.GetBottom()),
463 DlImageSampling::kMipmapLinear, &paint);
464 builder.Restore();
465
466 ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
467 }
468
469 TEST_P(AiksTest, ForegroundBlendSubpassCollapseOptimization) {
470 DisplayListBuilder builder;
471
472 DlPaint save_paint;
473 save_paint.setColorFilter(
474 DlColorFilter::MakeBlend(DlColor::kRed(), DlBlendMode::kColorDodge));
475 builder.SaveLayer(nullptr, &save_paint);
476
477 builder.Translate(500, 300);
478 builder.Rotate(120);
479
480 DlPaint paint;
481 paint.setColor(DlColor::kBlue());
482 builder.DrawRect(SkRect::MakeXYWH(100, 100, 200, 200), paint);
483
484 ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
485 }
486
487 TEST_P(AiksTest, ClearBlend) {
488 DisplayListBuilder builder;
489
490 DlPaint blue;
491 blue.setColor(DlColor::kBlue());
492 builder.DrawRect(SkRect::MakeXYWH(0, 0, 600.0, 600.0), blue);
493
494 DlPaint clear;
495 clear.setBlendMode(DlBlendMode::kClear);
496
497 builder.DrawCircle(SkPoint{300.0, 300.0}, 200.0, clear);
498
499 ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
500 }
501
502 static sk_sp<DisplayList> BlendModeTest(Vector2 content_scale,
503 BlendMode blend_mode,
504 const sk_sp<DlImageImpeller>& src_image,
505 const sk_sp<DlImageImpeller>& dst_image,
506 Scalar src_alpha) {
507 if (AiksTest::ImGuiBegin("Controls", nullptr,
508 ImGuiWindowFlags_AlwaysAutoResize)) {
509 ImGui::SliderFloat("Source alpha", &src_alpha, 0, 1);
510 ImGui::End();
511 }
512
513 Color destination_color = Color::CornflowerBlue().WithAlpha(0.75);
514 auto source_colors = std::vector<Color>({Color::White().WithAlpha(0.75),
515 Color::LimeGreen().WithAlpha(0.75),
516 Color::Black().WithAlpha(0.75)});
517
518 DisplayListBuilder builder;
519 {
520 DlPaint paint;
521 paint.setColor(DlColor::kBlack());
522 builder.DrawPaint(paint);
523 }
524 // TODO(bdero): Why does this cause the left image to double scale on high DPI
525 // displays.
526 // builder.Scale(content_scale);
527
528 //----------------------------------------------------------------------------
529 /// 1. Save layer blending (top squares).
530 ///
531
532 builder.Save();
533 for (const auto& color : source_colors) {
534 builder.Save();
535 {
536 builder.ClipRect(SkRect::MakeXYWH(25, 25, 100, 100));
537 // Perform the blend in a SaveLayer so that the initial backdrop color is
538 // fully transparent black. SourceOver blend the result onto the parent
539 // pass.
540 builder.SaveLayer({});
541 {
542 DlPaint draw_paint;
543 draw_paint.setColor(
544 DlColor::RGBA(destination_color.red, destination_color.green,
545 destination_color.blue, destination_color.alpha));
546 builder.DrawPaint(draw_paint);
547
548 // Draw the source color in an offscreen pass and blend it to the parent
549 // pass.
550 DlPaint save_paint;
551 save_paint.setBlendMode(static_cast<DlBlendMode>(blend_mode));
552 builder.SaveLayer(nullptr, &save_paint);
553 { //
554 DlPaint paint;
555 paint.setColor(
556 DlColor::RGBA(color.red, color.green, color.blue, color.alpha));
557 builder.DrawRect(SkRect::MakeXYWH(25, 25, 100, 100), paint);
558 }
559 builder.Restore();
560 }
561 builder.Restore();
562 }
563 builder.Restore();
564 builder.Translate(100, 0);
565 }
566 builder.RestoreToCount(0);
567
568 //----------------------------------------------------------------------------
569 /// 2. CPU blend modes (bottom squares).
570 ///
571
572 builder.Save();
573 builder.Translate(0, 100);
574 // Perform the blend in a SaveLayer so that the initial backdrop color is
575 // fully transparent black. SourceOver blend the result onto the parent pass.
576 builder.SaveLayer({});
577 for (const auto& color : source_colors) {
578 // Simply write the CPU blended color to the pass.
579 DlPaint paint;
580 auto dest = destination_color.Blend(color, blend_mode);
581 paint.setColor(DlColor::RGBA(dest.red, dest.green, dest.blue, dest.alpha));
582 paint.setBlendMode(DlBlendMode::kSrcOver);
583 builder.DrawRect(SkRect::MakeXYWH(25, 25, 100, 100), paint);
584 builder.Translate(100, 0);
585 }
586 builder.Restore();
587 builder.Restore();
588
589 //----------------------------------------------------------------------------
590 /// 3. Image blending (bottom images).
591 ///
592 /// Compare these results with the images in the Flutter blend mode
593 /// documentation: https://api.flutter.dev/flutter/dart-ui/BlendMode.html
594 ///
595
596 builder.Translate(0, 250);
597
598 // Draw grid behind the images.
599 {
600 DlPaint paint;
601 paint.setColor(DlColor::RGBA(41 / 255.0, 41 / 255.0, 41 / 255.0, 1));
602 builder.DrawRect(SkRect::MakeLTRB(0, 0, 800, 400), paint);
603 }
604
605 DlPaint square_paint;
606 square_paint.setColor(DlColor::RGBA(15 / 255.0, 15 / 255.0, 15 / 255.0, 1));
607 for (int y = 0; y < 400 / 8; y++) {
608 for (int x = 0; x < 800 / 16; x++) {
609 builder.DrawRect(SkRect::MakeXYWH(x * 16 + (y % 2) * 8, y * 8, 8, 8),
610 square_paint);
611 }
612 }
613
614 // Uploaded image source (left image).
615 DlPaint paint;
616 paint.setBlendMode(DlBlendMode::kSrcOver);
617 builder.Save();
618 builder.SaveLayer(nullptr, &paint);
619 {
620 builder.DrawImage(dst_image, SkPoint{0, 0}, DlImageSampling::kMipmapLinear,
621 &paint);
622
623 paint.setColor(DlColor::kWhite().withAlpha(src_alpha * 255));
624 paint.setBlendMode(static_cast<DlBlendMode>(blend_mode));
625 builder.DrawImage(src_image, SkPoint{0, 0}, DlImageSampling::kMipmapLinear,
626 &paint);
627 }
628 builder.Restore();
629 builder.Restore();
630
631 // Rendered image source (right image).
632 builder.Save();
633
634 DlPaint save_paint;
635 builder.SaveLayer(nullptr, &save_paint);
636 {
637 builder.DrawImage(dst_image, SkPoint{400, 0},
638 DlImageSampling::kMipmapLinear, nullptr);
639
640 DlPaint save_paint;
641 save_paint.setColor(DlColor::kWhite().withAlpha(src_alpha * 255));
642 save_paint.setBlendMode(static_cast<DlBlendMode>(blend_mode));
643 builder.SaveLayer(nullptr, &save_paint);
644 {
645 builder.DrawImage(src_image, SkPoint{400, 0},
646 DlImageSampling::kMipmapLinear, nullptr);
647 }
648 builder.Restore();
649 }
650 builder.Restore();
651 builder.Restore();
652
653 return builder.Build();
654 }
655
656 #define BLEND_MODE_TEST(blend_mode) \
657 TEST_P(AiksTest, BlendMode##blend_mode) { \
658 auto src_image = \
659 DlImageImpeller::Make(CreateTextureForFixture("blend_mode_src.png")); \
660 auto dst_image = \
661 DlImageImpeller::Make(CreateTextureForFixture("blend_mode_dst.png")); \
662 auto callback = [&]() -> sk_sp<DisplayList> { \
663 return BlendModeTest(GetContentScale(), BlendMode::k##blend_mode, \
664 src_image, dst_image, /*src_alpha=*/1.0); \
665 }; \
666 OpenPlaygroundHere(callback); \
667 }
668 IMPELLER_FOR_EACH_BLEND_MODE(BLEND_MODE_TEST)
669
670 #define BLEND_MODE_SRC_ALPHA_TEST(blend_mode) \
671 TEST_P(AiksTest, BlendModeSrcAlpha##blend_mode) { \
672 auto src_image = \
673 DlImageImpeller::Make(CreateTextureForFixture("blend_mode_src.png")); \
674 auto dst_image = \
675 DlImageImpeller::Make(CreateTextureForFixture("blend_mode_dst.png")); \
676 auto callback = [&]() -> sk_sp<DisplayList> { \
677 return BlendModeTest(GetContentScale(), BlendMode::k##blend_mode, \
678 src_image, dst_image, /*src_alpha=*/0.5); \
679 }; \
680 OpenPlaygroundHere(callback); \
681 }
682 IMPELLER_FOR_EACH_BLEND_MODE(BLEND_MODE_SRC_ALPHA_TEST)
683
684 TEST_P(AiksTest, CanDrawPaintMultipleTimesInteractive) {
685 auto modes = GetBlendModeSelection();
686
687 auto callback = [&]() -> sk_sp<DisplayList> {
688 static Color background = Color::MediumTurquoise();
689 static Color foreground = Color::Color::OrangeRed().WithAlpha(0.5);
690 static int current_blend_index = 3;
691
692 if (AiksTest::ImGuiBegin("Controls", nullptr,
693 ImGuiWindowFlags_AlwaysAutoResize)) {
694 ImGui::ColorEdit4("Background", reinterpret_cast<float*>(&background));
695 ImGui::ColorEdit4("Foreground", reinterpret_cast<float*>(&foreground));
696 ImGui::ListBox("Blend mode", &current_blend_index,
697 modes.blend_mode_names.data(),
698 modes.blend_mode_names.size());
699 ImGui::End();
700 }
701
702 DisplayListBuilder builder;
703 builder.Scale(0.2, 0.2);
704 DlPaint paint;
705 paint.setColor(DlColor(background.ToARGB()));
706 builder.DrawPaint(paint);
707
708 paint.setColor(DlColor(foreground.ToARGB()));
709 paint.setBlendMode(static_cast<DlBlendMode>(current_blend_index));
710 builder.DrawPaint(paint);
711 return builder.Build();
712 };
713 ASSERT_TRUE(OpenPlaygroundHere(callback));
714 }
715
716 TEST_P(AiksTest, ForegroundPipelineBlendAppliesTransformCorrectly) {
717 auto texture = CreateTextureForFixture("airplane.jpg",
718 /*enable_mipmapping=*/true);
719
720 DisplayListBuilder builder;
721 builder.Rotate(30);
722
723 DlPaint image_paint;
724 image_paint.setColorFilter(DlColorFilter::MakeBlend(
725 DlColor::RGBA(255.0f / 255.0f, 165.0f / 255.0f, 0.0f / 255.0f, 1.0f),
726 DlBlendMode::kSrcIn));
727
728 builder.DrawImage(DlImageImpeller::Make(texture), SkPoint{200, 200},
729 DlImageSampling::kMipmapLinear, &image_paint);
730
731 ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
732 }
733
734 TEST_P(AiksTest, ForegroundAdvancedBlendAppliesTransformCorrectly) {
735 auto texture = CreateTextureForFixture("airplane.jpg",
736 /*enable_mipmapping=*/true);
737
738 DisplayListBuilder builder;
739 builder.Rotate(30);
740
741 DlPaint image_paint;
742 image_paint.setColorFilter(DlColorFilter::MakeBlend(
743 DlColor::RGBA(255.0f / 255.0f, 165.0f / 255.0f, 0.0f / 255.0f, 1.0f),
744 DlBlendMode::kColorDodge));
745
746 builder.DrawImage(DlImageImpeller::Make(texture), SkPoint{200, 200},
747 DlImageSampling::kMipmapLinear, &image_paint);
748
749 ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
750 }
751
752 TEST_P(AiksTest, FramebufferAdvancedBlendCoverage) {
753 auto texture = CreateTextureForFixture("airplane.jpg",
754 /*enable_mipmapping=*/true);
755
756 // Draw with an advanced blend that can use FramebufferBlendContents and
757 // verify that the scale transform is correctly applied to the image.
758 DisplayListBuilder builder;
759
760 DlPaint paint;
761 paint.setColor(
762 DlColor::RGBA(169.0f / 255.0f, 169.0f / 255.0f, 169.0f / 255.0f, 1.0f));
763 builder.DrawPaint(paint);
764 builder.Scale(0.4, 0.4);
765
766 DlPaint image_paint;
767 image_paint.setBlendMode(DlBlendMode::kMultiply);
768
769 builder.DrawImage(DlImageImpeller::Make(texture), SkPoint{20, 20},
770 DlImageSampling::kMipmapLinear, &image_paint);
771
772 ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
773 }
774
775 TEST_P(AiksTest, ColorWheel) {
776 // Compare with https://fiddle.skia.org/c/@BlendModes
777
778 BlendModeSelection blend_modes = GetBlendModeSelection();
779
780 auto draw_color_wheel = [](DisplayListBuilder& builder) -> void {
781 /// color_wheel_sampler: r=0 -> fuchsia, r=2pi/3 -> yellow, r=4pi/3 ->
782 /// cyan domain: r >= 0 (because modulo used is non euclidean)
783 auto color_wheel_sampler = [](Radians r) {
784 Scalar x = r.radians / k2Pi + 1;
785
786 // https://www.desmos.com/calculator/6nhjelyoaj
787 auto color_cycle = [](Scalar x) {
788 Scalar cycle = std::fmod(x, 6.0f);
789 return std::max(0.0f, std::min(1.0f, 2 - std::abs(2 - cycle)));
790 };
791 return Color(color_cycle(6 * x + 1), //
792 color_cycle(6 * x - 1), //
793 color_cycle(6 * x - 3), //
794 1);
795 };
796
797 DlPaint paint;
798 paint.setBlendMode(DlBlendMode::kSrcOver);
799
800 // Draw a fancy color wheel for the backdrop.
801 // https://www.desmos.com/calculator/xw7kafthwd
802 const int max_dist = 900;
803 for (int i = 0; i <= 900; i++) {
804 Radians r(kPhi / k2Pi * i);
805 Scalar distance = r.radians / std::powf(4.12, 0.0026 * r.radians);
806 Scalar normalized_distance = static_cast<Scalar>(i) / max_dist;
807
808 auto color = color_wheel_sampler(r).WithAlpha(1.0f - normalized_distance);
809 paint.setColor(
810 DlColor::RGBA(color.red, color.green, color.blue, color.alpha));
811 SkPoint position = SkPoint::Make(distance * std::sin(r.radians),
812 -distance * std::cos(r.radians));
813
814 builder.DrawCircle(position, 9 + normalized_distance * 3, paint);
815 }
816 };
817
818 auto callback = [&]() -> sk_sp<DisplayList> {
819 // UI state.
820 static bool cache_the_wheel = true;
821 static int current_blend_index = 3;
822 static float dst_alpha = 1;
823 static float src_alpha = 1;
824 static DlColor color0 = DlColor::kRed();
825 static DlColor color1 = DlColor::kGreen();
826 static DlColor color2 = DlColor::kBlue();
827
828 if (AiksTest::ImGuiBegin("Controls", nullptr,
829 ImGuiWindowFlags_AlwaysAutoResize)) {
830 ImGui::Checkbox("Cache the wheel", &cache_the_wheel);
831 ImGui::ListBox("Blending mode", &current_blend_index,
832 blend_modes.blend_mode_names.data(),
833 blend_modes.blend_mode_names.size());
834 ImGui::SliderFloat("Source alpha", &src_alpha, 0, 1);
835 ImGui::ColorEdit4("Color A", reinterpret_cast<float*>(&color0));
836 ImGui::ColorEdit4("Color B", reinterpret_cast<float*>(&color1));
837 ImGui::ColorEdit4("Color C", reinterpret_cast<float*>(&color2));
838 ImGui::SliderFloat("Destination alpha", &dst_alpha, 0, 1);
839 ImGui::End();
840 }
841
842 DisplayListBuilder builder;
843
844 DlPaint paint;
845 paint.setColor(DlColor::kWhite().withAlpha(dst_alpha * 255));
846 paint.setBlendMode(DlBlendMode::kSrc);
847 builder.SaveLayer(nullptr, &paint);
848 {
849 DlPaint paint;
850 paint.setColor(DlColor::kWhite());
851 builder.DrawPaint(paint);
852
853 builder.SaveLayer(nullptr, nullptr);
854 builder.Scale(GetContentScale().x, GetContentScale().y);
855 builder.Translate(500, 400);
856 builder.Scale(3, 3);
857 draw_color_wheel(builder);
858 builder.Restore();
859 }
860 builder.Restore();
861
862 builder.Scale(GetContentScale().x, GetContentScale().y);
863 builder.Translate(500, 400);
864 builder.Scale(3, 3);
865
866 // Draw 3 circles to a subpass and blend it in.
867 DlPaint save_paint;
868 save_paint.setColor(DlColor::kWhite().withAlpha(src_alpha * 255));
869 save_paint.setBlendMode(static_cast<DlBlendMode>(
870 blend_modes.blend_mode_values[current_blend_index]));
871 builder.SaveLayer(nullptr, &save_paint);
872 {
873 DlPaint paint;
874 paint.setBlendMode(DlBlendMode::kPlus);
875 const Scalar x = std::sin(k2Pi / 3);
876 const Scalar y = -std::cos(k2Pi / 3);
877 paint.setColor(color0);
878 builder.DrawCircle(SkPoint::Make(-x * 45, y * 45), 65, paint);
879 paint.setColor(color1);
880 builder.DrawCircle(SkPoint::Make(0, -45), 65, paint);
881 paint.setColor(color2);
882 builder.DrawCircle(SkPoint::Make(x * 45, y * 45), 65, paint);
883 }
884 builder.Restore();
885
886 return builder.Build();
887 };
888
889 ASSERT_TRUE(OpenPlaygroundHere(callback));
890 }
891
892 TEST_P(AiksTest, DestructiveBlendColorFilterFloodsClip) {
893 DisplayListBuilder builder;
894
895 DlPaint paint;
896 paint.setColor(DlColor::kBlue());
897 builder.DrawPaint(paint);
898
899 DlPaint save_paint;
900 save_paint.setColorFilter(
901 DlColorFilter::MakeBlend(DlColor::kRed(), DlBlendMode::kSrc));
902 builder.SaveLayer(nullptr, &save_paint);
903 builder.Restore();
904
905 // Should be solid red as the destructive color filter floods the clip.
906 ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
907 }
908
909 TEST_P(AiksTest, AdvancedBlendColorFilterWithDestinationOpacity) {
910 DisplayListBuilder builder;
911
912 builder.DrawPaint(DlPaint(DlColor::kWhite()));
913
914 DlPaint save_paint;
915 save_paint.setOpacity(0.3);
916 save_paint.setColorFilter(DlColorFilter::MakeBlend(DlColor::kTransparent(),
917 DlBlendMode::kSaturation));
918 builder.SaveLayer(nullptr, &save_paint);
919 builder.DrawRect(SkRect::MakeXYWH(100, 100, 300, 300),
920 DlPaint(DlColor::kMaroon()));
921 builder.DrawRect(SkRect::MakeXYWH(200, 200, 300, 300),
922 DlPaint(DlColor::kBlue()));
923 builder.Restore();
924
925 // Should be solid red as the destructive color filter floods the clip.
926 ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
927 }
928
929 } // namespace testing
930 } // namespace impeller
impeller::AiksPlayground
Definition: aiks_playground.h:16
impeller::testing::BlendModeTest
static sk_sp< DisplayList > BlendModeTest(Vector2 content_scale, BlendMode blend_mode, const sk_sp< DlImageImpeller > &src_image, const sk_sp< DlImageImpeller > &dst_image, Scalar src_alpha)
Definition: aiks_dl_blend_unittests.cc:502
impeller::Scalar
float Scalar
Definition: scalar.h:18
impeller::PixelFormat::kB10G10R10A10XR
@ kB10G10R10A10XR
aiks_unittests.h
playground_test.h
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:58
impeller::Color
Definition: color.h:123
impeller::PlaygroundBackend::kMetal
@ kMetal
aiks_context.h
BLEND_MODE_TEST
#define BLEND_MODE_TEST(blend_mode)
Definition: aiks_dl_blend_unittests.cc:656
dl_dispatcher.h
impeller::Color::alpha
Scalar alpha
Definition: color.h:142
playground.h
impeller::Color::CornflowerBlue
static constexpr Color CornflowerBlue()
Definition: color.h:341
impeller::Color::ToARGB
constexpr uint32_t ToARGB() const
Convert to ARGB 32 bit color.
Definition: color.h:258
impeller::Radians::radians
Scalar radians
Definition: scalar.h:44
x
int32_t x
Definition: dl_golden_unittests.cc:443
impeller::Color::green
Scalar green
Definition: color.h:132
impeller::DlImageImpeller::Make
static sk_sp< DlImageImpeller > Make(std::shared_ptr< Texture > texture, OwningContext owning_context=OwningContext::kIO)
Definition: dl_image_impeller.cc:23
impeller::kPhi
constexpr float kPhi
Definition: constants.h:53
impeller::saturated::distance
SI distance
Definition: saturated_math.h:57
impeller::k2Pi
constexpr float k2Pi
Definition: constants.h:29
impeller::Color::OrangeRed
static constexpr Color OrangeRed()
Definition: color.h:693
impeller::testing::GetBlendModeSelection
static BlendModeSelection GetBlendModeSelection()
Definition: aiks_dl_blend_unittests.cc:45
impeller::Color::WithAlpha
constexpr Color WithAlpha(Scalar new_alpha) const
Definition: color.h:277
impeller::Entity::kLastAdvancedBlendMode
static constexpr BlendMode kLastAdvancedBlendMode
Definition: entity.h:23
impeller::Color::red
Scalar red
Definition: color.h:127
impeller::Color::White
static constexpr Color White()
Definition: color.h:263
impeller::Radians
Definition: scalar.h:43
flutter
Definition: dl_golden_blur_unittests.cc:16
impeller::testing::BlendModeSelection
Definition: aiks_dl_blend_unittests.cc:40
BLEND_MODE_SRC_ALPHA_TEST
#define BLEND_MODE_SRC_ALPHA_TEST(blend_mode)
Definition: aiks_dl_blend_unittests.cc:670
IMPELLER_FOR_EACH_BLEND_MODE
#define IMPELLER_FOR_EACH_BLEND_MODE(V)
Definition: color.h:19
impeller::testing::TEST_P
TEST_P(AiksTest, DrawAtlasNoColor)
Definition: aiks_dl_atlas_unittests.cc:57
scalar.h
impeller::Color::MediumTurquoise
static constexpr Color MediumTurquoise()
Definition: color.h:645
BLEND_MODE_TUPLE
#define BLEND_MODE_TUPLE(blend_mode)
Definition: aiks_dl_blend_unittests.cc:38
impeller::TPoint< Scalar >
impeller::Color::Black
static constexpr Color Black()
Definition: color.h:265
impeller::testing::BlendModeSelection::blend_mode_values
std::vector< BlendMode > blend_mode_values
Definition: aiks_dl_blend_unittests.cc:42
impeller::AiksPlayground::ImGuiBegin
static bool ImGuiBegin(const char *name, bool *p_open, ImGuiWindowFlags flags)
Definition: aiks_playground.cc:31
impeller::Color::LimeGreen
static constexpr Color LimeGreen()
Definition: color.h:601
impeller::testing::BlendModeSelection::blend_mode_names
std::vector< const char * > blend_mode_names
Definition: aiks_dl_blend_unittests.cc:41
impeller::Color::blue
Scalar blue
Definition: color.h:137
impeller
Definition: allocation.cc:12
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:622
dl_image_impeller.h
impeller::Color::Blend
Color Blend(Color source, BlendMode blend_mode) const
Blends an unpremultiplied destination color into a given unpremultiplied source color to form a new u...
Definition: color.cc:157