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