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