Flutter Impeller
aiks_dl_basic_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 "display_list/display_list.h"
6 #include "display_list/dl_sampling_options.h"
7 #include "display_list/dl_tile_mode.h"
8 #include "display_list/effects/dl_color_filter.h"
9 #include "display_list/effects/dl_color_source.h"
10 #include "display_list/effects/dl_image_filter.h"
11 #include "display_list/effects/dl_mask_filter.h"
13 
14 #include "flutter/display_list/dl_blend_mode.h"
15 #include "flutter/display_list/dl_builder.h"
16 #include "flutter/display_list/dl_color.h"
17 #include "flutter/display_list/dl_paint.h"
20 #include "flutter/testing/display_list_testing.h"
21 #include "flutter/testing/testing.h"
23 #include "include/core/SkMatrix.h"
24 
25 namespace impeller {
26 namespace testing {
27 
28 namespace {
29 SkM44 FromImpellerMatrix(const Matrix& matrix) {
30  return SkM44::ColMajor(matrix.m);
31 }
32 } // namespace
33 
34 using namespace flutter;
35 
36 TEST_P(AiksTest, CanRenderColoredRect) {
37  DisplayListBuilder builder;
38  DlPaint paint;
39  paint.setColor(DlColor::kBlue());
40  SkPath path = SkPath();
41  path.addRect(SkRect::MakeXYWH(100.0, 100.0, 100.0, 100.0));
42  builder.DrawPath(path, paint);
43  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
44 }
45 
46 TEST_P(AiksTest, CanRenderImage) {
47  DisplayListBuilder builder;
48  DlPaint paint;
49  paint.setColor(DlColor::kRed());
50  auto image = DlImageImpeller::Make(CreateTextureForFixture("kalimba.jpg"));
51  builder.DrawImage(image, SkPoint::Make(100.0, 100.0),
52  DlImageSampling::kNearestNeighbor, &paint);
53  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
54 }
55 
56 TEST_P(AiksTest, CanRenderInvertedImageWithColorFilter) {
57  DisplayListBuilder builder;
58  DlPaint paint;
59  paint.setColor(DlColor::kRed());
60  paint.setColorFilter(
61  DlColorFilter::MakeBlend(DlColor::kYellow(), DlBlendMode::kSrcOver));
62  paint.setInvertColors(true);
63  auto image = DlImageImpeller::Make(CreateTextureForFixture("kalimba.jpg"));
64 
65  builder.DrawImage(image, SkPoint::Make(100.0, 100.0),
66  DlImageSampling::kNearestNeighbor, &paint);
67  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
68 }
69 
70 TEST_P(AiksTest, CanRenderColorFilterWithInvertColors) {
71  DisplayListBuilder builder;
72  DlPaint paint;
73  paint.setColor(DlColor::kRed());
74  paint.setColorFilter(
75  DlColorFilter::MakeBlend(DlColor::kYellow(), DlBlendMode::kSrcOver));
76  paint.setInvertColors(true);
77 
78  builder.DrawRect(SkRect::MakeLTRB(0, 0, 100, 100), paint);
79  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
80 }
81 
82 TEST_P(AiksTest, CanRenderColorFilterWithInvertColorsDrawPaint) {
83  DisplayListBuilder builder;
84  DlPaint paint;
85  paint.setColor(DlColor::kRed());
86  paint.setColorFilter(
87  DlColorFilter::MakeBlend(DlColor::kYellow(), DlBlendMode::kSrcOver));
88  paint.setInvertColors(true);
89 
90  builder.DrawPaint(paint);
91  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
92 }
93 
94 namespace {
95 bool GenerateMipmap(const std::shared_ptr<Context>& context,
96  std::shared_ptr<Texture> texture,
97  std::string_view label) {
98  auto buffer = context->CreateCommandBuffer();
99  if (!buffer) {
100  return false;
101  }
102  auto pass = buffer->CreateBlitPass();
103  if (!pass) {
104  return false;
105  }
106  pass->GenerateMipmap(std::move(texture), label);
107 
108  pass->EncodeCommands();
109  return context->GetCommandQueue()->Submit({buffer}).ok();
110 }
111 
112 void CanRenderTiledTexture(AiksTest* aiks_test,
113  DlTileMode tile_mode,
114  Matrix local_matrix = {}) {
115  auto context = aiks_test->GetContext();
116  ASSERT_TRUE(context);
117  auto texture = aiks_test->CreateTextureForFixture("table_mountain_nx.png",
118  /*enable_mipmapping=*/true);
119  GenerateMipmap(context, texture, "table_mountain_nx");
120  auto image = DlImageImpeller::Make(texture);
121  auto color_source = DlColorSource::MakeImage(
122  image, tile_mode, tile_mode, DlImageSampling::kNearestNeighbor,
123  &local_matrix);
124 
125  DisplayListBuilder builder;
126  DlPaint paint;
127  paint.setColor(DlColor::kWhite());
128  paint.setColorSource(color_source);
129 
130  builder.Scale(aiks_test->GetContentScale().x, aiks_test->GetContentScale().y);
131  builder.Translate(100.0f, 100.0f);
132  builder.DrawRect(SkRect::MakeXYWH(0, 0, 600, 600), paint);
133 
134  // Should not change the image.
135  constexpr auto stroke_width = 64;
136  paint.setDrawStyle(DlDrawStyle::kStroke);
137  paint.setStrokeWidth(stroke_width);
138  if (tile_mode == DlTileMode::kDecal) {
139  builder.DrawRect(SkRect::MakeXYWH(stroke_width, stroke_width, 600, 600),
140  paint);
141  } else {
142  builder.DrawRect(SkRect::MakeXYWH(0, 0, 600, 600), paint);
143  }
144 
145  {
146  // Should not change the image.
147  SkPath path;
148  path.addCircle(150, 150, 150);
149  path.addRoundRect(SkRect::MakeLTRB(300, 300, 600, 600), 10, 10);
150 
151  // Make sure path cannot be simplified...
152  EXPECT_FALSE(path.isRect(nullptr));
153  EXPECT_FALSE(path.isOval(nullptr));
154  EXPECT_FALSE(path.isRRect(nullptr));
155 
156  // Make sure path will not trigger the optimal convex code
157  EXPECT_FALSE(path.isConvex());
158 
159  paint.setDrawStyle(DlDrawStyle::kFill);
160  builder.DrawPath(path, paint);
161  }
162 
163  {
164  // Should not change the image. Tests the Convex short-cut code.
165  SkPath circle;
166  circle.addCircle(150, 450, 150);
167 
168  // Unfortunately, the circle path can be simplified...
169  EXPECT_TRUE(circle.isOval(nullptr));
170  // At least it's convex, though...
171  EXPECT_TRUE(circle.isConvex());
172 
173  // Let's make a copy that doesn't remember that it's just a circle...
174  SkPath path;
175  // This moveTo confuses addPath into appending rather than replacing,
176  // which prevents it from noticing that it's just a circle...
177  path.moveTo(10, 10);
178  path.addPath(circle);
179 
180  // Make sure path cannot be simplified...
181  EXPECT_FALSE(path.isRect(nullptr));
182  EXPECT_FALSE(path.isOval(nullptr));
183  EXPECT_FALSE(path.isRRect(nullptr));
184 
185  // But check that we will trigger the optimal convex code
186  EXPECT_TRUE(path.isConvex());
187 
188  paint.setDrawStyle(DlDrawStyle::kFill);
189  builder.DrawPath(path, paint);
190  }
191 
192  ASSERT_TRUE(aiks_test->OpenPlaygroundHere(builder.Build()));
193 }
194 } // namespace
195 
196 TEST_P(AiksTest, CanRenderTiledTextureClamp) {
197  CanRenderTiledTexture(this, DlTileMode::kClamp);
198 }
199 
200 TEST_P(AiksTest, CanRenderTiledTextureRepeat) {
201  CanRenderTiledTexture(this, DlTileMode::kRepeat);
202 }
203 
204 TEST_P(AiksTest, CanRenderTiledTextureMirror) {
205  CanRenderTiledTexture(this, DlTileMode::kMirror);
206 }
207 
208 TEST_P(AiksTest, CanRenderTiledTextureDecal) {
209  CanRenderTiledTexture(this, DlTileMode::kDecal);
210 }
211 
212 TEST_P(AiksTest, CanRenderTiledTextureClampWithTranslate) {
213  CanRenderTiledTexture(this, DlTileMode::kClamp,
214  Matrix::MakeTranslation({172.f, 172.f, 0.f}));
215 }
216 
217 TEST_P(AiksTest, CanRenderImageRect) {
218  DisplayListBuilder builder;
219  auto image = DlImageImpeller::Make(CreateTextureForFixture("kalimba.jpg"));
220 
221  SkSize image_half_size = SkSize::Make(image->dimensions().fWidth * 0.5f,
222  image->dimensions().fHeight * 0.5f);
223 
224  // Render the bottom right quarter of the source image in a stretched rect.
225  auto source_rect = SkRect::MakeSize(image_half_size);
226  source_rect =
227  source_rect.makeOffset(image_half_size.fWidth, image_half_size.fHeight);
228 
229  builder.DrawImageRect(image, source_rect,
230  SkRect::MakeXYWH(100, 100, 600, 600),
231  DlImageSampling::kNearestNeighbor);
232  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
233 }
234 
235 TEST_P(AiksTest, DrawImageRectSrcOutsideBounds) {
236  DisplayListBuilder builder;
237  auto image = DlImageImpeller::Make(CreateTextureForFixture("kalimba.jpg"));
238 
239  // Use a source rect that is partially outside the bounds of the image.
240  auto source_rect = SkRect::MakeXYWH(
241  image->dimensions().fWidth * 0.25f, image->dimensions().fHeight * 0.4f,
242  image->dimensions().fWidth, image->dimensions().fHeight);
243 
244  auto dest_rect = SkRect::MakeXYWH(100, 100, 600, 600);
245 
246  DlPaint paint;
247  paint.setColor(DlColor::kMidGrey());
248  builder.DrawRect(dest_rect, paint);
249 
250  builder.DrawImageRect(image, source_rect, dest_rect,
251  DlImageSampling::kNearestNeighbor);
252  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
253 }
254 
255 TEST_P(AiksTest, CanRenderSimpleClips) {
256  DisplayListBuilder builder;
257  builder.Scale(GetContentScale().x, GetContentScale().y);
258  DlPaint paint;
259 
260  paint.setColor(DlColor::kWhite());
261  builder.DrawPaint(paint);
262 
263  auto draw = [&builder](const DlPaint& paint, Scalar x, Scalar y) {
264  builder.Save();
265  builder.Translate(x, y);
266  {
267  builder.Save();
268  builder.ClipRect(SkRect::MakeLTRB(50, 50, 150, 150));
269  builder.DrawPaint(paint);
270  builder.Restore();
271  }
272  {
273  builder.Save();
274  builder.ClipOval(SkRect::MakeLTRB(200, 50, 300, 150));
275  builder.DrawPaint(paint);
276  builder.Restore();
277  }
278  {
279  builder.Save();
280  builder.ClipRRect(
281  SkRRect::MakeRectXY(SkRect::MakeLTRB(50, 200, 150, 300), 20, 20));
282  builder.DrawPaint(paint);
283  builder.Restore();
284  }
285  {
286  builder.Save();
287  builder.ClipRRect(
288  SkRRect::MakeRectXY(SkRect::MakeLTRB(200, 230, 300, 270), 20, 20));
289  builder.DrawPaint(paint);
290  builder.Restore();
291  }
292  {
293  builder.Save();
294  builder.ClipRRect(
295  SkRRect::MakeRectXY(SkRect::MakeLTRB(230, 200, 270, 300), 20, 20));
296  builder.DrawPaint(paint);
297  builder.Restore();
298  }
299  builder.Restore();
300  };
301 
302  paint.setColor(DlColor::kBlue());
303  draw(paint, 0, 0);
304 
305  DlColor gradient_colors[7] = {
306  DlColor::RGBA(0x1f / 255.0, 0.0, 0x5c / 255.0, 1.0),
307  DlColor::RGBA(0x5b / 255.0, 0.0, 0x60 / 255.0, 1.0),
308  DlColor::RGBA(0x87 / 255.0, 0x01 / 255.0, 0x60 / 255.0, 1.0),
309  DlColor::RGBA(0xac / 255.0, 0x25 / 255.0, 0x53 / 255.0, 1.0),
310  DlColor::RGBA(0xe1 / 255.0, 0x6b / 255.0, 0x5c / 255.0, 1.0),
311  DlColor::RGBA(0xf3 / 255.0, 0x90 / 255.0, 0x60 / 255.0, 1.0),
312  DlColor::RGBA(0xff / 255.0, 0xb5 / 255.0, 0x6b / 250.0, 1.0),
313  };
314  Scalar stops[7] = {
315  0.0,
316  (1.0 / 6.0) * 1,
317  (1.0 / 6.0) * 2,
318  (1.0 / 6.0) * 3,
319  (1.0 / 6.0) * 4,
320  (1.0 / 6.0) * 5,
321  1.0,
322  };
323  auto texture = CreateTextureForFixture("airplane.jpg",
324  /*enable_mipmapping=*/true);
325  auto image = DlImageImpeller::Make(texture);
326 
327  paint.setColorSource(DlColorSource::MakeRadial(
328  {500, 600}, 75, 7, gradient_colors, stops, DlTileMode::kMirror));
329  draw(paint, 0, 300);
330 
331  paint.setColorSource(
332  DlColorSource::MakeImage(image, DlTileMode::kRepeat, DlTileMode::kRepeat,
333  DlImageSampling::kNearestNeighbor));
334  draw(paint, 300, 0);
335 
336  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
337 }
338 
339 TEST_P(AiksTest, CanSaveLayerStandalone) {
340  DisplayListBuilder builder;
341 
342  DlPaint red;
343  red.setColor(DlColor::kRed());
344 
345  DlPaint alpha;
346  alpha.setColor(DlColor::kRed().modulateOpacity(0.5));
347 
348  builder.SaveLayer(nullptr, &alpha);
349 
350  builder.DrawCircle(SkPoint{125, 125}, 125, red);
351 
352  builder.Restore();
353 
354  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
355 }
356 
357 TEST_P(AiksTest, CanRenderDifferentShapesWithSameColorSource) {
358  DisplayListBuilder builder;
359  DlPaint paint;
360 
361  DlColor colors[2] = {
362  DlColor::RGBA(0.9568, 0.2627, 0.2118, 1.0),
363  DlColor::RGBA(0.1294, 0.5882, 0.9529, 1.0),
364  };
365  DlScalar stops[2] = {
366  0.0,
367  1.0,
368  };
369 
370  paint.setColorSource(DlColorSource::MakeLinear(
371  /*start_point=*/{0, 0}, //
372  /*end_point=*/{100, 100}, //
373  /*stop_count=*/2, //
374  /*colors=*/colors, //
375  /*stops=*/stops, //
376  /*tile_mode=*/DlTileMode::kRepeat //
377  ));
378 
379  builder.Save();
380  builder.Translate(100, 100);
381  builder.DrawRect(SkRect::MakeXYWH(0, 0, 200, 200), paint);
382  builder.Restore();
383 
384  builder.Save();
385  builder.Translate(100, 400);
386  builder.DrawCircle(SkPoint{100, 100}, 100, paint);
387  builder.Restore();
388  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
389 }
390 
391 TEST_P(AiksTest, CanRenderRoundedRectWithNonUniformRadii) {
392  DisplayListBuilder builder;
393  DlPaint paint;
394  paint.setColor(DlColor::kRed());
395 
396  SkRRect rrect;
397  SkVector radii[4] = {
398  SkVector{50, 25},
399  SkVector{25, 50},
400  SkVector{50, 25},
401  SkVector{25, 50},
402  };
403  rrect.setRectRadii(SkRect::MakeXYWH(100, 100, 500, 500), radii);
404 
405  builder.DrawRRect(rrect, paint);
406 
407  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
408 }
409 
410 TEST_P(AiksTest, CanDrawPaint) {
411  auto medium_turquoise =
412  DlColor::RGBA(72.0f / 255.0f, 209.0f / 255.0f, 204.0f / 255.0f, 1.0f);
413 
414  DisplayListBuilder builder;
415  builder.Scale(0.2, 0.2);
416  builder.DrawPaint(DlPaint().setColor(medium_turquoise));
417  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
418 }
419 
420 TEST_P(AiksTest, CanDrawPaintMultipleTimes) {
421  auto medium_turquoise =
422  DlColor::RGBA(72.0f / 255.0f, 209.0f / 255.0f, 204.0f / 255.0f, 1.0f);
423  auto orange_red =
424  DlColor::RGBA(255.0f / 255.0f, 69.0f / 255.0f, 0.0f / 255.0f, 1.0f);
425 
426  DisplayListBuilder builder;
427  builder.Scale(0.2, 0.2);
428  builder.DrawPaint(DlPaint().setColor(medium_turquoise));
429  builder.DrawPaint(DlPaint().setColor(orange_red.modulateOpacity(0.5f)));
430  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
431 }
432 
433 TEST_P(AiksTest, FilledCirclesRenderCorrectly) {
434  DisplayListBuilder builder;
435  builder.Scale(GetContentScale().x, GetContentScale().y);
436  DlPaint paint;
437  const int color_count = 3;
438  DlColor colors[color_count] = {
439  DlColor::kBlue(),
440  DlColor::kGreen(),
441  DlColor::RGBA(220.0f / 255.0f, 20.0f / 255.0f, 60.0f / 255.0f, 1.0f),
442  };
443 
444  paint.setColor(DlColor::kWhite());
445  builder.DrawPaint(paint);
446 
447  int c_index = 0;
448  int radius = 600;
449  while (radius > 0) {
450  paint.setColor(colors[(c_index++) % color_count]);
451  builder.DrawCircle(SkPoint{10, 10}, radius, paint);
452  if (radius > 30) {
453  radius -= 10;
454  } else {
455  radius -= 2;
456  }
457  }
458 
459  DlColor gradient_colors[7] = {
460  DlColor::RGBA(0x1f / 255.0, 0.0, 0x5c / 255.0, 1.0),
461  DlColor::RGBA(0x5b / 255.0, 0.0, 0x60 / 255.0, 1.0),
462  DlColor::RGBA(0x87 / 255.0, 0x01 / 255.0, 0x60 / 255.0, 1.0),
463  DlColor::RGBA(0xac / 255.0, 0x25 / 255.0, 0x53 / 255.0, 1.0),
464  DlColor::RGBA(0xe1 / 255.0, 0x6b / 255.0, 0x5c / 255.0, 1.0),
465  DlColor::RGBA(0xf3 / 255.0, 0x90 / 255.0, 0x60 / 255.0, 1.0),
466  DlColor::RGBA(0xff / 255.0, 0xb5 / 255.0, 0x6b / 250.0, 1.0),
467  };
468  DlScalar stops[7] = {
469  0.0,
470  (1.0 / 6.0) * 1,
471  (1.0 / 6.0) * 2,
472  (1.0 / 6.0) * 3,
473  (1.0 / 6.0) * 4,
474  (1.0 / 6.0) * 5,
475  1.0,
476  };
477  auto texture = CreateTextureForFixture("airplane.jpg",
478  /*enable_mipmapping=*/true);
479  auto image = DlImageImpeller::Make(texture);
480 
481  paint.setColorSource(DlColorSource::MakeRadial(
482  {500, 600}, 75, 7, gradient_colors, stops, DlTileMode::kMirror));
483  builder.DrawCircle(SkPoint{500, 600}, 100, paint);
484 
485  DlMatrix local_matrix = DlMatrix::MakeTranslation({700, 200});
486  paint.setColorSource(DlColorSource::MakeImage(
487  image, DlTileMode::kRepeat, DlTileMode::kRepeat,
488  DlImageSampling::kNearestNeighbor, &local_matrix));
489  builder.DrawCircle(SkPoint{800, 300}, 100, paint);
490 
491  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
492 }
493 
494 TEST_P(AiksTest, StrokedCirclesRenderCorrectly) {
495  DisplayListBuilder builder;
496  builder.Scale(GetContentScale().x, GetContentScale().y);
497  DlPaint paint;
498  const int color_count = 3;
499  DlColor colors[color_count] = {
500  DlColor::kBlue(),
501  DlColor::kGreen(),
502  DlColor::RGBA(220.0f / 255.0f, 20.0f / 255.0f, 60.0f / 255.0f, 1.0f),
503  };
504 
505  paint.setColor(DlColor::kWhite());
506  builder.DrawPaint(paint);
507 
508  int c_index = 0;
509 
510  auto draw = [&paint, &colors, &c_index](DlCanvas& canvas, SkPoint center,
511  Scalar r, Scalar dr, int n) {
512  for (int i = 0; i < n; i++) {
513  paint.setColor(colors[(c_index++) % color_count]);
514  canvas.DrawCircle(center, r, paint);
515  r += dr;
516  }
517  };
518 
519  paint.setDrawStyle(DlDrawStyle::kStroke);
520  paint.setStrokeWidth(1);
521  draw(builder, {10, 10}, 2, 2, 14); // r = [2, 28], covers [1,29]
522  paint.setStrokeWidth(5);
523  draw(builder, {10, 10}, 35, 10, 56); // r = [35, 585], covers [30,590]
524 
525  DlColor gradient_colors[7] = {
526  DlColor::RGBA(0x1f / 255.0, 0.0, 0x5c / 255.0, 1.0),
527  DlColor::RGBA(0x5b / 255.0, 0.0, 0x60 / 255.0, 1.0),
528  DlColor::RGBA(0x87 / 255.0, 0x01 / 255.0, 0x60 / 255.0, 1.0),
529  DlColor::RGBA(0xac / 255.0, 0x25 / 255.0, 0x53 / 255.0, 1.0),
530  DlColor::RGBA(0xe1 / 255.0, 0x6b / 255.0, 0x5c / 255.0, 1.0),
531  DlColor::RGBA(0xf3 / 255.0, 0x90 / 255.0, 0x60 / 255.0, 1.0),
532  DlColor::RGBA(0xff / 255.0, 0xb5 / 255.0, 0x6b / 250.0, 1.0),
533  };
534  DlScalar stops[7] = {
535  0.0,
536  (1.0 / 6.0) * 1,
537  (1.0 / 6.0) * 2,
538  (1.0 / 6.0) * 3,
539  (1.0 / 6.0) * 4,
540  (1.0 / 6.0) * 5,
541  1.0,
542  };
543  auto texture = CreateTextureForFixture("airplane.jpg",
544  /*enable_mipmapping=*/true);
545  auto image = DlImageImpeller::Make(texture);
546 
547  paint.setColorSource(DlColorSource::MakeRadial(
548  {500, 600}, 75, 7, gradient_colors, stops, DlTileMode::kMirror));
549  draw(builder, {500, 600}, 5, 10, 10);
550 
551  DlMatrix local_matrix = DlMatrix::MakeTranslation({700, 200});
552  paint.setColorSource(DlColorSource::MakeImage(
553  image, DlTileMode::kRepeat, DlTileMode::kRepeat,
554  DlImageSampling::kNearestNeighbor, &local_matrix));
555  draw(builder, {800, 300}, 5, 10, 10);
556 
557  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
558 }
559 
560 TEST_P(AiksTest, FilledEllipsesRenderCorrectly) {
561  DisplayListBuilder builder;
562  builder.Scale(GetContentScale().x, GetContentScale().y);
563  DlPaint paint;
564  const int color_count = 3;
565  DlColor colors[color_count] = {
566  DlColor::kBlue(),
567  DlColor::kGreen(),
568  DlColor::RGBA(220.0f / 255.0f, 20.0f / 255.0f, 60.0f / 255.0f, 1.0f),
569  };
570 
571  paint.setColor(DlColor::kWhite());
572  builder.DrawPaint(paint);
573 
574  int c_index = 0;
575  int long_radius = 600;
576  int short_radius = 600;
577  while (long_radius > 0 && short_radius > 0) {
578  paint.setColor(colors[(c_index++) % color_count]);
579  builder.DrawOval(SkRect::MakeXYWH(10 - long_radius, 10 - short_radius,
580  long_radius * 2, short_radius * 2),
581  paint);
582  builder.DrawOval(SkRect::MakeXYWH(1000 - short_radius, 750 - long_radius,
583  short_radius * 2, long_radius * 2),
584  paint);
585  if (short_radius > 30) {
586  short_radius -= 10;
587  long_radius -= 5;
588  } else {
589  short_radius -= 2;
590  long_radius -= 1;
591  }
592  }
593 
594  DlColor gradient_colors[7] = {
595  DlColor::RGBA(0x1f / 255.0, 0.0, 0x5c / 255.0, 1.0),
596  DlColor::RGBA(0x5b / 255.0, 0.0, 0x60 / 255.0, 1.0),
597  DlColor::RGBA(0x87 / 255.0, 0x01 / 255.0, 0x60 / 255.0, 1.0),
598  DlColor::RGBA(0xac / 255.0, 0x25 / 255.0, 0x53 / 255.0, 1.0),
599  DlColor::RGBA(0xe1 / 255.0, 0x6b / 255.0, 0x5c / 255.0, 1.0),
600  DlColor::RGBA(0xf3 / 255.0, 0x90 / 255.0, 0x60 / 255.0, 1.0),
601  DlColor::RGBA(0xff / 255.0, 0xb5 / 255.0, 0x6b / 250.0, 1.0),
602  };
603  DlScalar stops[7] = {
604  0.0,
605  (1.0 / 6.0) * 1,
606  (1.0 / 6.0) * 2,
607  (1.0 / 6.0) * 3,
608  (1.0 / 6.0) * 4,
609  (1.0 / 6.0) * 5,
610  1.0,
611  };
612  auto texture = CreateTextureForFixture("airplane.jpg",
613  /*enable_mipmapping=*/true);
614  auto image = DlImageImpeller::Make(texture);
615 
616  paint.setColor(DlColor::kWhite().modulateOpacity(0.5));
617 
618  paint.setColorSource(DlColorSource::MakeRadial(
619  {300, 650}, 75, 7, gradient_colors, stops, DlTileMode::kMirror));
620  builder.DrawOval(SkRect::MakeXYWH(200, 625, 200, 50), paint);
621  builder.DrawOval(SkRect::MakeXYWH(275, 550, 50, 200), paint);
622 
623  DlMatrix local_matrix = DlMatrix::MakeTranslation({610, 15});
624  paint.setColorSource(DlColorSource::MakeImage(
625  image, DlTileMode::kRepeat, DlTileMode::kRepeat,
626  DlImageSampling::kNearestNeighbor, &local_matrix));
627  builder.DrawOval(SkRect::MakeXYWH(610, 90, 200, 50), paint);
628  builder.DrawOval(SkRect::MakeXYWH(685, 15, 50, 200), paint);
629 
630  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
631 }
632 
633 TEST_P(AiksTest, FilledRoundRectsRenderCorrectly) {
634  DisplayListBuilder builder;
635  builder.Scale(GetContentScale().x, GetContentScale().y);
636  DlPaint paint;
637  const int color_count = 3;
638  DlColor colors[color_count] = {
639  DlColor::kBlue(),
640  DlColor::kGreen(),
641  DlColor::RGBA(220.0f / 255.0f, 20.0f / 255.0f, 60.0f / 255.0f, 1.0f),
642  };
643 
644  paint.setColor(DlColor::kWhite());
645  builder.DrawPaint(paint);
646 
647  int c_index = 0;
648  for (int i = 0; i < 4; i++) {
649  for (int j = 0; j < 4; j++) {
650  paint.setColor(colors[(c_index++) % color_count]);
651  builder.DrawRRect(
652  SkRRect::MakeRectXY(
653  SkRect::MakeXYWH(i * 100 + 10, j * 100 + 20, 80, 80), //
654  i * 5 + 10, j * 5 + 10),
655  paint);
656  }
657  }
658  paint.setColor(colors[(c_index++) % color_count]);
659  builder.DrawRRect(
660  SkRRect::MakeRectXY(SkRect::MakeXYWH(10, 420, 380, 80), 40, 40), paint);
661  paint.setColor(colors[(c_index++) % color_count]);
662  builder.DrawRRect(
663  SkRRect::MakeRectXY(SkRect::MakeXYWH(410, 20, 80, 380), 40, 40), paint);
664 
665  DlColor gradient_colors[7] = {
666  DlColor::RGBA(0x1f / 255.0, 0.0, 0x5c / 255.0, 1.0),
667  DlColor::RGBA(0x5b / 255.0, 0.0, 0x60 / 255.0, 1.0),
668  DlColor::RGBA(0x87 / 255.0, 0x01 / 255.0, 0x60 / 255.0, 1.0),
669  DlColor::RGBA(0xac / 255.0, 0x25 / 255.0, 0x53 / 255.0, 1.0),
670  DlColor::RGBA(0xe1 / 255.0, 0x6b / 255.0, 0x5c / 255.0, 1.0),
671  DlColor::RGBA(0xf3 / 255.0, 0x90 / 255.0, 0x60 / 255.0, 1.0),
672  DlColor::RGBA(0xff / 255.0, 0xb5 / 255.0, 0x6b / 250.0, 1.0),
673  };
674  DlScalar stops[7] = {
675  0.0,
676  (1.0 / 6.0) * 1,
677  (1.0 / 6.0) * 2,
678  (1.0 / 6.0) * 3,
679  (1.0 / 6.0) * 4,
680  (1.0 / 6.0) * 5,
681  1.0,
682  };
683  auto texture = CreateTextureForFixture("airplane.jpg",
684  /*enable_mipmapping=*/true);
685  auto image = DlImageImpeller::Make(texture);
686 
687  paint.setColor(DlColor::kWhite().modulateOpacity(0.1));
688  paint.setColorSource(DlColorSource::MakeRadial(
689  {550, 550}, 75, 7, gradient_colors, stops, DlTileMode::kMirror));
690  for (int i = 1; i <= 10; i++) {
691  int j = 11 - i;
692  builder.DrawRRect(
693  SkRRect::MakeRectXY(SkRect::MakeLTRB(550 - i * 20, 550 - j * 20, //
694  550 + i * 20, 550 + j * 20),
695  i * 10, j * 10),
696  paint);
697  }
698 
699  paint.setColor(DlColor::kWhite().modulateOpacity(0.5));
700  paint.setColorSource(DlColorSource::MakeRadial(
701  {200, 650}, 75, 7, gradient_colors, stops, DlTileMode::kMirror));
702  paint.setColor(DlColor::kWhite().modulateOpacity(0.5));
703  builder.DrawRRect(
704  SkRRect::MakeRectXY(SkRect::MakeLTRB(100, 610, 300, 690), 40, 40), paint);
705  builder.DrawRRect(
706  SkRRect::MakeRectXY(SkRect::MakeLTRB(160, 550, 240, 750), 40, 40), paint);
707 
708  paint.setColor(DlColor::kWhite().modulateOpacity(0.1));
709  DlMatrix local_matrix = DlMatrix::MakeTranslation({520, 20});
710  paint.setColorSource(DlColorSource::MakeImage(
711  image, DlTileMode::kRepeat, DlTileMode::kRepeat,
712  DlImageSampling::kNearestNeighbor, &local_matrix));
713  for (int i = 1; i <= 10; i++) {
714  int j = 11 - i;
715  builder.DrawRRect(
716  SkRRect::MakeRectXY(SkRect::MakeLTRB(720 - i * 20, 220 - j * 20, //
717  720 + i * 20, 220 + j * 20),
718  i * 10, j * 10),
719  paint);
720  }
721 
722  paint.setColor(DlColor::kWhite().modulateOpacity(0.5));
723  local_matrix = DlMatrix::MakeTranslation({800, 300});
724  paint.setColorSource(DlColorSource::MakeImage(
725  image, DlTileMode::kRepeat, DlTileMode::kRepeat,
726  DlImageSampling::kNearestNeighbor, &local_matrix));
727  builder.DrawRRect(
728  SkRRect::MakeRectXY(SkRect::MakeLTRB(800, 410, 1000, 490), 40, 40),
729  paint);
730  builder.DrawRRect(
731  SkRRect::MakeRectXY(SkRect::MakeLTRB(860, 350, 940, 550), 40, 40), paint);
732 
733  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
734 }
735 
736 TEST_P(AiksTest, SolidColorCirclesOvalsRRectsMaskBlurCorrectly) {
737  DisplayListBuilder builder;
738  builder.Scale(GetContentScale().x, GetContentScale().y);
739  DlPaint paint;
740  paint.setMaskFilter(DlBlurMaskFilter::Make(DlBlurStyle::kNormal, 1.0f));
741 
742  builder.DrawPaint(DlPaint().setColor(DlColor::kWhite()));
743 
744  paint.setColor(
745  DlColor::RGBA(220.0f / 255.0f, 20.0f / 255.0f, 60.0f / 255.0f, 1.0f));
746  Scalar y = 100.0f;
747  for (int i = 0; i < 5; i++) {
748  Scalar x = (i + 1) * 100;
749  Scalar radius = x / 10.0f;
750  builder.DrawRect(SkRect::MakeXYWH(x + 25 - radius / 2, y + radius / 2, //
751  radius, 60.0f - radius),
752  paint);
753  }
754 
755  paint.setColor(DlColor::kBlue());
756  y += 100.0f;
757  for (int i = 0; i < 5; i++) {
758  Scalar x = (i + 1) * 100;
759  Scalar radius = x / 10.0f;
760  builder.DrawCircle(SkPoint{x + 25, y + 25}, radius, paint);
761  }
762 
763  paint.setColor(DlColor::kGreen());
764  y += 100.0f;
765  for (int i = 0; i < 5; i++) {
766  Scalar x = (i + 1) * 100;
767  Scalar radius = x / 10.0f;
768  builder.DrawOval(SkRect::MakeXYWH(x + 25 - radius / 2, y + radius / 2, //
769  radius, 60.0f - radius),
770  paint);
771  }
772 
773  paint.setColor(
774  DlColor::RGBA(128.0f / 255.0f, 0.0f / 255.0f, 128.0f / 255.0f, 1.0f));
775  y += 100.0f;
776  for (int i = 0; i < 5; i++) {
777  Scalar x = (i + 1) * 100;
778  Scalar radius = x / 20.0f;
779  builder.DrawRRect(SkRRect::MakeRectXY(SkRect::MakeXYWH(x, y, 60.0f, 60.0f),
780  radius, radius),
781  paint);
782  }
783 
784  paint.setColor(
785  DlColor::RGBA(255.0f / 255.0f, 165.0f / 255.0f, 0.0f / 255.0f, 1.0f));
786  y += 100.0f;
787  for (int i = 0; i < 5; i++) {
788  Scalar x = (i + 1) * 100;
789  Scalar radius = x / 20.0f;
790  builder.DrawRRect(
791  SkRRect::MakeRectXY(SkRect::MakeXYWH(x, y, 60.0f, 60.0f), radius, 5.0f),
792  paint);
793  }
794 
795  auto dl = builder.Build();
796  ASSERT_TRUE(OpenPlaygroundHere(dl));
797 }
798 
799 TEST_P(AiksTest, CanRenderClippedBackdropFilter) {
800  DisplayListBuilder builder;
801 
802  builder.Scale(GetContentScale().x, GetContentScale().y);
803 
804  // Draw something interesting in the background.
805  std::vector<DlColor> colors = {DlColor::RGBA(0.9568, 0.2627, 0.2118, 1.0),
806  DlColor::RGBA(0.1294, 0.5882, 0.9529, 1.0)};
807  std::vector<Scalar> stops = {
808  0.0,
809  1.0,
810  };
811  DlPaint paint;
812  paint.setColorSource(DlColorSource::MakeLinear(
813  /*start_point=*/{0, 0}, //
814  /*end_point=*/{100, 100}, //
815  /*stop_count=*/2, //
816  /*colors=*/colors.data(), //
817  /*stops=*/stops.data(), //
818  /*tile_mode=*/DlTileMode::kRepeat //
819  ));
820 
821  builder.DrawPaint(paint);
822 
823  SkRect clip_rect = SkRect::MakeLTRB(50, 50, 400, 300);
824  SkRRect clip_rrect = SkRRect::MakeRectXY(clip_rect, 100, 100);
825 
826  // Draw a clipped SaveLayer, where the clip coverage and SaveLayer size are
827  // the same.
828  builder.ClipRRect(clip_rrect, DlCanvas::ClipOp::kIntersect);
829 
830  DlPaint save_paint;
831  auto backdrop_filter = DlImageFilter::MakeColorFilter(
832  DlColorFilter::MakeBlend(DlColor::kRed(), DlBlendMode::kExclusion));
833  builder.SaveLayer(&clip_rect, &save_paint, backdrop_filter.get());
834 
835  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
836 }
837 
838 TEST_P(AiksTest, CanDrawPerspectiveTransformWithClips) {
839  // Avoiding `GetSecondsElapsed()` to reduce risk of golden flakiness.
840  int time = 0;
841  auto callback = [&]() -> sk_sp<DisplayList> {
842  DisplayListBuilder builder;
843 
844  builder.Save();
845  {
846  builder.Translate(300, 300);
847 
848  // 1. Draw/restore a clip before drawing the image, which will get drawn
849  // to the depth buffer behind the image.
850  builder.Save();
851  {
852  DlPaint paint;
853  paint.setColor(DlColor::kGreen());
854  builder.DrawPaint(paint);
855  builder.ClipRect(SkRect::MakeLTRB(-180, -180, 180, 180),
856  DlCanvas::ClipOp::kDifference);
857 
858  paint.setColor(DlColor::kBlack());
859  builder.DrawPaint(paint);
860  }
861  builder.Restore(); // Restore rectangle difference clip.
862 
863  builder.Save();
864  {
865  // 2. Draw an oval clip that applies to the image, which will get drawn
866  // in front of the image on the depth buffer.
867  builder.ClipOval(SkRect::MakeLTRB(-200, -200, 200, 200));
868 
869  Matrix result =
870  Matrix(1.0, 0.0, 0.0, 0.0, //
871  0.0, 1.0, 0.0, 0.0, //
872  0.0, 0.0, 1.0, 0.003, //
873  0.0, 0.0, 0.0, 1.0) *
874  Matrix::MakeRotationY({Radians{-1.0f + (time++ / 60.0f)}});
875 
876  // 3. Draw the rotating image with a perspective transform.
877  builder.Transform(FromImpellerMatrix(result));
878 
879  auto image =
880  DlImageImpeller::Make(CreateTextureForFixture("airplane.jpg"));
881  auto position = -SkPoint::Make(image->dimensions().fWidth,
882  image->dimensions().fHeight) *
883  0.5;
884  builder.DrawImage(image, position, {});
885  }
886  builder.Restore(); // Restore oval intersect clip.
887 
888  // 4. Draw a semi-translucent blue circle atop all previous draws.
889  DlPaint paint;
890  paint.setColor(DlColor::kBlue().modulateOpacity(0.4));
891  builder.DrawCircle(SkPoint{}, 230, paint);
892  }
893  builder.Restore(); // Restore translation.
894 
895  return builder.Build();
896  };
897  ASSERT_TRUE(OpenPlaygroundHere(callback));
898 }
899 
900 TEST_P(AiksTest, ImageColorSourceEffectTransform) {
901  // Compare with https://fiddle.skia.org/c/6cdc5aefb291fda3833b806ca347a885
902 
903  DisplayListBuilder builder;
904  auto texture = DlImageImpeller::Make(CreateTextureForFixture("monkey.png"));
905 
906  DlPaint paint;
907  paint.setColor(DlColor::kWhite());
908  builder.DrawPaint(paint);
909 
910  // Translation
911  {
912  DlMatrix matrix = DlMatrix::MakeTranslation({50, 50});
913  DlPaint paint;
914  paint.setColorSource(DlColorSource::MakeImage(
915  texture, DlTileMode::kRepeat, DlTileMode::kRepeat,
916  DlImageSampling::kNearestNeighbor, &matrix));
917 
918  builder.DrawRect(SkRect::MakeLTRB(0, 0, 100, 100), paint);
919  }
920 
921  // Rotation/skew
922  {
923  builder.Save();
924  builder.Rotate(45);
925  DlPaint paint;
926 
927  Matrix matrix(1, -1, 0, 0, //
928  1, 1, 0, 0, //
929  0, 0, 1, 0, //
930  0, 0, 0, 1);
931  paint.setColorSource(DlColorSource::MakeImage(
932  texture, DlTileMode::kRepeat, DlTileMode::kRepeat,
933  DlImageSampling::kNearestNeighbor, &matrix));
934  builder.DrawRect(SkRect::MakeLTRB(100, 0, 200, 100), paint);
935  builder.Restore();
936  }
937 
938  // Scale
939  {
940  builder.Translate(100, 0);
941  builder.Scale(100, 100);
942  DlPaint paint;
943 
944  DlMatrix matrix = DlMatrix::MakeScale({0.005, 0.005, 1});
945  paint.setColorSource(DlColorSource::MakeImage(
946  texture, DlTileMode::kRepeat, DlTileMode::kRepeat,
947  DlImageSampling::kNearestNeighbor, &matrix));
948 
949  builder.DrawRect(SkRect::MakeLTRB(0, 0, 1, 1), paint);
950  }
951 
952  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
953 }
954 
955 TEST_P(AiksTest, SubpassWithClearColorOptimization) {
956  DisplayListBuilder builder;
957 
958  // Use a non-srcOver blend mode to ensure that we don't detect this as an
959  // opacity peephole optimization.
960  DlPaint paint;
961  paint.setColor(DlColor::kBlue().modulateOpacity(0.5));
962  paint.setBlendMode(DlBlendMode::kSrc);
963 
964  SkRect bounds = SkRect::MakeLTRB(0, 0, 200, 200);
965  builder.SaveLayer(&bounds, &paint);
966 
967  paint.setColor(DlColor::kTransparent());
968  paint.setBlendMode(DlBlendMode::kSrc);
969  builder.DrawPaint(paint);
970  builder.Restore();
971 
972  paint.setColor(DlColor::kBlue());
973  paint.setBlendMode(DlBlendMode::kDstOver);
974  builder.SaveLayer(nullptr, &paint);
975  builder.Restore();
976 
977  // This playground should appear blank on CI since we are only drawing
978  // transparent black. If the clear color optimization is broken, the texture
979  // will be filled with NaNs and may produce a magenta texture on macOS or iOS.
980  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
981 }
982 
983 // Render a white circle at the top left corner of the screen.
984 TEST_P(AiksTest, MatrixImageFilterDoesntCullWhenTranslatedFromOffscreen) {
985  DisplayListBuilder builder;
986  builder.Scale(GetContentScale().x, GetContentScale().y);
987  builder.Translate(100, 100);
988  // Draw a circle in a SaveLayer at -300, but move it back on-screen with a
989  // +300 translation applied by a SaveLayer image filter.
990  DlPaint paint;
991  DlMatrix translate = DlMatrix::MakeTranslation({300, 0});
992  paint.setImageFilter(
993  DlImageFilter::MakeMatrix(translate, DlImageSampling::kLinear));
994  builder.SaveLayer(nullptr, &paint);
995 
996  DlPaint circle_paint;
997  circle_paint.setColor(DlColor::kGreen());
998  builder.DrawCircle(SkPoint{-300, 0}, 100, circle_paint);
999  builder.Restore();
1000 
1001  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
1002 }
1003 
1004 // Render a white circle at the top left corner of the screen.
1006  MatrixImageFilterDoesntCullWhenScaledAndTranslatedFromOffscreen) {
1007  DisplayListBuilder builder;
1008  builder.Scale(GetContentScale().x, GetContentScale().y);
1009  builder.Translate(100, 100);
1010  // Draw a circle in a SaveLayer at -300, but move it back on-screen with a
1011  // +300 translation applied by a SaveLayer image filter.
1012 
1013  DlPaint paint;
1014  paint.setImageFilter(DlImageFilter::MakeMatrix(
1015  DlMatrix::MakeTranslation({300, 0}) * DlMatrix::MakeScale({2, 2, 1}),
1016  DlImageSampling::kNearestNeighbor));
1017  builder.SaveLayer(nullptr, &paint);
1018 
1019  DlPaint circle_paint;
1020  circle_paint.setColor(DlColor::kGreen());
1021  builder.DrawCircle(SkPoint{-150, 0}, 50, circle_paint);
1022  builder.Restore();
1023 
1024  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
1025 }
1026 
1027 // This should be solid red, if you see a little red box this is broken.
1028 TEST_P(AiksTest, ClearColorOptimizationWhenSubpassIsBiggerThanParentPass) {
1029  SetWindowSize({400, 400});
1030  DisplayListBuilder builder;
1031 
1032  builder.Scale(GetContentScale().x, GetContentScale().y);
1033 
1034  DlPaint paint;
1035  paint.setColor(DlColor::kRed());
1036  builder.DrawRect(SkRect::MakeLTRB(200, 200, 300, 300), paint);
1037 
1038  paint.setImageFilter(DlImageFilter::MakeMatrix(DlMatrix::MakeScale({2, 2, 1}),
1039  DlImageSampling::kLinear));
1040  builder.SaveLayer(nullptr, &paint);
1041  // Draw a rectangle that would fully cover the parent pass size, but not
1042  // the subpass that it is rendered in.
1043  paint.setColor(DlColor::kGreen());
1044  builder.DrawRect(SkRect::MakeLTRB(0, 0, 400, 400), paint);
1045  // Draw a bigger rectangle to force the subpass to be bigger.
1046 
1047  paint.setColor(DlColor::kRed());
1048  builder.DrawRect(SkRect::MakeLTRB(0, 0, 800, 800), paint);
1049  builder.Restore();
1050 
1051  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
1052 }
1053 
1054 TEST_P(AiksTest, EmptySaveLayerIgnoresPaint) {
1055  DisplayListBuilder builder;
1056  builder.Scale(GetContentScale().x, GetContentScale().y);
1057 
1058  DlPaint paint;
1059  paint.setColor(DlColor::kRed());
1060  builder.DrawPaint(paint);
1061  builder.ClipRect(SkRect::MakeXYWH(100, 100, 200, 200));
1062  paint.setColor(DlColor::kBlue());
1063  builder.SaveLayer(nullptr, &paint);
1064  builder.Restore();
1065 
1066  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
1067 }
1068 
1069 TEST_P(AiksTest, EmptySaveLayerRendersWithClear) {
1070  DisplayListBuilder builder;
1071  builder.Scale(GetContentScale().x, GetContentScale().y);
1072  auto image = DlImageImpeller::Make(CreateTextureForFixture("airplane.jpg"));
1073  builder.DrawImage(image, SkPoint{10, 10}, {});
1074  builder.ClipRect(SkRect::MakeXYWH(100, 100, 200, 200));
1075 
1076  DlPaint paint;
1077  paint.setBlendMode(DlBlendMode::kClear);
1078  builder.SaveLayer(nullptr, &paint);
1079  builder.Restore();
1080 
1081  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
1082 }
1083 
1085  CanPerformSaveLayerWithBoundsAndLargerIntermediateIsNotAllocated) {
1086  DisplayListBuilder builder;
1087 
1088  DlPaint red;
1089  red.setColor(DlColor::kRed());
1090 
1091  DlPaint green;
1092  green.setColor(DlColor::kGreen());
1093 
1094  DlPaint blue;
1095  blue.setColor(DlColor::kBlue());
1096 
1097  DlPaint save;
1098  save.setColor(DlColor::kBlack().modulateOpacity(0.5));
1099 
1100  SkRect huge_bounds = SkRect::MakeXYWH(0, 0, 100000, 100000);
1101  builder.SaveLayer(&huge_bounds, &save);
1102 
1103  builder.DrawRect(SkRect::MakeXYWH(0, 0, 100, 100), red);
1104  builder.DrawRect(SkRect::MakeXYWH(10, 10, 100, 100), green);
1105  builder.DrawRect(SkRect::MakeXYWH(20, 20, 100, 100), blue);
1106 
1107  builder.Restore();
1108 
1109  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
1110 }
1111 
1112 // This makes sure the WideGamut named tests use 16bit float pixel format.
1113 TEST_P(AiksTest, FormatWideGamut) {
1114  EXPECT_EQ(GetContext()->GetCapabilities()->GetDefaultColorFormat(),
1116 }
1117 
1118 TEST_P(AiksTest, FormatSRGB) {
1119  PixelFormat pixel_format =
1120  GetContext()->GetCapabilities()->GetDefaultColorFormat();
1121  EXPECT_TRUE(pixel_format == PixelFormat::kR8G8B8A8UNormInt ||
1122  pixel_format == PixelFormat::kB8G8R8A8UNormInt)
1123  << "pixel format: " << PixelFormatToString(pixel_format);
1124 }
1125 
1126 TEST_P(AiksTest, CoordinateConversionsAreCorrect) {
1127  DisplayListBuilder builder;
1128 
1129  // Render a texture directly.
1130  {
1131  auto image = DlImageImpeller::Make(CreateTextureForFixture("kalimba.jpg"));
1132 
1133  builder.Save();
1134  builder.Translate(100, 200);
1135  builder.Scale(0.5, 0.5);
1136  builder.DrawImage(image, SkPoint::Make(100.0, 100.0),
1137  DlImageSampling::kNearestNeighbor);
1138  builder.Restore();
1139  }
1140 
1141  // Render an offscreen rendered texture.
1142  {
1143  DlPaint alpha;
1144  alpha.setColor(DlColor::kRed().modulateOpacity(0.5));
1145 
1146  builder.SaveLayer(nullptr, &alpha);
1147 
1148  DlPaint paint;
1149  paint.setColor(DlColor::kRed());
1150  builder.DrawRect(SkRect::MakeXYWH(000, 000, 100, 100), paint);
1151  paint.setColor(DlColor::kGreen());
1152  builder.DrawRect(SkRect::MakeXYWH(020, 020, 100, 100), paint);
1153  paint.setColor(DlColor::kBlue());
1154  builder.DrawRect(SkRect::MakeXYWH(040, 040, 100, 100), paint);
1155 
1156  builder.Restore();
1157  }
1158 
1159  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
1160 }
1161 
1162 TEST_P(AiksTest, CanPerformFullScreenMSAA) {
1163  DisplayListBuilder builder;
1164 
1165  DlPaint paint;
1166  paint.setColor(DlColor::kRed());
1167  builder.DrawCircle(SkPoint::Make(250, 250), 125, paint);
1168 
1169  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
1170 }
1171 
1172 TEST_P(AiksTest, CanPerformSkew) {
1173  DisplayListBuilder builder;
1174 
1175  DlPaint red;
1176  red.setColor(DlColor::kRed());
1177  builder.Skew(2, 5);
1178  builder.DrawRect(SkRect::MakeXYWH(0, 0, 100, 100), red);
1179 
1180  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
1181 }
1182 
1183 TEST_P(AiksTest, CanPerformSaveLayerWithBounds) {
1184  DisplayListBuilder builder;
1185 
1186  DlPaint save;
1187  save.setColor(DlColor::kBlack());
1188 
1189  SkRect save_bounds = SkRect::MakeXYWH(0, 0, 50, 50);
1190  builder.SaveLayer(&save_bounds, &save);
1191 
1192  DlPaint paint;
1193  paint.setColor(DlColor::kRed());
1194  builder.DrawRect(SkRect::MakeXYWH(0, 0, 100, 100), paint);
1195  paint.setColor(DlColor::kGreen());
1196  builder.DrawRect(SkRect::MakeXYWH(10, 10, 100, 100), paint);
1197  paint.setColor(DlColor::kBlue());
1198  builder.DrawRect(SkRect::MakeXYWH(20, 20, 100, 100), paint);
1199 
1200  builder.Restore();
1201 
1202  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
1203 }
1204 
1205 TEST_P(AiksTest, FilledRoundRectPathsRenderCorrectly) {
1206  DisplayListBuilder builder;
1207  builder.Scale(GetContentScale().x, GetContentScale().y);
1208 
1209  DlPaint paint;
1210  const int color_count = 3;
1211  DlColor colors[color_count] = {
1212  DlColor::kBlue(),
1213  DlColor::kGreen(),
1214  DlColor::ARGB(1.0, 220.0f / 255.0f, 20.0f / 255.0f, 60.0f / 255.0f),
1215  };
1216 
1217  paint.setColor(DlColor::kWhite());
1218  builder.DrawPaint(paint);
1219 
1220  auto draw_rrect_as_path = [&builder](const SkRect& rect, Scalar x, Scalar y,
1221  const DlPaint& paint) {
1222  SkPath path;
1223  path.addRoundRect(rect, x, y);
1224  builder.DrawPath(path, paint);
1225  };
1226 
1227  int c_index = 0;
1228  for (int i = 0; i < 4; i++) {
1229  for (int j = 0; j < 4; j++) {
1230  paint.setColor(colors[(c_index++) % color_count]);
1231  draw_rrect_as_path(SkRect::MakeXYWH(i * 100 + 10, j * 100 + 20, 80, 80),
1232  i * 5 + 10, j * 5 + 10, paint);
1233  }
1234  }
1235  paint.setColor(colors[(c_index++) % color_count]);
1236  draw_rrect_as_path(SkRect::MakeXYWH(10, 420, 380, 80), 40, 40, paint);
1237  paint.setColor(colors[(c_index++) % color_count]);
1238  draw_rrect_as_path(SkRect::MakeXYWH(410, 20, 80, 380), 40, 40, paint);
1239 
1240  std::vector<DlColor> gradient_colors = {
1241  DlColor::RGBA(0x1f / 255.0, 0.0, 0x5c / 255.0, 1.0),
1242  DlColor::RGBA(0x5b / 255.0, 0.0, 0x60 / 255.0, 1.0),
1243  DlColor::RGBA(0x87 / 255.0, 0x01 / 255.0, 0x60 / 255.0, 1.0),
1244  DlColor::RGBA(0xac / 255.0, 0x25 / 255.0, 0x53 / 255.0, 1.0),
1245  DlColor::RGBA(0xe1 / 255.0, 0x6b / 255.0, 0x5c / 255.0, 1.0),
1246  DlColor::RGBA(0xf3 / 255.0, 0x90 / 255.0, 0x60 / 255.0, 1.0),
1247  DlColor::RGBA(0xff / 255.0, 0xb5 / 255.0, 0x6b / 250.0, 1.0)};
1248  std::vector<Scalar> stops = {
1249  0.0,
1250  (1.0 / 6.0) * 1,
1251  (1.0 / 6.0) * 2,
1252  (1.0 / 6.0) * 3,
1253  (1.0 / 6.0) * 4,
1254  (1.0 / 6.0) * 5,
1255  1.0,
1256  };
1257  auto texture = DlImageImpeller::Make(
1258  CreateTextureForFixture("airplane.jpg",
1259  /*enable_mipmapping=*/true));
1260 
1261  paint.setColor(DlColor::kWhite().modulateOpacity(0.1));
1262  paint.setColorSource(DlColorSource::MakeRadial(
1263  /*center=*/{550, 550},
1264  /*radius=*/75,
1265  /*stop_count=*/gradient_colors.size(),
1266  /*colors=*/gradient_colors.data(),
1267  /*stops=*/stops.data(),
1268  /*tile_mode=*/DlTileMode::kMirror));
1269  for (int i = 1; i <= 10; i++) {
1270  int j = 11 - i;
1271  draw_rrect_as_path(SkRect::MakeLTRB(550 - i * 20, 550 - j * 20, //
1272  550 + i * 20, 550 + j * 20),
1273  i * 10, j * 10, paint);
1274  }
1275  paint.setColor(DlColor::kWhite().modulateOpacity(0.5));
1276  paint.setColorSource(DlColorSource::MakeRadial(
1277  /*center=*/{200, 650},
1278  /*radius=*/75,
1279  /*stop_count=*/gradient_colors.size(),
1280  /*colors=*/gradient_colors.data(),
1281  /*stops=*/stops.data(),
1282  /*tile_mode=*/DlTileMode::kMirror));
1283  draw_rrect_as_path(SkRect::MakeLTRB(100, 610, 300, 690), 40, 40, paint);
1284  draw_rrect_as_path(SkRect::MakeLTRB(160, 550, 240, 750), 40, 40, paint);
1285 
1286  auto matrix = DlMatrix::MakeTranslation({520, 20});
1287  paint.setColor(DlColor::kWhite().modulateOpacity(0.1));
1288  paint.setColorSource(DlColorSource::MakeImage(
1289  texture, DlTileMode::kRepeat, DlTileMode::kRepeat,
1290  DlImageSampling::kMipmapLinear, &matrix));
1291  for (int i = 1; i <= 10; i++) {
1292  int j = 11 - i;
1293  draw_rrect_as_path(SkRect::MakeLTRB(720 - i * 20, 220 - j * 20, //
1294  720 + i * 20, 220 + j * 20),
1295  i * 10, j * 10, paint);
1296  }
1297  matrix = DlMatrix::MakeTranslation({800, 300});
1298  paint.setColor(DlColor::kWhite().modulateOpacity(0.5));
1299  paint.setColorSource(DlColorSource::MakeImage(
1300  texture, DlTileMode::kRepeat, DlTileMode::kRepeat,
1301  DlImageSampling::kMipmapLinear, &matrix));
1302 
1303  draw_rrect_as_path(SkRect::MakeLTRB(800, 410, 1000, 490), 40, 40, paint);
1304  draw_rrect_as_path(SkRect::MakeLTRB(860, 350, 940, 550), 40, 40, paint);
1305 
1306  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
1307 }
1308 
1309 TEST_P(AiksTest, CoverageOriginShouldBeAccountedForInSubpasses) {
1310  auto callback = [&]() -> sk_sp<DisplayList> {
1311  DisplayListBuilder builder;
1312  builder.Scale(GetContentScale().x, GetContentScale().y);
1313 
1314  DlPaint alpha;
1315  alpha.setColor(DlColor::kRed().modulateOpacity(0.5));
1316 
1317  auto current = Point{25, 25};
1318  const auto offset = Point{25, 25};
1319  const auto size = Size(100, 100);
1320 
1321  static PlaygroundPoint point_a(Point(40, 40), 10, Color::White());
1322  static PlaygroundPoint point_b(Point(160, 160), 10, Color::White());
1323  auto [b0, b1] = DrawPlaygroundLine(point_a, point_b);
1324  SkRect bounds = SkRect::MakeLTRB(b0.x, b0.y, b1.x, b1.y);
1325 
1326  DlPaint stroke_paint;
1327  stroke_paint.setColor(DlColor::kYellow());
1328  stroke_paint.setStrokeWidth(5);
1329  stroke_paint.setDrawStyle(DlDrawStyle::kStroke);
1330  builder.DrawRect(bounds, stroke_paint);
1331 
1332  builder.SaveLayer(&bounds, &alpha);
1333 
1334  DlPaint paint;
1335  paint.setColor(DlColor::kRed());
1336  builder.DrawRect(
1337  SkRect::MakeXYWH(current.x, current.y, size.width, size.height), paint);
1338 
1339  paint.setColor(DlColor::kGreen());
1340  current += offset;
1341  builder.DrawRect(
1342  SkRect::MakeXYWH(current.x, current.y, size.width, size.height), paint);
1343 
1344  paint.setColor(DlColor::kBlue());
1345  current += offset;
1346  builder.DrawRect(
1347  SkRect::MakeXYWH(current.x, current.y, size.width, size.height), paint);
1348 
1349  builder.Restore();
1350 
1351  return builder.Build();
1352  };
1353 
1354  ASSERT_TRUE(OpenPlaygroundHere(callback));
1355 }
1356 
1357 TEST_P(AiksTest, SaveLayerDrawsBehindSubsequentEntities) {
1358  // Compare with https://fiddle.skia.org/c/9e03de8567ffb49e7e83f53b64bcf636
1359  DisplayListBuilder builder;
1360  DlPaint paint;
1361 
1362  paint.setColor(DlColor::kBlack());
1363  SkRect rect = SkRect::MakeXYWH(25, 25, 25, 25);
1364  builder.DrawRect(rect, paint);
1365 
1366  builder.Translate(10, 10);
1367 
1368  DlPaint save_paint;
1369  builder.SaveLayer(nullptr, &save_paint);
1370 
1371  paint.setColor(DlColor::kGreen());
1372  builder.DrawRect(rect, paint);
1373 
1374  builder.Restore();
1375 
1376  builder.Translate(10, 10);
1377  paint.setColor(DlColor::kRed());
1378  builder.DrawRect(rect, paint);
1379 
1380  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
1381 }
1382 
1383 TEST_P(AiksTest, SiblingSaveLayerBoundsAreRespected) {
1384  DisplayListBuilder builder;
1385  DlPaint paint;
1386  SkRect rect = SkRect::MakeXYWH(0, 0, 1000, 1000);
1387 
1388  // Black, green, and red squares offset by [10, 10].
1389  {
1390  DlPaint save_paint;
1391  SkRect bounds = SkRect::MakeXYWH(25, 25, 25, 25);
1392  builder.SaveLayer(&bounds, &save_paint);
1393  paint.setColor(DlColor::kBlack());
1394  builder.DrawRect(rect, paint);
1395  builder.Restore();
1396  }
1397 
1398  {
1399  DlPaint save_paint;
1400  SkRect bounds = SkRect::MakeXYWH(35, 35, 25, 25);
1401  builder.SaveLayer(&bounds, &save_paint);
1402  paint.setColor(DlColor::kGreen());
1403  builder.DrawRect(rect, paint);
1404  builder.Restore();
1405  }
1406 
1407  {
1408  DlPaint save_paint;
1409  SkRect bounds = SkRect::MakeXYWH(45, 45, 25, 25);
1410  builder.SaveLayer(&bounds, &save_paint);
1411  paint.setColor(DlColor::kRed());
1412  builder.DrawRect(rect, paint);
1413  builder.Restore();
1414  }
1415 
1416  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
1417 }
1418 
1419 TEST_P(AiksTest, CanRenderClippedLayers) {
1420  DisplayListBuilder builder;
1421 
1422  DlPaint paint;
1423  paint.setColor(DlColor::kWhite());
1424  builder.DrawPaint(paint);
1425 
1426  // Draw a green circle on the screen.
1427  {
1428  // Increase the clip depth for the savelayer to contend with.
1429  SkPath path = SkPath::Circle(100, 100, 50);
1430  builder.ClipPath(path);
1431 
1432  SkRect bounds = SkRect::MakeXYWH(50, 50, 100, 100);
1433  DlPaint save_paint;
1434  builder.SaveLayer(&bounds, &save_paint);
1435 
1436  // Fill the layer with white.
1437  paint.setColor(DlColor::kWhite());
1438  builder.DrawRect(SkRect::MakeSize(SkSize{400, 400}), paint);
1439  // Fill the layer with green, but do so with a color blend that can't be
1440  // collapsed into the parent pass.
1441  paint.setColor(DlColor::kGreen());
1442  paint.setBlendMode(DlBlendMode::kHardLight);
1443  builder.DrawRect(SkRect::MakeSize(SkSize{400, 400}), paint);
1444  }
1445 
1446  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
1447 }
1448 
1449 TEST_P(AiksTest, SaveLayerFiltersScaleWithTransform) {
1450  DisplayListBuilder builder;
1451 
1452  builder.Scale(GetContentScale().x, GetContentScale().y);
1453  builder.Translate(100, 100);
1454 
1455  auto texture = DlImageImpeller::Make(CreateTextureForFixture("boston.jpg"));
1456  auto draw_image_layer = [&builder, &texture](const DlPaint& paint) {
1457  builder.SaveLayer(nullptr, &paint);
1458  builder.DrawImage(texture, SkPoint{}, DlImageSampling::kLinear);
1459  builder.Restore();
1460  };
1461 
1462  DlPaint effect_paint;
1463  effect_paint.setMaskFilter(DlBlurMaskFilter::Make(DlBlurStyle::kNormal, 6));
1464  draw_image_layer(effect_paint);
1465 
1466  builder.Translate(300, 300);
1467  builder.Scale(3, 3);
1468  draw_image_layer(effect_paint);
1469 
1470  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
1471 }
1472 
1473 TEST_P(AiksTest, FastEllipticalRRectMaskBlursRenderCorrectly) {
1474  DisplayListBuilder builder;
1475 
1476  builder.Scale(GetContentScale().x, GetContentScale().y);
1477  DlPaint paint;
1478  paint.setMaskFilter(DlBlurMaskFilter::Make(DlBlurStyle::kNormal, 1));
1479 
1480  DlPaint save_paint;
1481  save_paint.setColor(DlColor::kWhite());
1482  builder.DrawPaint(save_paint);
1483 
1484  paint.setColor(DlColor::kBlue());
1485  for (int i = 0; i < 5; i++) {
1486  Scalar y = i * 125;
1487  Scalar y_radius = i * 15;
1488  for (int j = 0; j < 5; j++) {
1489  Scalar x = j * 125;
1490  Scalar x_radius = j * 15;
1491  builder.DrawRRect(
1492  SkRRect::MakeRectXY(SkRect::MakeXYWH(x + 50, y + 50, 100.0f, 100.0f),
1493  x_radius, y_radius),
1494  paint);
1495  }
1496  }
1497 
1498  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
1499 }
1500 
1501 TEST_P(AiksTest, PipelineBlendSingleParameter) {
1502  DisplayListBuilder builder;
1503 
1504  // Should render a green square in the middle of a blue circle.
1505  DlPaint paint;
1506  builder.SaveLayer(nullptr, &paint);
1507  {
1508  builder.Translate(100, 100);
1509  paint.setColor(DlColor::kBlue());
1510  builder.DrawCircle(SkPoint::Make(200, 200), 200, paint);
1511  builder.ClipRect(SkRect::MakeXYWH(100, 100, 200, 200));
1512 
1513  paint.setColor(DlColor::kGreen());
1514  paint.setBlendMode(DlBlendMode::kSrcOver);
1515  paint.setImageFilter(DlImageFilter::MakeColorFilter(
1516  DlColorFilter::MakeBlend(DlColor::kWhite(), DlBlendMode::kDst)));
1517  builder.DrawCircle(SkPoint::Make(200, 200), 200, paint);
1518  builder.Restore();
1519  }
1520 
1521  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
1522 }
1523 
1524 // Creates an image matrix filter that scales large content such that it would
1525 // exceed the max texture size. See
1526 // https://github.com/flutter/flutter/issues/128912
1527 TEST_P(AiksTest, MassiveScalingMatrixImageFilter) {
1528  if (GetBackend() == PlaygroundBackend::kVulkan) {
1529  GTEST_SKIP() << "Swiftshader is running out of memory on this example.";
1530  }
1531  DisplayListBuilder builder(SkRect::MakeSize(SkSize::Make(1000, 1000)));
1532 
1533  auto filter = DlImageFilter::MakeMatrix(
1534  DlMatrix::MakeScale({0.001, 0.001, 1}), DlImageSampling::kLinear);
1535 
1536  DlPaint paint;
1537  paint.setImageFilter(filter);
1538  builder.SaveLayer(nullptr, &paint);
1539  {
1540  DlPaint paint;
1541  paint.setColor(DlColor::kRed());
1542  builder.DrawRect(SkRect::MakeLTRB(0, 0, 100000, 100000), paint);
1543  }
1544  builder.Restore();
1545 
1546  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
1547 }
1548 
1549 TEST_P(AiksTest, NoDimplesInRRectPath) {
1550  Scalar width = 200.f;
1551  Scalar height = 60.f;
1552  Scalar corner = 1.f;
1553  auto callback = [&]() -> sk_sp<DisplayList> {
1554  if (AiksTest::ImGuiBegin("Controls", nullptr,
1555  ImGuiWindowFlags_AlwaysAutoResize)) {
1556  ImGui::SliderFloat("width", &width, 0, 200);
1557  ImGui::SliderFloat("height", &height, 0, 200);
1558  ImGui::SliderFloat("corner", &corner, 0, 1);
1559  ImGui::End();
1560  }
1561 
1562  DisplayListBuilder builder;
1563  builder.Scale(GetContentScale().x, GetContentScale().y);
1564 
1565  DlPaint background_paint;
1566  background_paint.setColor(DlColor(1, 0.1, 0.1, 0.1, DlColorSpace::kSRGB));
1567  builder.DrawPaint(background_paint);
1568 
1569  std::vector<DlColor> colors = {DlColor::kRed(), DlColor::kBlue()};
1570  std::vector<Scalar> stops = {0.0, 1.0};
1571 
1572  DlPaint paint;
1573  auto gradient = DlColorSource::MakeLinear(
1574  {0, 0}, {200, 200}, 2, colors.data(), stops.data(), DlTileMode::kClamp);
1575  paint.setColorSource(gradient);
1576  paint.setColor(DlColor::kWhite());
1577  paint.setDrawStyle(DlDrawStyle::kStroke);
1578  paint.setStrokeWidth(20);
1579 
1580  builder.Save();
1581  builder.Translate(100, 100);
1582 
1583  Scalar corner_x = ((1 - corner) * 50) + 50;
1584  Scalar corner_y = corner * 50 + 50;
1585  SkRRect rrect = SkRRect::MakeRectXY(SkRect::MakeXYWH(0, 0, width, height),
1586  corner_x, corner_y);
1587  builder.DrawRRect(rrect, paint);
1588  builder.Restore();
1589  return builder.Build();
1590  };
1591  ASSERT_TRUE(OpenPlaygroundHere(callback));
1592 }
1593 
1594 TEST_P(AiksTest, BackdropFilterOverUnclosedClip) {
1595  DisplayListBuilder builder;
1596 
1597  builder.DrawPaint(DlPaint().setColor(DlColor::kWhite()));
1598  builder.Save();
1599  {
1600  builder.ClipRect(DlRect::MakeLTRB(100, 100, 800, 800));
1601 
1602  builder.Save();
1603  {
1604  builder.ClipRect(DlRect::MakeLTRB(600, 600, 800, 800));
1605  builder.DrawPaint(DlPaint().setColor(DlColor::kRed()));
1606  builder.DrawPaint(DlPaint().setColor(DlColor::kBlue().withAlphaF(0.5)));
1607  builder.ClipRect(DlRect::MakeLTRB(700, 700, 750, 800));
1608  builder.DrawPaint(DlPaint().setColor(DlColor::kRed().withAlphaF(0.5)));
1609  }
1610  builder.Restore();
1611 
1612  auto image_filter = DlImageFilter::MakeBlur(10, 10, DlTileMode::kDecal);
1613  builder.SaveLayer(std::nullopt, nullptr, image_filter.get());
1614  }
1615  builder.Restore();
1616  builder.DrawCircle(SkPoint{100, 100}, 100,
1617  DlPaint().setColor(DlColor::kAqua()));
1618 
1619  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
1620 }
1621 
1622 } // namespace testing
1623 } // namespace impeller
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)
int32_t x
AiksPlayground AiksTest
TEST_P(AiksTest, DrawAtlasNoColor)
float Scalar
Definition: scalar.h:18
std::tuple< Point, Point > DrawPlaygroundLine(PlaygroundPoint &point_a, PlaygroundPoint &point_b)
Definition: widgets.cc:50
TPoint< Scalar > Point
Definition: point.h:327
PixelFormat
The Pixel formats supported by Impeller. The naming convention denotes the usage of the component,...
Definition: formats.h:99
TSize< Scalar > Size
Definition: size.h:171
constexpr const char * PixelFormatToString(PixelFormat format)
Definition: formats.h:140
flutter::DlScalar DlScalar
Definition: dl_dispatcher.h:23
const Scalar stroke_width
SeparatedVector2 offset
static constexpr Color White()
Definition: color.h:263
A 4x4 matrix using column-major storage.
Definition: matrix.h:37
static constexpr Matrix MakeTranslation(const Vector3 &t)
Definition: matrix.h:95
static Matrix MakeRotationY(Radians r)
Definition: matrix.h:208
constexpr Quad Transform(const Quad &quad) const
Definition: matrix.h:543