| 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
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
39 struct BlendModeSelection {
40 std::vector<const char*> blend_mode_names;
41 std::vector<BlendMode> blend_mode_values;
42 };
43
44 static BlendModeSelection GetBlendModeSelection() {
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 = {
49 IMPELLER_FOR_EACH_BLEND_MODE(BLEND_MODE_TUPLE)};
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(
113 Color::MediumTurquoise().red, Color::MediumTurquoise().green,
114 Color::MediumTurquoise().blue, Color::MediumTurquoise().alpha));
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 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(std::nullopt);
413
414 paint.setBlendMode(DlBlendMode::kPlus);
415 paint.setColor(DlColor::kRed());
416
417 builder.DrawRect(DlRect::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 DlRect::MakeWH(texture->GetSize().width, texture->GetSize().height),
424 DlRect::MakeLTRB(rect.GetLeft(), rect.GetTop(), //
425 rect.GetRight(), rect.GetBottom()),
426 DlImageSampling::kMipmapLinear, &paint);
427 builder.Restore();
428 ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
429 }
430
431 // Bug: https://github.com/flutter/flutter/issues/142549
432 TEST_P(AiksTest, BlendModePlusAlphaColorFilterWideGamut) {
433 EXPECT_EQ(GetContext()->GetCapabilities()->GetDefaultColorFormat(),
434 PixelFormat::kB10G10R10A10XR);
435 auto texture = CreateTextureForFixture("airplane.jpg",
436 /*enable_mipmapping=*/true);
437
438 DisplayListBuilder builder;
439 builder.Scale(GetContentScale().x, GetContentScale().y);
440
441 DlPaint paint;
442 paint.setColor(DlColor::RGBA(0.1, 0.2, 0.1, 1.0));
443 builder.DrawPaint(paint);
444
445 DlPaint save_paint;
446 save_paint.setColorFilter(
447 DlColorFilter::MakeBlend(DlColor::RGBA(1, 0, 0, 1), DlBlendMode::kPlus));
448 builder.SaveLayer(std::nullopt, &save_paint);
449
450 paint.setColor(DlColor::kRed());
451 builder.DrawRect(DlRect::MakeXYWH(100, 100, 400, 400), paint);
452
453 paint.setColor(DlColor::kWhite());
454
455 auto rect = Rect::MakeXYWH(100, 100, 400, 400).Expand(-100, -100);
456 builder.DrawImageRect(
457 DlImageImpeller::Make(texture),
458 DlRect::MakeWH(texture->GetSize().width, texture->GetSize().height),
459 DlRect::MakeLTRB(rect.GetLeft(), rect.GetTop(), //
460 rect.GetRight(), rect.GetBottom()),
461 DlImageSampling::kMipmapLinear, &paint);
462 builder.Restore();
463
464 ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
465 }
466
467 TEST_P(AiksTest, ForegroundBlendSubpassCollapseOptimization) {
468 DisplayListBuilder builder;
469
470 DlPaint save_paint;
471 save_paint.setColorFilter(
472 DlColorFilter::MakeBlend(DlColor::kRed(), DlBlendMode::kColorDodge));
473 builder.SaveLayer(std::nullopt, &save_paint);
474
475 builder.Translate(500, 300);
476 builder.Rotate(120);
477
478 DlPaint paint;
479 paint.setColor(DlColor::kBlue());
480 builder.DrawRect(DlRect::MakeXYWH(100, 100, 200, 200), paint);
481
482 ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
483 }
484
485 TEST_P(AiksTest, ClearBlend) {
486 DisplayListBuilder builder;
487
488 DlPaint blue;
489 blue.setColor(DlColor::kBlue());
490 builder.DrawRect(DlRect::MakeXYWH(0, 0, 600.0, 600.0), blue);
491
492 DlPaint clear;
493 clear.setBlendMode(DlBlendMode::kClear);
494
495 builder.DrawCircle(DlPoint(300.0, 300.0), 200.0, clear);
496
497 ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
498 }
499
500 static sk_sp<DisplayList> BlendModeTest(Vector2 content_scale,
501 BlendMode blend_mode,
502 const sk_sp<DlImageImpeller>& src_image,
503 const sk_sp<DlImageImpeller>& dst_image,
504 Scalar src_alpha) {
505 if (AiksTest::ImGuiBegin("Controls", nullptr,
506 ImGuiWindowFlags_AlwaysAutoResize)) {
507 ImGui::SliderFloat("Source alpha", &src_alpha, 0, 1);
508 ImGui::End();
509 }
510
511 Color destination_color = Color::CornflowerBlue().WithAlpha(0.75);
512 auto source_colors = std::vector<Color>({Color::White().WithAlpha(0.75),
513 Color::LimeGreen().WithAlpha(0.75),
514 Color::Black().WithAlpha(0.75)});
515
516 DisplayListBuilder builder;
517 {
518 DlPaint paint;
519 paint.setColor(DlColor::kBlack());
520 builder.DrawPaint(paint);
521 }
522 // TODO(bdero): Why does this cause the left image to double scale on high DPI
523 // displays.
524 // builder.Scale(content_scale);
525
526 //----------------------------------------------------------------------------
527 /// 1. Save layer blending (top squares).
528 ///
529
530 builder.Save();
531 for (const auto& color : source_colors) {
532 builder.Save();
533 {
534 builder.ClipRect(DlRect::MakeXYWH(25, 25, 100, 100));
535 // Perform the blend in a SaveLayer so that the initial backdrop color is
536 // fully transparent black. SourceOver blend the result onto the parent
537 // pass.
538 builder.SaveLayer(std::nullopt);
539 {
540 DlPaint draw_paint;
541 draw_paint.setColor(
542 DlColor::RGBA(destination_color.red, destination_color.green,
543 destination_color.blue, destination_color.alpha));
544 builder.DrawPaint(draw_paint);
545
546 // Draw the source color in an offscreen pass and blend it to the parent
547 // pass.
548 DlPaint save_paint;
549 save_paint.setBlendMode(static_cast<DlBlendMode>(blend_mode));
550 builder.SaveLayer(std::nullopt, &save_paint);
551 { //
552 DlPaint paint;
553 paint.setColor(
554 DlColor::RGBA(color.red, color.green, color.blue, color.alpha));
555 builder.DrawRect(DlRect::MakeXYWH(25, 25, 100, 100), paint);
556 }
557 builder.Restore();
558 }
559 builder.Restore();
560 }
561 builder.Restore();
562 builder.Translate(100, 0);
563 }
564 builder.RestoreToCount(0);
565
566 //----------------------------------------------------------------------------
567 /// 2. CPU blend modes (bottom squares).
568 ///
569
570 builder.Save();
571 builder.Translate(0, 100);
572 // Perform the blend in a SaveLayer so that the initial backdrop color is
573 // fully transparent black. SourceOver blend the result onto the parent pass.
574 builder.SaveLayer(std::nullopt);
575 for (const auto& color : source_colors) {
576 // Simply write the CPU blended color to the pass.
577 DlPaint paint;
578 auto dest = destination_color.Blend(color, blend_mode);
579 paint.setColor(DlColor::RGBA(dest.red, dest.green, dest.blue, dest.alpha));
580 paint.setBlendMode(DlBlendMode::kSrcOver);
581 builder.DrawRect(DlRect::MakeXYWH(25, 25, 100, 100), paint);
582 builder.Translate(100, 0);
583 }
584 builder.Restore();
585 builder.Restore();
586
587 //----------------------------------------------------------------------------
588 /// 3. Image blending (bottom images).
589 ///
590 /// Compare these results with the images in the Flutter blend mode
591 /// documentation: https://api.flutter.dev/flutter/dart-ui/BlendMode.html
592 ///
593
594 builder.Translate(0, 250);
595
596 // Draw grid behind the images.
597 {
598 DlPaint paint;
599 paint.setColor(DlColor::RGBA(41 / 255.0, 41 / 255.0, 41 / 255.0, 1));
600 builder.DrawRect(DlRect::MakeLTRB(0, 0, 800, 400), paint);
601 }
602
603 DlPaint square_paint;
604 square_paint.setColor(DlColor::RGBA(15 / 255.0, 15 / 255.0, 15 / 255.0, 1));
605 for (int y = 0; y < 400 / 8; y++) {
606 for (int x = 0; x < 800 / 16; x++) {
607 builder.DrawRect(DlRect::MakeXYWH(x * 16 + (y % 2) * 8, y * 8, 8, 8),
608 square_paint);
609 }
610 }
611
612 // Uploaded image source (left image).
613 DlPaint paint;
614 paint.setBlendMode(DlBlendMode::kSrcOver);
615 builder.Save();
616 builder.SaveLayer(std::nullopt, &paint);
617 {
618 builder.DrawImage(dst_image, DlPoint(0, 0), DlImageSampling::kMipmapLinear,
619 &paint);
620
621 paint.setColor(DlColor::kWhite().withAlpha(src_alpha * 255));
622 paint.setBlendMode(static_cast<DlBlendMode>(blend_mode));
623 builder.DrawImage(src_image, DlPoint(0, 0), DlImageSampling::kMipmapLinear,
624 &paint);
625 }
626 builder.Restore();
627 builder.Restore();
628
629 // Rendered image source (right image).
630 builder.Save();
631
632 DlPaint save_paint;
633 builder.SaveLayer(std::nullopt, &save_paint);
634 {
635 builder.DrawImage(dst_image, DlPoint(400, 0),
636 DlImageSampling::kMipmapLinear, nullptr);
637
638 DlPaint save_paint;
639 save_paint.setColor(DlColor::kWhite().withAlpha(src_alpha * 255));
640 save_paint.setBlendMode(static_cast<DlBlendMode>(blend_mode));
641 builder.SaveLayer(std::nullopt, &save_paint);
642 {
643 builder.DrawImage(src_image, DlPoint(400, 0),
644 DlImageSampling::kMipmapLinear, nullptr);
645 }
646 builder.Restore();
647 }
648 builder.Restore();
649 builder.Restore();
650
651 return builder.Build();
652 }
653
654 #define BLEND_MODE_TEST(blend_mode) \
655 TEST_P(AiksTest, BlendMode##blend_mode) { \
656 auto src_image = \
657 DlImageImpeller::Make(CreateTextureForFixture("blend_mode_src.png")); \
658 auto dst_image = \
659 DlImageImpeller::Make(CreateTextureForFixture("blend_mode_dst.png")); \
660 auto callback = [&]() -> sk_sp<DisplayList> { \
661 return BlendModeTest(GetContentScale(), BlendMode::k##blend_mode, \
662 src_image, dst_image, /*src_alpha=*/1.0); \
663 }; \
664 OpenPlaygroundHere(callback); \
665 }
666 IMPELLER_FOR_EACH_BLEND_MODE(BLEND_MODE_TEST)
667
668 #define BLEND_MODE_SRC_ALPHA_TEST(blend_mode) \
669 TEST_P(AiksTest, BlendModeSrcAlpha##blend_mode) { \
670 auto src_image = \
671 DlImageImpeller::Make(CreateTextureForFixture("blend_mode_src.png")); \
672 auto dst_image = \
673 DlImageImpeller::Make(CreateTextureForFixture("blend_mode_dst.png")); \
674 auto callback = [&]() -> sk_sp<DisplayList> { \
675 return BlendModeTest(GetContentScale(), BlendMode::k##blend_mode, \
676 src_image, dst_image, /*src_alpha=*/0.5); \
677 }; \
678 OpenPlaygroundHere(callback); \
679 }
680 IMPELLER_FOR_EACH_BLEND_MODE(BLEND_MODE_SRC_ALPHA_TEST)
681
682 TEST_P(AiksTest, CanDrawPaintMultipleTimesInteractive) {
683 auto modes = GetBlendModeSelection();
684
685 auto callback = [&]() -> sk_sp<DisplayList> {
686 static Color background = Color::MediumTurquoise();
687 static Color foreground = Color::Color::OrangeRed().WithAlpha(0.5);
688 static int current_blend_index = 3;
689
690 if (AiksTest::ImGuiBegin("Controls", nullptr,
691 ImGuiWindowFlags_AlwaysAutoResize)) {
692 ImGui::ColorEdit4("Background", reinterpret_cast<float*>(&background));
693 ImGui::ColorEdit4("Foreground", reinterpret_cast<float*>(&foreground));
694 ImGui::ListBox("Blend mode", &current_blend_index,
695 modes.blend_mode_names.data(),
696 modes.blend_mode_names.size());
697 ImGui::End();
698 }
699
700 DisplayListBuilder builder;
701 builder.Scale(0.2, 0.2);
702 DlPaint paint;
703 paint.setColor(DlColor(background.ToARGB()));
704 builder.DrawPaint(paint);
705
706 paint.setColor(DlColor(foreground.ToARGB()));
707 paint.setBlendMode(static_cast<DlBlendMode>(current_blend_index));
708 builder.DrawPaint(paint);
709 return builder.Build();
710 };
711 ASSERT_TRUE(OpenPlaygroundHere(callback));
712 }
713
714 TEST_P(AiksTest, ForegroundPipelineBlendAppliesTransformCorrectly) {
715 auto texture = CreateTextureForFixture("airplane.jpg",
716 /*enable_mipmapping=*/true);
717
718 DisplayListBuilder builder;
719 builder.Rotate(30);
720
721 DlPaint image_paint;
722 image_paint.setColorFilter(DlColorFilter::MakeBlend(
723 DlColor::RGBA(255.0f / 255.0f, 165.0f / 255.0f, 0.0f / 255.0f, 1.0f),
724 DlBlendMode::kSrcIn));
725
726 builder.DrawImage(DlImageImpeller::Make(texture), DlPoint(200, 200),
727 DlImageSampling::kMipmapLinear, &image_paint);
728
729 ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
730 }
731
732 TEST_P(AiksTest, ForegroundAdvancedBlendAppliesTransformCorrectly) {
733 auto texture = CreateTextureForFixture("airplane.jpg",
734 /*enable_mipmapping=*/true);
735
736 DisplayListBuilder builder;
737 builder.Rotate(30);
738
739 DlPaint image_paint;
740 image_paint.setColorFilter(DlColorFilter::MakeBlend(
741 DlColor::RGBA(255.0f / 255.0f, 165.0f / 255.0f, 0.0f / 255.0f, 1.0f),
742 DlBlendMode::kColorDodge));
743
744 builder.DrawImage(DlImageImpeller::Make(texture), DlPoint(200, 200),
745 DlImageSampling::kMipmapLinear, &image_paint);
746
747 ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
748 }
749
750 TEST_P(AiksTest, FramebufferAdvancedBlendCoverage) {
751 auto texture = CreateTextureForFixture("airplane.jpg",
752 /*enable_mipmapping=*/true);
753
754 // Draw with an advanced blend that can use FramebufferBlendContents and
755 // verify that the scale transform is correctly applied to the image.
756 DisplayListBuilder builder;
757
758 DlPaint paint;
759 paint.setColor(
760 DlColor::RGBA(169.0f / 255.0f, 169.0f / 255.0f, 169.0f / 255.0f, 1.0f));
761 builder.DrawPaint(paint);
762 builder.Scale(0.4, 0.4);
763
764 DlPaint image_paint;
765 image_paint.setBlendMode(DlBlendMode::kMultiply);
766
767 builder.DrawImage(DlImageImpeller::Make(texture), DlPoint(20, 20),
768 DlImageSampling::kMipmapLinear, &image_paint);
769
770 ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
771 }
772
773 TEST_P(AiksTest, ColorWheel) {
774 // Compare with https://fiddle.skia.org/c/@BlendModes
775
776 BlendModeSelection blend_modes = GetBlendModeSelection();
777
778 auto draw_color_wheel = [](DisplayListBuilder& builder) -> void {
779 /// color_wheel_sampler: r=0 -> fuchsia, r=2pi/3 -> yellow, r=4pi/3 ->
780 /// cyan domain: r >= 0 (because modulo used is non euclidean)
781 auto color_wheel_sampler = [](Radians r) {
782 Scalar x = r.radians / k2Pi + 1;
783
784 // https://www.desmos.com/calculator/6nhjelyoaj
785 auto color_cycle = [](Scalar x) {
786 Scalar cycle = std::fmod(x, 6.0f);
787 return std::max(0.0f, std::min(1.0f, 2 - std::abs(2 - cycle)));
788 };
789 return Color(color_cycle(6 * x + 1), //
790 color_cycle(6 * x - 1), //
791 color_cycle(6 * x - 3), //
792 1);
793 };
794
795 DlPaint paint;
796 paint.setBlendMode(DlBlendMode::kSrcOver);
797
798 // Draw a fancy color wheel for the backdrop.
799 // https://www.desmos.com/calculator/xw7kafthwd
800 const int max_dist = 900;
801 for (int i = 0; i <= 900; i++) {
802 Radians r(kPhi / k2Pi * i);
803 Scalar distance = r.radians / std::powf(4.12, 0.0026 * r.radians);
804 Scalar normalized_distance = static_cast<Scalar>(i) / max_dist;
805
806 auto color = color_wheel_sampler(r).WithAlpha(1.0f - normalized_distance);
807 paint.setColor(
808 DlColor::RGBA(color.red, color.green, color.blue, color.alpha));
809 DlPoint position = DlPoint(distance * std::sin(r.radians),
810 -distance * std::cos(r.radians));
811
812 builder.DrawCircle(position, 9 + normalized_distance * 3, paint);
813 }
814 };
815
816 auto callback = [&]() -> sk_sp<DisplayList> {
817 // UI state.
818 static bool cache_the_wheel = true;
819 static int current_blend_index = 3;
820 static float dst_alpha = 1;
821 static float src_alpha = 1;
822 static DlColor color0 = DlColor::kRed();
823 static DlColor color1 = DlColor::kGreen();
824 static DlColor color2 = DlColor::kBlue();
825
826 if (AiksTest::ImGuiBegin("Controls", nullptr,
827 ImGuiWindowFlags_AlwaysAutoResize)) {
828 ImGui::Checkbox("Cache the wheel", &cache_the_wheel);
829 ImGui::ListBox("Blending mode", &current_blend_index,
830 blend_modes.blend_mode_names.data(),
831 blend_modes.blend_mode_names.size());
832 ImGui::SliderFloat("Source alpha", &src_alpha, 0, 1);
833 ImGui::ColorEdit4("Color A", reinterpret_cast<float*>(&color0));
834 ImGui::ColorEdit4("Color B", reinterpret_cast<float*>(&color1));
835 ImGui::ColorEdit4("Color C", reinterpret_cast<float*>(&color2));
836 ImGui::SliderFloat("Destination alpha", &dst_alpha, 0, 1);
837 ImGui::End();
838 }
839
840 DisplayListBuilder builder;
841
842 DlPaint paint;
843 paint.setColor(DlColor::kWhite().withAlpha(dst_alpha * 255));
844 paint.setBlendMode(DlBlendMode::kSrc);
845 builder.SaveLayer(std::nullopt, &paint);
846 {
847 DlPaint paint;
848 paint.setColor(DlColor::kWhite());
849 builder.DrawPaint(paint);
850
851 builder.SaveLayer(std::nullopt, nullptr);
852 builder.Scale(GetContentScale().x, GetContentScale().y);
853 builder.Translate(500, 400);
854 builder.Scale(3, 3);
855 draw_color_wheel(builder);
856 builder.Restore();
857 }
858 builder.Restore();
859
860 builder.Scale(GetContentScale().x, GetContentScale().y);
861 builder.Translate(500, 400);
862 builder.Scale(3, 3);
863
864 // Draw 3 circles to a subpass and blend it in.
865 DlPaint save_paint;
866 save_paint.setColor(DlColor::kWhite().withAlpha(src_alpha * 255));
867 save_paint.setBlendMode(static_cast<DlBlendMode>(
868 blend_modes.blend_mode_values[current_blend_index]));
869 builder.SaveLayer(std::nullopt, &save_paint);
870 {
871 DlPaint paint;
872 paint.setBlendMode(DlBlendMode::kPlus);
873 const Scalar x = std::sin(k2Pi / 3);
874 const Scalar y = -std::cos(k2Pi / 3);
875 paint.setColor(color0);
876 builder.DrawCircle(DlPoint(-x * 45, y * 45), 65, paint);
877 paint.setColor(color1);
878 builder.DrawCircle(DlPoint(0, -45), 65, paint);
879 paint.setColor(color2);
880 builder.DrawCircle(DlPoint(x * 45, y * 45), 65, paint);
881 }
882 builder.Restore();
883
884 return builder.Build();
885 };
886
887 ASSERT_TRUE(OpenPlaygroundHere(callback));
888 }
889
890 TEST_P(AiksTest, DestructiveBlendColorFilterFloodsClip) {
891 DisplayListBuilder builder;
892
893 DlPaint paint;
894 paint.setColor(DlColor::kBlue());
895 builder.DrawPaint(paint);
896
897 DlPaint save_paint;
898 save_paint.setColorFilter(
899 DlColorFilter::MakeBlend(DlColor::kRed(), DlBlendMode::kSrc));
900 builder.SaveLayer(std::nullopt, &save_paint);
901 builder.Restore();
902
903 // Should be solid red as the destructive color filter floods the clip.
904 ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
905 }
906
907 TEST_P(AiksTest, AdvancedBlendColorFilterWithDestinationOpacity) {
908 DisplayListBuilder builder;
909
910 builder.DrawPaint(DlPaint(DlColor::kWhite()));
911
912 DlPaint save_paint;
913 save_paint.setOpacity(0.3);
914 save_paint.setColorFilter(DlColorFilter::MakeBlend(DlColor::kTransparent(),
915 DlBlendMode::kSaturation));
916 builder.SaveLayer(std::nullopt, &save_paint);
917 builder.DrawRect(DlRect::MakeXYWH(100, 100, 300, 300),
918 DlPaint(DlColor::kMaroon()));
919 builder.DrawRect(DlRect::MakeXYWH(200, 200, 300, 300),
920 DlPaint(DlColor::kBlue()));
921 builder.Restore();
922
923 // Should be solid red as the destructive color filter floods the clip.
924 ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
925 }
926
927 TEST_P(AiksTest, EmulatedAdvancedBlendRestore) {
928 DisplayListBuilder builder;
929
930 builder.DrawPaint(DlPaint(DlColor::kWhite()));
931 builder.Save();
932 builder.ClipRect(DlRect::MakeLTRB(100, 100, 400, 300));
933
934 // Draw should apply the clip, even though it is an advanced blend.
935 builder.DrawRect(DlRect::MakeLTRB(0, 0, 400, 300),
936 DlPaint()
937 .setColor(DlColor::kRed())
938 .setBlendMode(DlBlendMode::kDifference));
939 // This color should not show if clip is still functional.
940 builder.DrawRect(DlRect::MakeLTRB(0, 0, 100, 100),
941 DlPaint().setColor(DlColor::kBlue()));
942 builder.Restore();
943
944 ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
945 }
946
947 } // namespace testing
948 } // namespace impeller
aiks_context.h
BLEND_MODE_TUPLE
#define BLEND_MODE_TUPLE(blend_mode)
Definition: aiks_dl_blend_unittests.cc:37
BLEND_MODE_SRC_ALPHA_TEST
#define BLEND_MODE_SRC_ALPHA_TEST(blend_mode)
Definition: aiks_dl_blend_unittests.cc:668
BLEND_MODE_TEST
#define BLEND_MODE_TEST(blend_mode)
Definition: aiks_dl_blend_unittests.cc:654
aiks_unittests.h
impeller::AiksPlayground
Definition: aiks_playground.h:16
impeller::AiksPlayground::ImGuiBegin
static bool ImGuiBegin(const char *name, bool *p_open, ImGuiWindowFlags flags)
Definition: aiks_playground.cc:30
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::Entity::kLastAdvancedBlendMode
static constexpr BlendMode kLastAdvancedBlendMode
Definition: entity.h:29
IMPELLER_FOR_EACH_BLEND_MODE
#define IMPELLER_FOR_EACH_BLEND_MODE(V)
Definition: color.h:19
dl_dispatcher.h
x
int32_t x
Definition: dl_golden_unittests.cc:458
dl_image_impeller.h
flutter
Definition: dl_golden_blur_unittests.cc:16
impeller::saturated::distance
SI distance
Definition: saturated_math.h:57
impeller::testing::TEST_P
TEST_P(AiksTest, DrawAtlasNoColor)
Definition: aiks_dl_atlas_unittests.cc:59
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:500
impeller::testing::GetBlendModeSelection
static BlendModeSelection GetBlendModeSelection()
Definition: aiks_dl_blend_unittests.cc:44
impeller
Definition: allocation.cc:12
impeller::k2Pi
constexpr float k2Pi
Definition: constants.h:29
impeller::DlRect
flutter::DlRect DlRect
Definition: dl_dispatcher.h:25
impeller::Scalar
float Scalar
Definition: scalar.h:19
impeller::PixelFormat::kB10G10R10A10XR
@ kB10G10R10A10XR
impeller::DlPoint
flutter::DlPoint DlPoint
Definition: dl_dispatcher.h:24
impeller::BlendMode
BlendMode
Definition: color.h:58
impeller::PlaygroundBackend::kMetal
@ kMetal
impeller::kPhi
constexpr float kPhi
Definition: constants.h:54
playground_test.h
playground.h
scalar.h
impeller::Color
Definition: color.h:124
impeller::Color::blue
Scalar blue
Definition: color.h:138
impeller::Color::ToARGB
uint32_t ToARGB() const
Convert to ARGB 32 bit color.
Definition: color.h:259
impeller::Color::LimeGreen
static constexpr Color LimeGreen()
Definition: color.h:602
impeller::Color::alpha
Scalar alpha
Definition: color.h:143
impeller::Color::Black
static constexpr Color Black()
Definition: color.h:266
impeller::Color::CornflowerBlue
static constexpr Color CornflowerBlue()
Definition: color.h:342
impeller::Color::MediumTurquoise
static constexpr Color MediumTurquoise()
Definition: color.h:646
impeller::Color::White
static constexpr Color White()
Definition: color.h:264
impeller::Color::WithAlpha
constexpr Color WithAlpha(Scalar new_alpha) const
Definition: color.h:278
impeller::Color::OrangeRed
static constexpr Color OrangeRed()
Definition: color.h:694
impeller::Color::red
Scalar red
Definition: color.h:128
impeller::Color::green
Scalar green
Definition: color.h:133
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
impeller::Radians
Definition: scalar.h:44
impeller::Radians::radians
Scalar radians
Definition: scalar.h:45
impeller::TPoint< Scalar >
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:618
impeller::TRect< Scalar >::MakeXYWH
constexpr static TRect MakeXYWH(Type x, Type y, Type width, Type height)
Definition: rect.h:136
impeller::testing::BlendModeSelection
Definition: aiks_dl_blend_unittests.cc:39
impeller::testing::BlendModeSelection::blend_mode_values
std::vector< BlendMode > blend_mode_values
Definition: aiks_dl_blend_unittests.cc:41
impeller::testing::BlendModeSelection::blend_mode_names
std::vector< const char * > blend_mode_names
Definition: aiks_dl_blend_unittests.cc:40