Flutter Impeller
entity_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 <algorithm>
6 #include <cstring>
7 #include <memory>
8 #include <optional>
9 #include <utility>
10 #include <vector>
11 
12 #include "flutter/display_list/geometry/dl_path_builder.h"
13 #include "flutter/display_list/testing/dl_test_snippets.h"
14 #include "fml/logging.h"
15 #include "gtest/gtest.h"
17 #include "impeller/core/formats.h"
19 #include "impeller/core/raw_ptr.h"
37 #include "impeller/entity/entity.h"
55 #include "impeller/renderer/testing/mocks.h"
57 #include "third_party/imgui/imgui.h"
58 
59 // TODO(zanderso): https://github.com/flutter/flutter/issues/127701
60 // NOLINTBEGIN(bugprone-unchecked-optional-access)
61 
62 namespace impeller {
63 namespace testing {
64 
65 using EntityTest = EntityPlayground;
67 
69  return Rect::MakeSize(size).Shift(center - size / 2);
70 }
71 
72 TEST_P(EntityTest, CanCreateEntity) {
73  Entity entity;
74  ASSERT_TRUE(entity.GetTransform().IsIdentity());
75 }
76 
77 TEST_P(EntityTest, FilterCoverageRespectsCropRect) {
78  auto image = CreateTextureForFixture("boston.jpg");
80  FilterInput::Make({image}));
81 
82  // Without the crop rect (default behavior).
83  {
84  auto actual = filter->GetCoverage({});
85  auto expected = Rect::MakeSize(image->GetSize());
86 
87  ASSERT_TRUE(actual.has_value());
88  ASSERT_RECT_NEAR(actual.value(), expected);
89  }
90 
91  // With the crop rect.
92  {
93  auto expected = Rect::MakeLTRB(50, 50, 100, 100);
94  filter->SetCoverageHint(expected);
95  auto actual = filter->GetCoverage({});
96 
97  ASSERT_TRUE(actual.has_value());
98  ASSERT_RECT_NEAR(actual.value(), expected);
99  }
100 }
101 
102 TEST_P(EntityTest, GeometryBoundsAreTransformed) {
103  auto geometry = Geometry::MakeRect(Rect::MakeXYWH(100, 100, 100, 100));
104  auto transform = Matrix::MakeScale({2.0, 2.0, 2.0});
105 
106  ASSERT_RECT_NEAR(geometry->GetCoverage(transform).value(),
107  Rect::MakeXYWH(200, 200, 200, 200));
108 }
109 
110 TEST_P(EntityTest, ThreeStrokesInOnePath) {
111  flutter::DlPath path = flutter::DlPathBuilder{}
112  .MoveTo({100, 100})
113  .LineTo({100, 200})
114  .MoveTo({100, 300})
115  .LineTo({100, 400})
116  .MoveTo({100, 500})
117  .LineTo({100, 600})
118  .TakePath();
119 
120  Entity entity;
121  entity.SetTransform(Matrix::MakeScale(GetContentScale()));
122  auto contents = std::make_unique<SolidColorContents>();
123 
124  std::unique_ptr<Geometry> geom =
125  Geometry::MakeStrokePath(path, {.width = 5.0f});
126  contents->SetGeometry(geom.get());
127  contents->SetColor(Color::Red());
128  entity.SetContents(std::move(contents));
129  ASSERT_TRUE(OpenPlaygroundHere(std::move(entity)));
130 }
131 
132 TEST_P(EntityTest, StrokeWithTextureContents) {
133  auto bridge = CreateTextureForFixture("bay_bridge.jpg");
134  flutter::DlPath path = flutter::DlPathBuilder{}
135  .MoveTo({100, 100})
136  .LineTo({100, 200})
137  .MoveTo({100, 300})
138  .LineTo({100, 400})
139  .MoveTo({100, 500})
140  .LineTo({100, 600})
141  .TakePath();
142 
143  Entity entity;
144  entity.SetTransform(Matrix::MakeScale(GetContentScale()));
145  auto contents = std::make_unique<TiledTextureContents>();
146  std::unique_ptr<Geometry> geom =
147  Geometry::MakeStrokePath(path, {.width = 100.0f});
148  contents->SetGeometry(geom.get());
149  contents->SetTexture(bridge);
150  contents->SetTileModes(Entity::TileMode::kClamp, Entity::TileMode::kClamp);
151  entity.SetContents(std::move(contents));
152  ASSERT_TRUE(OpenPlaygroundHere(std::move(entity)));
153 }
154 
155 TEST_P(EntityTest, TriangleInsideASquare) {
156  auto callback = [&](ContentContext& context, RenderPass& pass) {
157  Point offset(100, 100);
158 
159  static PlaygroundPoint point_a(Point(10, 10) + offset, 20, Color::White());
160  Point a = DrawPlaygroundPoint(point_a);
161  static PlaygroundPoint point_b(Point(210, 10) + offset, 20, Color::White());
162  Point b = DrawPlaygroundPoint(point_b);
163  static PlaygroundPoint point_c(Point(210, 210) + offset, 20,
164  Color::White());
165  Point c = DrawPlaygroundPoint(point_c);
166  static PlaygroundPoint point_d(Point(10, 210) + offset, 20, Color::White());
167  Point d = DrawPlaygroundPoint(point_d);
168  static PlaygroundPoint point_e(Point(50, 50) + offset, 20, Color::White());
169  Point e = DrawPlaygroundPoint(point_e);
170  static PlaygroundPoint point_f(Point(100, 50) + offset, 20, Color::White());
171  Point f = DrawPlaygroundPoint(point_f);
172  static PlaygroundPoint point_g(Point(50, 150) + offset, 20, Color::White());
173  Point g = DrawPlaygroundPoint(point_g);
174  flutter::DlPath path = flutter::DlPathBuilder{}
175  .MoveTo(a)
176  .LineTo(b)
177  .LineTo(c)
178  .LineTo(d)
179  .Close()
180  .MoveTo(e)
181  .LineTo(f)
182  .LineTo(g)
183  .Close()
184  .TakePath();
185 
186  Entity entity;
187  entity.SetTransform(Matrix::MakeScale(GetContentScale()));
188  auto contents = std::make_unique<SolidColorContents>();
189  std::unique_ptr<Geometry> geom =
190  Geometry::MakeStrokePath(path, {.width = 20.0});
191  contents->SetGeometry(geom.get());
192  contents->SetColor(Color::Red());
193  entity.SetContents(std::move(contents));
194 
195  return entity.Render(context, pass);
196  };
197  ASSERT_TRUE(OpenPlaygroundHere(callback));
198 }
199 
200 TEST_P(EntityTest, StrokeCapAndJoinTest) {
201  const Point padding(300, 250);
202  const Point margin(140, 180);
203 
204  auto callback = [&](ContentContext& context, RenderPass& pass) {
205  // Slightly above sqrt(2) by default, so that right angles are just below
206  // the limit and acute angles are over the limit (causing them to get
207  // beveled).
208  static Scalar miter_limit = 1.41421357;
209  static Scalar width = 30;
210 
211  ImGui::Begin("Controls", nullptr, ImGuiWindowFlags_AlwaysAutoResize);
212  {
213  ImGui::SliderFloat("Miter limit", &miter_limit, 0, 30);
214  ImGui::SliderFloat("Stroke width", &width, 0, 100);
215  if (ImGui::Button("Reset")) {
216  miter_limit = 1.41421357;
217  width = 30;
218  }
219  }
220  ImGui::End();
221 
222  auto world_matrix = Matrix::MakeScale(GetContentScale());
223  auto render_path = [width = width, &context, &pass, &world_matrix](
224  const flutter::DlPath& path, Cap cap, Join join) {
225  auto contents = std::make_unique<SolidColorContents>();
226  std::unique_ptr<Geometry> geom =
228  .width = width,
229  .cap = cap,
230  .join = join,
231  .miter_limit = miter_limit,
232  });
233  contents->SetGeometry(geom.get());
234  contents->SetColor(Color::Red());
235 
236  Entity entity;
237  entity.SetTransform(world_matrix);
238  entity.SetContents(std::move(contents));
239 
240  auto coverage = entity.GetCoverage();
241  if (coverage.has_value()) {
242  auto bounds_contents = std::make_unique<SolidColorContents>();
243 
244  std::unique_ptr<Geometry> geom = Geometry::MakeFillPath(
245  flutter::DlPath::MakeRect(entity.GetCoverage().value()));
246 
247  bounds_contents->SetGeometry(geom.get());
248  bounds_contents->SetColor(Color::Green().WithAlpha(0.5));
249  Entity bounds_entity;
250  bounds_entity.SetContents(std::move(bounds_contents));
251  bounds_entity.Render(context, pass);
252  }
253 
254  entity.Render(context, pass);
255  };
256 
257  const Point a_def(0, 0), b_def(0, 100), c_def(150, 0), d_def(150, -100),
258  e_def(75, 75);
259  const Scalar r = 30;
260  // Cap::kButt demo.
261  {
262  Point off = Point(0, 0) * padding + margin;
263  static PlaygroundPoint point_a(off + a_def, r, Color::Black());
264  static PlaygroundPoint point_b(off + b_def, r, Color::White());
265  auto [a, b] = DrawPlaygroundLine(point_a, point_b);
266  static PlaygroundPoint point_c(off + c_def, r, Color::Black());
267  static PlaygroundPoint point_d(off + d_def, r, Color::White());
268  auto [c, d] = DrawPlaygroundLine(point_c, point_d);
269  render_path(flutter::DlPathBuilder{} //
270  .MoveTo(a)
271  .CubicCurveTo(b, d, c)
272  .TakePath(),
274  }
275 
276  // Cap::kSquare demo.
277  {
278  Point off = Point(1, 0) * padding + margin;
279  static PlaygroundPoint point_a(off + a_def, r, Color::Black());
280  static PlaygroundPoint point_b(off + b_def, r, Color::White());
281  auto [a, b] = DrawPlaygroundLine(point_a, point_b);
282  static PlaygroundPoint point_c(off + c_def, r, Color::Black());
283  static PlaygroundPoint point_d(off + d_def, r, Color::White());
284  auto [c, d] = DrawPlaygroundLine(point_c, point_d);
285  render_path(flutter::DlPathBuilder{} //
286  .MoveTo(a)
287  .CubicCurveTo(b, d, c)
288  .TakePath(),
290  }
291 
292  // Cap::kRound demo.
293  {
294  Point off = Point(2, 0) * padding + margin;
295  static PlaygroundPoint point_a(off + a_def, r, Color::Black());
296  static PlaygroundPoint point_b(off + b_def, r, Color::White());
297  auto [a, b] = DrawPlaygroundLine(point_a, point_b);
298  static PlaygroundPoint point_c(off + c_def, r, Color::Black());
299  static PlaygroundPoint point_d(off + d_def, r, Color::White());
300  auto [c, d] = DrawPlaygroundLine(point_c, point_d);
301  render_path(flutter::DlPathBuilder{} //
302  .MoveTo(a)
303  .CubicCurveTo(b, d, c)
304  .TakePath(),
306  }
307 
308  // Join::kBevel demo.
309  {
310  Point off = Point(0, 1) * padding + margin;
311  static PlaygroundPoint point_a =
312  PlaygroundPoint(off + a_def, r, Color::White());
313  static PlaygroundPoint point_b =
314  PlaygroundPoint(off + e_def, r, Color::White());
315  static PlaygroundPoint point_c =
316  PlaygroundPoint(off + c_def, r, Color::White());
317  Point a = DrawPlaygroundPoint(point_a);
318  Point b = DrawPlaygroundPoint(point_b);
319  Point c = DrawPlaygroundPoint(point_c);
320  render_path(flutter::DlPathBuilder{} //
321  .MoveTo(a)
322  .LineTo(b)
323  .LineTo(c)
324  .Close()
325  .TakePath(),
327  }
328 
329  // Join::kMiter demo.
330  {
331  Point off = Point(1, 1) * padding + margin;
332  static PlaygroundPoint point_a(off + a_def, r, Color::White());
333  static PlaygroundPoint point_b(off + e_def, r, Color::White());
334  static PlaygroundPoint point_c(off + c_def, r, Color::White());
335  Point a = DrawPlaygroundPoint(point_a);
336  Point b = DrawPlaygroundPoint(point_b);
337  Point c = DrawPlaygroundPoint(point_c);
338  render_path(flutter::DlPathBuilder{} //
339  .MoveTo(a)
340  .LineTo(b)
341  .LineTo(c)
342  .Close()
343  .TakePath(),
345  }
346 
347  // Join::kRound demo.
348  {
349  Point off = Point(2, 1) * padding + margin;
350  static PlaygroundPoint point_a(off + a_def, r, Color::White());
351  static PlaygroundPoint point_b(off + e_def, r, Color::White());
352  static PlaygroundPoint point_c(off + c_def, r, Color::White());
353  Point a = DrawPlaygroundPoint(point_a);
354  Point b = DrawPlaygroundPoint(point_b);
355  Point c = DrawPlaygroundPoint(point_c);
356  render_path(flutter::DlPathBuilder{} //
357  .MoveTo(a)
358  .LineTo(b)
359  .LineTo(c)
360  .Close()
361  .TakePath(),
363  }
364 
365  return true;
366  };
367  ASSERT_TRUE(OpenPlaygroundHere(callback));
368 }
369 
370 TEST_P(EntityTest, CubicCurveTest) {
371  // Compare with https://fiddle.skia.org/c/b3625f26122c9de7afe7794fcf25ead3
372  flutter::DlPath path =
373  flutter::DlPathBuilder{}
374  .MoveTo({237.164, 125.003})
375  .CubicCurveTo({236.709, 125.184}, {236.262, 125.358},
376  {235.81, 125.538})
377  .CubicCurveTo({235.413, 125.68}, {234.994, 125.832},
378  {234.592, 125.977})
379  .CubicCurveTo({234.592, 125.977}, {234.591, 125.977},
380  {234.59, 125.977})
381  .CubicCurveTo({222.206, 130.435}, {207.708, 135.753},
382  {192.381, 141.429})
383  .CubicCurveTo({162.77, 151.336}, {122.17, 156.894}, {84.1123, 160})
384  .Close()
385  .TakePath();
386  Entity entity;
387  entity.SetTransform(Matrix::MakeScale(GetContentScale()));
388 
389  std::unique_ptr<Geometry> geom = Geometry::MakeFillPath(path);
390 
391  auto contents = std::make_shared<SolidColorContents>();
392  contents->SetColor(Color::Red());
393  contents->SetGeometry(geom.get());
394 
395  entity.SetContents(contents);
396  ASSERT_TRUE(OpenPlaygroundHere(std::move(entity)));
397 }
398 
399 TEST_P(EntityTest, CanDrawCorrectlyWithRotatedTransform) {
400  auto callback = [&](ContentContext& context, RenderPass& pass) -> bool {
401  const char* input_axis[] = {"X", "Y", "Z"};
402  static int rotation_axis_index = 0;
403  static float rotation = 0;
404  ImGui::Begin("Controls", nullptr, ImGuiWindowFlags_AlwaysAutoResize);
405  ImGui::SliderFloat("Rotation", &rotation, -kPi, kPi);
406  ImGui::Combo("Rotation Axis", &rotation_axis_index, input_axis,
407  sizeof(input_axis) / sizeof(char*));
408  Matrix rotation_matrix;
409  switch (rotation_axis_index) {
410  case 0:
411  rotation_matrix = Matrix::MakeRotationX(Radians(rotation));
412  break;
413  case 1:
414  rotation_matrix = Matrix::MakeRotationY(Radians(rotation));
415  break;
416  case 2:
417  rotation_matrix = Matrix::MakeRotationZ(Radians(rotation));
418  break;
419  default:
420  rotation_matrix = Matrix{};
421  break;
422  }
423 
424  if (ImGui::Button("Reset")) {
425  rotation = 0;
426  }
427  ImGui::End();
428  Matrix current_transform =
429  Matrix::MakeScale(GetContentScale())
431  Vector3(Point(pass.GetRenderTargetSize().width / 2.0,
432  pass.GetRenderTargetSize().height / 2.0)));
433  Matrix result_transform = current_transform * rotation_matrix;
434  flutter::DlPath path =
435  flutter::DlPath::MakeRect(Rect::MakeXYWH(-300, -400, 600, 800));
436 
437  Entity entity;
438  entity.SetTransform(result_transform);
439 
440  std::unique_ptr<Geometry> geom = Geometry::MakeFillPath(path);
441 
442  auto contents = std::make_shared<SolidColorContents>();
443  contents->SetColor(Color::Red());
444  contents->SetGeometry(geom.get());
445 
446  entity.SetContents(contents);
447  return entity.Render(context, pass);
448  };
449  ASSERT_TRUE(OpenPlaygroundHere(callback));
450 }
451 
452 TEST_P(EntityTest, CubicCurveAndOverlapTest) {
453  // Compare with https://fiddle.skia.org/c/7a05a3e186c65a8dfb732f68020aae06
454  flutter::DlPath path =
455  flutter::DlPathBuilder{}
456  .MoveTo({359.934, 96.6335})
457  .CubicCurveTo({358.189, 96.7055}, {356.436, 96.7908},
458  {354.673, 96.8895})
459  .CubicCurveTo({354.571, 96.8953}, {354.469, 96.9016},
460  {354.367, 96.9075})
461  .CubicCurveTo({352.672, 97.0038}, {350.969, 97.113},
462  {349.259, 97.2355})
463  .CubicCurveTo({349.048, 97.2506}, {348.836, 97.2678},
464  {348.625, 97.2834})
465  .CubicCurveTo({347.019, 97.4014}, {345.407, 97.5299},
466  {343.789, 97.6722})
467  .CubicCurveTo({343.428, 97.704}, {343.065, 97.7402},
468  {342.703, 97.7734})
469  .CubicCurveTo({341.221, 97.9086}, {339.736, 98.0505},
470  {338.246, 98.207})
471  .CubicCurveTo({337.702, 98.2642}, {337.156, 98.3292},
472  {336.612, 98.3894})
473  .CubicCurveTo({335.284, 98.5356}, {333.956, 98.6837},
474  {332.623, 98.8476})
475  .CubicCurveTo({332.495, 98.8635}, {332.366, 98.8818},
476  {332.237, 98.8982})
477  .LineTo({332.237, 102.601})
478  .LineTo({321.778, 102.601})
479  .LineTo({321.778, 100.382})
480  .CubicCurveTo({321.572, 100.413}, {321.367, 100.442},
481  {321.161, 100.476})
482  .CubicCurveTo({319.22, 100.79}, {317.277, 101.123},
483  {315.332, 101.479})
484  .CubicCurveTo({315.322, 101.481}, {315.311, 101.482},
485  {315.301, 101.484})
486  .LineTo({310.017, 105.94})
487  .LineTo({309.779, 105.427})
488  .LineTo({314.403, 101.651})
489  .CubicCurveTo({314.391, 101.653}, {314.379, 101.656},
490  {314.368, 101.658})
491  .CubicCurveTo({312.528, 102.001}, {310.687, 102.366},
492  {308.846, 102.748})
493  .CubicCurveTo({307.85, 102.955}, {306.855, 103.182}, {305.859, 103.4})
494  .CubicCurveTo({305.048, 103.579}, {304.236, 103.75},
495  {303.425, 103.936})
496  .LineTo({299.105, 107.578})
497  .LineTo({298.867, 107.065})
498  .LineTo({302.394, 104.185})
499  .LineTo({302.412, 104.171})
500  .CubicCurveTo({301.388, 104.409}, {300.366, 104.67},
501  {299.344, 104.921})
502  .CubicCurveTo({298.618, 105.1}, {297.89, 105.269}, {297.165, 105.455})
503  .CubicCurveTo({295.262, 105.94}, {293.36, 106.445},
504  {291.462, 106.979})
505  .CubicCurveTo({291.132, 107.072}, {290.802, 107.163},
506  {290.471, 107.257})
507  .CubicCurveTo({289.463, 107.544}, {288.455, 107.839},
508  {287.449, 108.139})
509  .CubicCurveTo({286.476, 108.431}, {285.506, 108.73},
510  {284.536, 109.035})
511  .CubicCurveTo({283.674, 109.304}, {282.812, 109.579},
512  {281.952, 109.859})
513  .CubicCurveTo({281.177, 110.112}, {280.406, 110.377},
514  {279.633, 110.638})
515  .CubicCurveTo({278.458, 111.037}, {277.256, 111.449},
516  {276.803, 111.607})
517  .CubicCurveTo({276.76, 111.622}, {276.716, 111.637},
518  {276.672, 111.653})
519  .CubicCurveTo({275.017, 112.239}, {273.365, 112.836},
520  {271.721, 113.463})
521  .LineTo({271.717, 113.449})
522  .CubicCurveTo({271.496, 113.496}, {271.238, 113.559},
523  {270.963, 113.628})
524  .CubicCurveTo({270.893, 113.645}, {270.822, 113.663},
525  {270.748, 113.682})
526  .CubicCurveTo({270.468, 113.755}, {270.169, 113.834},
527  {269.839, 113.926})
528  .CubicCurveTo({269.789, 113.94}, {269.732, 113.957},
529  {269.681, 113.972})
530  .CubicCurveTo({269.391, 114.053}, {269.081, 114.143},
531  {268.756, 114.239})
532  .CubicCurveTo({268.628, 114.276}, {268.5, 114.314},
533  {268.367, 114.354})
534  .CubicCurveTo({268.172, 114.412}, {267.959, 114.478},
535  {267.752, 114.54})
536  .CubicCurveTo({263.349, 115.964}, {258.058, 117.695},
537  {253.564, 119.252})
538  .CubicCurveTo({253.556, 119.255}, {253.547, 119.258},
539  {253.538, 119.261})
540  .CubicCurveTo({251.844, 119.849}, {250.056, 120.474},
541  {248.189, 121.131})
542  .CubicCurveTo({248, 121.197}, {247.812, 121.264}, {247.621, 121.331})
543  .CubicCurveTo({247.079, 121.522}, {246.531, 121.715},
544  {245.975, 121.912})
545  .CubicCurveTo({245.554, 122.06}, {245.126, 122.212},
546  {244.698, 122.364})
547  .CubicCurveTo({244.071, 122.586}, {243.437, 122.811},
548  {242.794, 123.04})
549  .CubicCurveTo({242.189, 123.255}, {241.58, 123.472},
550  {240.961, 123.693})
551  .CubicCurveTo({240.659, 123.801}, {240.357, 123.909},
552  {240.052, 124.018})
553  .CubicCurveTo({239.12, 124.351}, {238.18, 124.687}, {237.22, 125.032})
554  .LineTo({237.164, 125.003})
555  .CubicCurveTo({236.709, 125.184}, {236.262, 125.358},
556  {235.81, 125.538})
557  .CubicCurveTo({235.413, 125.68}, {234.994, 125.832},
558  {234.592, 125.977})
559  .CubicCurveTo({234.592, 125.977}, {234.591, 125.977},
560  {234.59, 125.977})
561  .CubicCurveTo({222.206, 130.435}, {207.708, 135.753},
562  {192.381, 141.429})
563  .CubicCurveTo({162.77, 151.336}, {122.17, 156.894}, {84.1123, 160})
564  .LineTo({360, 160})
565  .LineTo({360, 119.256})
566  .LineTo({360, 106.332})
567  .LineTo({360, 96.6307})
568  .CubicCurveTo({359.978, 96.6317}, {359.956, 96.6326},
569  {359.934, 96.6335})
570  .Close()
571  .MoveTo({337.336, 124.143})
572  .CubicCurveTo({337.274, 122.359}, {338.903, 121.511},
573  {338.903, 121.511})
574  .CubicCurveTo({338.903, 121.511}, {338.96, 123.303},
575  {337.336, 124.143})
576  .Close()
577  .MoveTo({340.082, 121.849})
578  .CubicCurveTo({340.074, 121.917}, {340.062, 121.992},
579  {340.046, 122.075})
580  .CubicCurveTo({340.039, 122.109}, {340.031, 122.142},
581  {340.023, 122.177})
582  .CubicCurveTo({340.005, 122.26}, {339.98, 122.346},
583  {339.952, 122.437})
584  .CubicCurveTo({339.941, 122.473}, {339.931, 122.507},
585  {339.918, 122.544})
586  .CubicCurveTo({339.873, 122.672}, {339.819, 122.804},
587  {339.75, 122.938})
588  .CubicCurveTo({339.747, 122.944}, {339.743, 122.949},
589  {339.74, 122.955})
590  .CubicCurveTo({339.674, 123.08}, {339.593, 123.205},
591  {339.501, 123.328})
592  .CubicCurveTo({339.473, 123.366}, {339.441, 123.401},
593  {339.41, 123.438})
594  .CubicCurveTo({339.332, 123.534}, {339.243, 123.625},
595  {339.145, 123.714})
596  .CubicCurveTo({339.105, 123.75}, {339.068, 123.786},
597  {339.025, 123.821})
598  .CubicCurveTo({338.881, 123.937}, {338.724, 124.048},
599  {338.539, 124.143})
600  .CubicCurveTo({338.532, 123.959}, {338.554, 123.79},
601  {338.58, 123.626})
602  .CubicCurveTo({338.58, 123.625}, {338.58, 123.625}, {338.58, 123.625})
603  .CubicCurveTo({338.607, 123.455}, {338.65, 123.299},
604  {338.704, 123.151})
605  .CubicCurveTo({338.708, 123.14}, {338.71, 123.127},
606  {338.714, 123.117})
607  .CubicCurveTo({338.769, 122.971}, {338.833, 122.838},
608  {338.905, 122.712})
609  .CubicCurveTo({338.911, 122.702}, {338.916, 122.69200000000001},
610  {338.922, 122.682})
611  .CubicCurveTo({338.996, 122.557}, {339.072, 122.444},
612  {339.155, 122.34})
613  .CubicCurveTo({339.161, 122.333}, {339.166, 122.326},
614  {339.172, 122.319})
615  .CubicCurveTo({339.256, 122.215}, {339.339, 122.12},
616  {339.425, 122.037})
617  .CubicCurveTo({339.428, 122.033}, {339.431, 122.03},
618  {339.435, 122.027})
619  .CubicCurveTo({339.785, 121.687}, {340.106, 121.511},
620  {340.106, 121.511})
621  .CubicCurveTo({340.106, 121.511}, {340.107, 121.645},
622  {340.082, 121.849})
623  .Close()
624  .MoveTo({340.678, 113.245})
625  .CubicCurveTo({340.594, 113.488}, {340.356, 113.655},
626  {340.135, 113.775})
627  .CubicCurveTo({339.817, 113.948}, {339.465, 114.059},
628  {339.115, 114.151})
629  .CubicCurveTo({338.251, 114.379}, {337.34, 114.516},
630  {336.448, 114.516})
631  .CubicCurveTo({335.761, 114.516}, {335.072, 114.527},
632  {334.384, 114.513})
633  .CubicCurveTo({334.125, 114.508}, {333.862, 114.462},
634  {333.605, 114.424})
635  .CubicCurveTo({332.865, 114.318}, {332.096, 114.184},
636  {331.41, 113.883})
637  .CubicCurveTo({330.979, 113.695}, {330.442, 113.34},
638  {330.672, 112.813})
639  .CubicCurveTo({331.135, 111.755}, {333.219, 112.946},
640  {334.526, 113.833})
641  .CubicCurveTo({334.54, 113.816}, {334.554, 113.8}, {334.569, 113.784})
642  .CubicCurveTo({333.38, 112.708}, {331.749, 110.985},
643  {332.76, 110.402})
644  .CubicCurveTo({333.769, 109.82}, {334.713, 111.93},
645  {335.228, 113.395})
646  .CubicCurveTo({334.915, 111.889}, {334.59, 109.636},
647  {335.661, 109.592})
648  .CubicCurveTo({336.733, 109.636}, {336.408, 111.889},
649  {336.07, 113.389})
650  .CubicCurveTo({336.609, 111.93}, {337.553, 109.82},
651  {338.563, 110.402})
652  .CubicCurveTo({339.574, 110.984}, {337.942, 112.708},
653  {336.753, 113.784})
654  .CubicCurveTo({336.768, 113.8}, {336.782, 113.816},
655  {336.796, 113.833})
656  .CubicCurveTo({338.104, 112.946}, {340.187, 111.755},
657  {340.65, 112.813})
658  .CubicCurveTo({340.71, 112.95}, {340.728, 113.102},
659  {340.678, 113.245})
660  .Close()
661  .MoveTo({346.357, 106.771})
662  .CubicCurveTo({346.295, 104.987}, {347.924, 104.139},
663  {347.924, 104.139})
664  .CubicCurveTo({347.924, 104.139}, {347.982, 105.931},
665  {346.357, 106.771})
666  .Close()
667  .MoveTo({347.56, 106.771})
668  .CubicCurveTo({347.498, 104.987}, {349.127, 104.139},
669  {349.127, 104.139})
670  .CubicCurveTo({349.127, 104.139}, {349.185, 105.931},
671  {347.56, 106.771})
672  .Close()
673  .TakePath();
674  Entity entity;
675  entity.SetTransform(Matrix::MakeScale(GetContentScale()));
676 
677  std::unique_ptr<Geometry> geom = Geometry::MakeFillPath(path);
678 
679  auto contents = std::make_shared<SolidColorContents>();
680  contents->SetColor(Color::Red());
681  contents->SetGeometry(geom.get());
682 
683  entity.SetContents(contents);
684  ASSERT_TRUE(OpenPlaygroundHere(std::move(entity)));
685 }
686 
687 TEST_P(EntityTest, SolidColorContentsStrokeSetStrokeCapsAndJoins) {
688  {
689  auto geometry = Geometry::MakeStrokePath(flutter::DlPath{});
690  auto path_geometry = static_cast<StrokePathGeometry*>(geometry.get());
691  // Defaults.
692  ASSERT_EQ(path_geometry->GetStrokeCap(), Cap::kButt);
693  ASSERT_EQ(path_geometry->GetStrokeJoin(), Join::kMiter);
694  }
695 
696  {
697  auto geometry = Geometry::MakeStrokePath(flutter::DlPath{}, //
698  {
699  .width = 1.0f,
700  .cap = Cap::kSquare,
701  .miter_limit = 4.0f,
702  });
703  auto path_geometry = static_cast<StrokePathGeometry*>(geometry.get());
704  ASSERT_EQ(path_geometry->GetStrokeCap(), Cap::kSquare);
705  }
706 
707  {
708  auto geometry = Geometry::MakeStrokePath(flutter::DlPath{}, //
709  {
710  .width = 1.0f,
711  .cap = Cap::kRound,
712  .miter_limit = 4.0f,
713  });
714  auto path_geometry = static_cast<StrokePathGeometry*>(geometry.get());
715  ASSERT_EQ(path_geometry->GetStrokeCap(), Cap::kRound);
716  }
717 }
718 
719 TEST_P(EntityTest, SolidColorContentsStrokeSetMiterLimit) {
720  {
721  auto geometry = Geometry::MakeStrokePath(flutter::DlPath{});
722  auto path_geometry = static_cast<StrokePathGeometry*>(geometry.get());
723  ASSERT_FLOAT_EQ(path_geometry->GetMiterLimit(), 4);
724  }
725 
726  {
727  auto geometry = Geometry::MakeStrokePath(flutter::DlPath{}, //
728  {
729  .width = 1.0f,
730  .miter_limit = 8.0f,
731  });
732  auto path_geometry = static_cast<StrokePathGeometry*>(geometry.get());
733  ASSERT_FLOAT_EQ(path_geometry->GetMiterLimit(), 8);
734  }
735 
736  {
737  auto geometry = Geometry::MakeStrokePath(flutter::DlPath{}, //
738  {
739  .width = 1.0f,
740  .miter_limit = -1.0f,
741  });
742  auto path_geometry = static_cast<StrokePathGeometry*>(geometry.get());
743  ASSERT_FLOAT_EQ(path_geometry->GetMiterLimit(), 4);
744  }
745 }
746 
747 TEST_P(EntityTest, BlendingModeOptions) {
748  std::vector<const char*> blend_mode_names;
749  std::vector<BlendMode> blend_mode_values;
750  {
751  // Force an exhausiveness check with a switch. When adding blend modes,
752  // update this switch with a new name/value to make it selectable in the
753  // test GUI.
754 
755  const BlendMode b{};
756  static_assert(b == BlendMode::kClear); // Ensure the first item in
757  // the switch is the first
758  // item in the enum.
760  switch (b) {
761  case BlendMode::kClear:
762  blend_mode_names.push_back("Clear");
763  blend_mode_values.push_back(BlendMode::kClear);
764  case BlendMode::kSrc:
765  blend_mode_names.push_back("Source");
766  blend_mode_values.push_back(BlendMode::kSrc);
767  case BlendMode::kDst:
768  blend_mode_names.push_back("Destination");
769  blend_mode_values.push_back(BlendMode::kDst);
770  case BlendMode::kSrcOver:
771  blend_mode_names.push_back("SourceOver");
772  blend_mode_values.push_back(BlendMode::kSrcOver);
773  case BlendMode::kDstOver:
774  blend_mode_names.push_back("DestinationOver");
775  blend_mode_values.push_back(BlendMode::kDstOver);
776  case BlendMode::kSrcIn:
777  blend_mode_names.push_back("SourceIn");
778  blend_mode_values.push_back(BlendMode::kSrcIn);
779  case BlendMode::kDstIn:
780  blend_mode_names.push_back("DestinationIn");
781  blend_mode_values.push_back(BlendMode::kDstIn);
782  case BlendMode::kSrcOut:
783  blend_mode_names.push_back("SourceOut");
784  blend_mode_values.push_back(BlendMode::kSrcOut);
785  case BlendMode::kDstOut:
786  blend_mode_names.push_back("DestinationOut");
787  blend_mode_values.push_back(BlendMode::kDstOut);
788  case BlendMode::kSrcATop:
789  blend_mode_names.push_back("SourceATop");
790  blend_mode_values.push_back(BlendMode::kSrcATop);
791  case BlendMode::kDstATop:
792  blend_mode_names.push_back("DestinationATop");
793  blend_mode_values.push_back(BlendMode::kDstATop);
794  case BlendMode::kXor:
795  blend_mode_names.push_back("Xor");
796  blend_mode_values.push_back(BlendMode::kXor);
797  case BlendMode::kPlus:
798  blend_mode_names.push_back("Plus");
799  blend_mode_values.push_back(BlendMode::kPlus);
801  blend_mode_names.push_back("Modulate");
802  blend_mode_values.push_back(BlendMode::kModulate);
803  };
804  }
805 
806  auto callback = [&](ContentContext& context, RenderPass& pass) {
807  auto world_matrix = Matrix::MakeScale(GetContentScale());
808  auto draw_rect = [&context, &pass, &world_matrix](
809  Rect rect, Color color, BlendMode blend_mode) -> bool {
812 
814  {
815  auto r = rect.GetLTRB();
816  vtx_builder.AddVertices({
817  {Point(r[0], r[1])},
818  {Point(r[2], r[1])},
819  {Point(r[2], r[3])},
820  {Point(r[0], r[1])},
821  {Point(r[2], r[3])},
822  {Point(r[0], r[3])},
823  });
824  }
825 
826  pass.SetCommandLabel("Blended Rectangle");
827  auto options = OptionsFromPass(pass);
828  options.blend_mode = blend_mode;
829  options.primitive_type = PrimitiveType::kTriangle;
830  pass.SetPipeline(context.GetSolidFillPipeline(options));
831  pass.SetVertexBuffer(
832  vtx_builder.CreateVertexBuffer(context.GetTransientsDataBuffer(),
833  context.GetTransientsIndexesBuffer()));
834 
835  VS::FrameInfo frame_info;
836  frame_info.mvp = pass.GetOrthographicTransform() * world_matrix;
837  VS::BindFrameInfo(
838  pass, context.GetTransientsDataBuffer().EmplaceUniform(frame_info));
839  FS::FragInfo frag_info;
840  frag_info.color = color.Premultiply();
841  FS::BindFragInfo(
842  pass, context.GetTransientsDataBuffer().EmplaceUniform(frag_info));
843  return pass.Draw().ok();
844  };
845 
846  ImGui::Begin("Controls", nullptr, ImGuiWindowFlags_AlwaysAutoResize);
847  static Color color1(1, 0, 0, 0.5), color2(0, 1, 0, 0.5);
848  ImGui::ColorEdit4("Color 1", reinterpret_cast<float*>(&color1));
849  ImGui::ColorEdit4("Color 2", reinterpret_cast<float*>(&color2));
850  static int current_blend_index = 3;
851  ImGui::ListBox("Blending mode", &current_blend_index,
852  blend_mode_names.data(), blend_mode_names.size());
853  ImGui::End();
854 
855  BlendMode selected_mode = blend_mode_values[current_blend_index];
856 
857  Point a, b, c, d;
858  static PlaygroundPoint point_a(Point(400, 100), 20, Color::White());
859  static PlaygroundPoint point_b(Point(200, 300), 20, Color::White());
860  std::tie(a, b) = DrawPlaygroundLine(point_a, point_b);
861  static PlaygroundPoint point_c(Point(470, 190), 20, Color::White());
862  static PlaygroundPoint point_d(Point(270, 390), 20, Color::White());
863  std::tie(c, d) = DrawPlaygroundLine(point_c, point_d);
864 
865  bool result = true;
866  result = result &&
867  draw_rect(Rect::MakeXYWH(0, 0, pass.GetRenderTargetSize().width,
868  pass.GetRenderTargetSize().height),
870  result = result && draw_rect(Rect::MakeLTRB(a.x, a.y, b.x, b.y), color1,
872  result = result && draw_rect(Rect::MakeLTRB(c.x, c.y, d.x, d.y), color2,
873  selected_mode);
874  return result;
875  };
876  ASSERT_TRUE(OpenPlaygroundHere(callback));
877 }
878 
879 TEST_P(EntityTest, BezierCircleScaled) {
880  auto callback = [&](ContentContext& context, RenderPass& pass) -> bool {
881  static float scale = 20;
882 
883  ImGui::Begin("Controls", nullptr, ImGuiWindowFlags_AlwaysAutoResize);
884  ImGui::SliderFloat("Scale", &scale, 1, 100);
885  ImGui::End();
886 
887  Entity entity;
888  entity.SetTransform(Matrix::MakeScale(GetContentScale()));
889  auto path = flutter::DlPathBuilder{}
890  .MoveTo({97.325, 34.818})
891  .CubicCurveTo({98.50862885295136, 34.81812293973836},
892  {99.46822048142015, 33.85863261475589},
893  {99.46822048142015, 32.67499810206613})
894  .CubicCurveTo({99.46822048142015, 31.491363589376355},
895  {98.50862885295136, 30.53187326439389},
896  {97.32499434685802, 30.531998226542708})
897  .CubicCurveTo({96.14153655073771, 30.532123170035373},
898  {95.18222070648729, 31.491540299350355},
899  {95.18222070648729, 32.67499810206613})
900  .CubicCurveTo({95.18222070648729, 33.85845590478189},
901  {96.14153655073771, 34.81787303409686},
902  {97.32499434685802, 34.81799797758954})
903  .Close()
904  .TakePath();
905  entity.SetTransform(
906  Matrix::MakeScale({scale, scale, 1.0}).Translate({-90, -20, 0}));
907 
908  std::unique_ptr<Geometry> geom = Geometry::MakeFillPath(path);
909 
910  auto contents = std::make_shared<SolidColorContents>();
911  contents->SetColor(Color::Red());
912  contents->SetGeometry(geom.get());
913 
914  entity.SetContents(contents);
915  return entity.Render(context, pass);
916  };
917  ASSERT_TRUE(OpenPlaygroundHere(callback));
918 }
919 
920 TEST_P(EntityTest, Filters) {
921  auto bridge = CreateTextureForFixture("bay_bridge.jpg");
922  auto boston = CreateTextureForFixture("boston.jpg");
923  auto kalimba = CreateTextureForFixture("kalimba.jpg");
924  ASSERT_TRUE(bridge && boston && kalimba);
925 
926  auto callback = [&](ContentContext& context, RenderPass& pass) -> bool {
927  auto fi_bridge = FilterInput::Make(bridge);
928  auto fi_boston = FilterInput::Make(boston);
929  auto fi_kalimba = FilterInput::Make(kalimba);
930 
931  std::shared_ptr<FilterContents> blend0 = ColorFilterContents::MakeBlend(
932  BlendMode::kModulate, {fi_kalimba, fi_boston});
933 
934  auto blend1 = ColorFilterContents::MakeBlend(
936  {FilterInput::Make(blend0), fi_bridge, fi_bridge, fi_bridge});
937 
938  Entity entity;
939  entity.SetTransform(Matrix::MakeScale(GetContentScale()) *
940  Matrix::MakeTranslation({500, 300}) *
941  Matrix::MakeScale(Vector2{0.5, 0.5}));
942  entity.SetContents(blend1);
943  return entity.Render(context, pass);
944  };
945  ASSERT_TRUE(OpenPlaygroundHere(callback));
946 }
947 
948 TEST_P(EntityTest, GaussianBlurFilter) {
949  auto boston =
950  CreateTextureForFixture("boston.jpg", /*enable_mipmapping=*/true);
951  ASSERT_TRUE(boston);
952 
953  auto callback = [&](ContentContext& context, RenderPass& pass) -> bool {
954  const char* input_type_names[] = {"Texture", "Solid Color"};
955  const char* blur_type_names[] = {"Image blur", "Mask blur"};
956  const char* pass_variation_names[] = {"New"};
957  const char* blur_style_names[] = {"Normal", "Solid", "Outer", "Inner"};
958  const char* tile_mode_names[] = {"Clamp", "Repeat", "Mirror", "Decal"};
959  const FilterContents::BlurStyle blur_styles[] = {
962  const Entity::TileMode tile_modes[] = {
965 
966  // UI state.
967  static int selected_input_type = 0;
968  static Color input_color = Color::Black();
969  static int selected_blur_type = 0;
970  static int selected_pass_variation = 0;
971  static bool combined_sigma = false;
972  static float blur_amount_coarse[2] = {0, 0};
973  static float blur_amount_fine[2] = {10, 10};
974  static int selected_blur_style = 0;
975  static int selected_tile_mode = 3;
976  static Color cover_color(1, 0, 0, 0.2);
977  static Color bounds_color(0, 1, 0, 0.1);
978  static float offset[2] = {500, 400};
979  static float rotation = 0;
980  static float scale[2] = {0.65, 0.65};
981  static float skew[2] = {0, 0};
982  static float path_rect[4] = {0, 0,
983  static_cast<float>(boston->GetSize().width),
984  static_cast<float>(boston->GetSize().height)};
985 
986  ImGui::Begin("Controls", nullptr, ImGuiWindowFlags_AlwaysAutoResize);
987  {
988  ImGui::Combo("Input type", &selected_input_type, input_type_names,
989  sizeof(input_type_names) / sizeof(char*));
990  if (selected_input_type == 0) {
991  ImGui::SliderFloat("Input opacity", &input_color.alpha, 0, 1);
992  } else {
993  ImGui::ColorEdit4("Input color",
994  reinterpret_cast<float*>(&input_color));
995  }
996  ImGui::Combo("Blur type", &selected_blur_type, blur_type_names,
997  sizeof(blur_type_names) / sizeof(char*));
998  if (selected_blur_type == 0) {
999  ImGui::Combo("Pass variation", &selected_pass_variation,
1000  pass_variation_names,
1001  sizeof(pass_variation_names) / sizeof(char*));
1002  }
1003  ImGui::Checkbox("Combined sigma", &combined_sigma);
1004  if (combined_sigma) {
1005  ImGui::SliderFloat("Sigma (coarse)", blur_amount_coarse, 0, 1000);
1006  ImGui::SliderFloat("Sigma (fine)", blur_amount_fine, 0, 10);
1007  blur_amount_coarse[1] = blur_amount_coarse[0];
1008  blur_amount_fine[1] = blur_amount_fine[0];
1009  } else {
1010  ImGui::SliderFloat2("Sigma (coarse)", blur_amount_coarse, 0, 1000);
1011  ImGui::SliderFloat2("Sigma (fine)", blur_amount_fine, 0, 10);
1012  }
1013  ImGui::Combo("Blur style", &selected_blur_style, blur_style_names,
1014  sizeof(blur_style_names) / sizeof(char*));
1015  ImGui::Combo("Tile mode", &selected_tile_mode, tile_mode_names,
1016  sizeof(tile_mode_names) / sizeof(char*));
1017  ImGui::ColorEdit4("Cover color", reinterpret_cast<float*>(&cover_color));
1018  ImGui::ColorEdit4("Bounds color ",
1019  reinterpret_cast<float*>(&bounds_color));
1020  ImGui::SliderFloat2("Translation", offset, 0,
1021  pass.GetRenderTargetSize().width);
1022  ImGui::SliderFloat("Rotation", &rotation, 0, kPi * 2);
1023  ImGui::SliderFloat2("Scale", scale, 0, 3);
1024  ImGui::SliderFloat2("Skew", skew, -3, 3);
1025  ImGui::SliderFloat4("Path XYWH", path_rect, -1000, 1000);
1026  }
1027  ImGui::End();
1028 
1029  auto blur_sigma_x = Sigma{blur_amount_coarse[0] + blur_amount_fine[0]};
1030  auto blur_sigma_y = Sigma{blur_amount_coarse[1] + blur_amount_fine[1]};
1031 
1032  std::shared_ptr<Contents> input;
1033  Size input_size;
1034 
1035  auto input_rect =
1036  Rect::MakeXYWH(path_rect[0], path_rect[1], path_rect[2], path_rect[3]);
1037 
1038  std::unique_ptr<Geometry> solid_color_input;
1039  if (selected_input_type == 0) {
1040  auto texture = std::make_shared<TextureContents>();
1041  texture->SetSourceRect(Rect::MakeSize(boston->GetSize()));
1042  texture->SetDestinationRect(input_rect);
1043  texture->SetTexture(boston);
1044  texture->SetOpacity(input_color.alpha);
1045 
1046  input = texture;
1047  input_size = input_rect.GetSize();
1048  } else {
1049  auto fill = std::make_shared<SolidColorContents>();
1050  fill->SetColor(input_color);
1051  solid_color_input =
1052  Geometry::MakeFillPath(flutter::DlPath::MakeRect(input_rect));
1053 
1054  fill->SetGeometry(solid_color_input.get());
1055 
1056  input = fill;
1057  input_size = input_rect.GetSize();
1058  }
1059 
1060  std::shared_ptr<FilterContents> blur;
1061  switch (selected_pass_variation) {
1062  case 0:
1063  blur = std::make_shared<GaussianBlurFilterContents>(
1064  blur_sigma_x.sigma, blur_sigma_y.sigma,
1065  tile_modes[selected_tile_mode], blur_styles[selected_blur_style],
1066  /*geometry=*/nullptr);
1067  blur->SetInputs({FilterInput::Make(input)});
1068  break;
1069  case 1:
1071  FilterInput::Make(input), blur_sigma_x, blur_sigma_y,
1072  tile_modes[selected_tile_mode], blur_styles[selected_blur_style]);
1073  break;
1074  };
1075  FML_CHECK(blur);
1076 
1077  auto mask_blur = FilterContents::MakeBorderMaskBlur(
1078  FilterInput::Make(input), blur_sigma_x, blur_sigma_y,
1079  blur_styles[selected_blur_style]);
1080 
1081  auto ctm = Matrix::MakeScale(GetContentScale()) *
1082  Matrix::MakeTranslation(Vector3(offset[0], offset[1])) *
1083  Matrix::MakeRotationZ(Radians(rotation)) *
1084  Matrix::MakeScale(Vector2(scale[0], scale[1])) *
1085  Matrix::MakeSkew(skew[0], skew[1]) *
1086  Matrix::MakeTranslation(-Point(input_size) / 2);
1087 
1088  auto target_contents = selected_blur_type == 0 ? blur : mask_blur;
1089 
1090  Entity entity;
1091  entity.SetContents(target_contents);
1092  entity.SetTransform(ctm);
1093 
1094  entity.Render(context, pass);
1095 
1096  // Renders a red "cover" rectangle that shows the original position of the
1097  // unfiltered input.
1098  Entity cover_entity;
1099  std::unique_ptr<Geometry> geom =
1100  Geometry::MakeFillPath(flutter::DlPath::MakeRect(input_rect));
1101  auto contents = std::make_shared<SolidColorContents>();
1102  contents->SetColor(cover_color);
1103  contents->SetGeometry(geom.get());
1104  cover_entity.SetContents(std::move(contents));
1105  cover_entity.SetTransform(ctm);
1106  cover_entity.Render(context, pass);
1107 
1108  // Renders a green bounding rect of the target filter.
1109  Entity bounds_entity;
1110  std::optional<Rect> target_contents_coverage =
1111  target_contents->GetCoverage(entity);
1112  if (target_contents_coverage.has_value()) {
1113  std::unique_ptr<Geometry> geom =
1114  Geometry::MakeFillPath(flutter::DlPath::MakeRect(
1115  target_contents->GetCoverage(entity).value()));
1116  auto contents = std::make_shared<SolidColorContents>();
1117  contents->SetColor(bounds_color);
1118  contents->SetGeometry(geom.get());
1119 
1120  bounds_entity.SetContents(contents);
1121  bounds_entity.SetTransform(Matrix());
1122  bounds_entity.Render(context, pass);
1123  }
1124 
1125  return true;
1126  };
1127  ASSERT_TRUE(OpenPlaygroundHere(callback));
1128 }
1129 
1130 TEST_P(EntityTest, MorphologyFilter) {
1131  auto boston = CreateTextureForFixture("boston.jpg");
1132  ASSERT_TRUE(boston);
1133 
1134  auto callback = [&](ContentContext& context, RenderPass& pass) -> bool {
1135  const char* morphology_type_names[] = {"Dilate", "Erode"};
1136  const FilterContents::MorphType morphology_types[] = {
1138  static Color input_color = Color::Black();
1139  // UI state.
1140  static int selected_morphology_type = 0;
1141  static float radius[2] = {20, 20};
1142  static Color cover_color(1, 0, 0, 0.2);
1143  static Color bounds_color(0, 1, 0, 0.1);
1144  static float offset[2] = {500, 400};
1145  static float rotation = 0;
1146  static float scale[2] = {0.65, 0.65};
1147  static float skew[2] = {0, 0};
1148  static float path_rect[4] = {0, 0,
1149  static_cast<float>(boston->GetSize().width),
1150  static_cast<float>(boston->GetSize().height)};
1151  static float effect_transform_scale = 1;
1152 
1153  ImGui::Begin("Controls", nullptr, ImGuiWindowFlags_AlwaysAutoResize);
1154  {
1155  ImGui::Combo("Morphology type", &selected_morphology_type,
1156  morphology_type_names,
1157  sizeof(morphology_type_names) / sizeof(char*));
1158  ImGui::SliderFloat2("Radius", radius, 0, 200);
1159  ImGui::SliderFloat("Input opacity", &input_color.alpha, 0, 1);
1160  ImGui::ColorEdit4("Cover color", reinterpret_cast<float*>(&cover_color));
1161  ImGui::ColorEdit4("Bounds color ",
1162  reinterpret_cast<float*>(&bounds_color));
1163  ImGui::SliderFloat2("Translation", offset, 0,
1164  pass.GetRenderTargetSize().width);
1165  ImGui::SliderFloat("Rotation", &rotation, 0, kPi * 2);
1166  ImGui::SliderFloat2("Scale", scale, 0, 3);
1167  ImGui::SliderFloat2("Skew", skew, -3, 3);
1168  ImGui::SliderFloat4("Path XYWH", path_rect, -1000, 1000);
1169  ImGui::SliderFloat("Effect transform scale", &effect_transform_scale, 0,
1170  3);
1171  }
1172  ImGui::End();
1173 
1174  std::shared_ptr<Contents> input;
1175  Size input_size;
1176 
1177  auto input_rect =
1178  Rect::MakeXYWH(path_rect[0], path_rect[1], path_rect[2], path_rect[3]);
1179  auto texture = std::make_shared<TextureContents>();
1180  texture->SetSourceRect(Rect::MakeSize(boston->GetSize()));
1181  texture->SetDestinationRect(input_rect);
1182  texture->SetTexture(boston);
1183  texture->SetOpacity(input_color.alpha);
1184 
1185  input = texture;
1186  input_size = input_rect.GetSize();
1187 
1188  auto contents = FilterContents::MakeMorphology(
1189  FilterInput::Make(input), Radius{radius[0]}, Radius{radius[1]},
1190  morphology_types[selected_morphology_type]);
1191  contents->SetEffectTransform(Matrix::MakeScale(
1192  Vector2{effect_transform_scale, effect_transform_scale}));
1193 
1194  auto ctm = Matrix::MakeScale(GetContentScale()) *
1195  Matrix::MakeTranslation(Vector3(offset[0], offset[1])) *
1196  Matrix::MakeRotationZ(Radians(rotation)) *
1197  Matrix::MakeScale(Vector2(scale[0], scale[1])) *
1198  Matrix::MakeSkew(skew[0], skew[1]) *
1199  Matrix::MakeTranslation(-Point(input_size) / 2);
1200 
1201  Entity entity;
1202  entity.SetContents(contents);
1203  entity.SetTransform(ctm);
1204 
1205  entity.Render(context, pass);
1206 
1207  // Renders a red "cover" rectangle that shows the original position of the
1208  // unfiltered input.
1209  Entity cover_entity;
1210  std::unique_ptr<Geometry> geom =
1211  Geometry::MakeFillPath(flutter::DlPath::MakeRect(input_rect));
1212  auto cover_contents = std::make_shared<SolidColorContents>();
1213  cover_contents->SetColor(cover_color);
1214  cover_contents->SetGeometry(geom.get());
1215  cover_entity.SetContents(cover_contents);
1216  cover_entity.SetTransform(ctm);
1217  cover_entity.Render(context, pass);
1218 
1219  // Renders a green bounding rect of the target filter.
1220  Entity bounds_entity;
1221  std::unique_ptr<Geometry> bounds_geom = Geometry::MakeFillPath(
1222  flutter::DlPath::MakeRect(contents->GetCoverage(entity).value()));
1223  auto bounds_contents = std::make_shared<SolidColorContents>();
1224  bounds_contents->SetColor(bounds_color);
1225  bounds_contents->SetGeometry(bounds_geom.get());
1226  bounds_entity.SetContents(std::move(bounds_contents));
1227  bounds_entity.SetTransform(Matrix());
1228 
1229  bounds_entity.Render(context, pass);
1230 
1231  return true;
1232  };
1233  ASSERT_TRUE(OpenPlaygroundHere(callback));
1234 }
1235 
1236 TEST_P(EntityTest, SetBlendMode) {
1237  Entity entity;
1238  ASSERT_EQ(entity.GetBlendMode(), BlendMode::kSrcOver);
1240  ASSERT_EQ(entity.GetBlendMode(), BlendMode::kClear);
1241 }
1242 
1243 TEST_P(EntityTest, ContentsGetBoundsForEmptyPathReturnsNullopt) {
1244  Entity entity;
1245  entity.SetContents(std::make_shared<SolidColorContents>());
1246  ASSERT_FALSE(entity.GetCoverage().has_value());
1247 }
1248 
1249 TEST_P(EntityTest, SolidStrokeCoverageIsCorrect) {
1250  {
1251  auto geometry = Geometry::MakeStrokePath(
1252  flutter::DlPath::MakeLine({0, 0}, {10, 10}), //
1253  {
1254  .width = 4.0f,
1255  .cap = Cap::kButt,
1256  .join = Join::kBevel,
1257  .miter_limit = 4.0f,
1258  });
1259 
1260  Entity entity;
1261  auto contents = std::make_unique<SolidColorContents>();
1262  contents->SetGeometry(geometry.get());
1263  contents->SetColor(Color::Black());
1264  entity.SetContents(std::move(contents));
1265  auto actual = entity.GetCoverage();
1266  auto expected = Rect::MakeLTRB(-2, -2, 12, 12);
1267 
1268  ASSERT_TRUE(actual.has_value());
1269  ASSERT_RECT_NEAR(actual.value(), expected);
1270  }
1271 
1272  // Cover the Cap::kSquare case.
1273  {
1274  auto geometry = Geometry::MakeStrokePath(
1275  flutter::DlPath::MakeLine({0, 0}, {10, 10}), //
1276  {
1277  .width = 4.0,
1278  .cap = Cap::kSquare,
1279  .join = Join::kBevel,
1280  .miter_limit = 4.0,
1281  });
1282 
1283  Entity entity;
1284  auto contents = std::make_unique<SolidColorContents>();
1285  contents->SetGeometry(geometry.get());
1286  contents->SetColor(Color::Black());
1287  entity.SetContents(std::move(contents));
1288  auto actual = entity.GetCoverage();
1289  auto expected =
1290  Rect::MakeLTRB(-sqrt(8), -sqrt(8), 10 + sqrt(8), 10 + sqrt(8));
1291 
1292  ASSERT_TRUE(actual.has_value());
1293  ASSERT_RECT_NEAR(actual.value(), expected);
1294  }
1295 
1296  // Cover the Join::kMiter case.
1297  {
1298  auto geometry = Geometry::MakeStrokePath(
1299  flutter::DlPath::MakeLine({0, 0}, {10, 10}), //
1300  {
1301  .width = 4.0f,
1302  .cap = Cap::kSquare,
1303  .join = Join::kMiter,
1304  .miter_limit = 2.0f,
1305  });
1306 
1307  Entity entity;
1308  auto contents = std::make_unique<SolidColorContents>();
1309  contents->SetGeometry(geometry.get());
1310  contents->SetColor(Color::Black());
1311  entity.SetContents(std::move(contents));
1312  auto actual = entity.GetCoverage();
1313  auto expected = Rect::MakeLTRB(-4, -4, 14, 14);
1314 
1315  ASSERT_TRUE(actual.has_value());
1316  ASSERT_RECT_NEAR(actual.value(), expected);
1317  }
1318 }
1319 
1320 TEST_P(EntityTest, BorderMaskBlurCoverageIsCorrect) {
1321  auto fill = std::make_shared<SolidColorContents>();
1322  auto geom = Geometry::MakeFillPath(
1323  flutter::DlPath::MakeRect(Rect::MakeXYWH(0, 0, 300, 400)));
1324  fill->SetGeometry(geom.get());
1325  fill->SetColor(Color::CornflowerBlue());
1326  auto border_mask_blur = FilterContents::MakeBorderMaskBlur(
1327  FilterInput::Make(fill), Radius{3}, Radius{4});
1328 
1329  {
1330  Entity e;
1331  e.SetTransform(Matrix());
1332  auto actual = border_mask_blur->GetCoverage(e);
1333  auto expected = Rect::MakeXYWH(-3, -4, 306, 408);
1334  ASSERT_TRUE(actual.has_value());
1335  ASSERT_RECT_NEAR(actual.value(), expected);
1336  }
1337 
1338  {
1339  Entity e;
1341  auto actual = border_mask_blur->GetCoverage(e);
1342  auto expected = Rect::MakeXYWH(-287.792, -4.94975, 504.874, 504.874);
1343  ASSERT_TRUE(actual.has_value());
1344  ASSERT_RECT_NEAR(actual.value(), expected);
1345  }
1346 }
1347 
1348 TEST_P(EntityTest, SolidFillCoverageIsCorrect) {
1349  // No transform
1350  {
1351  auto fill = std::make_shared<SolidColorContents>();
1352  fill->SetColor(Color::CornflowerBlue());
1353  auto expected = Rect::MakeLTRB(100, 110, 200, 220);
1354  auto geom = Geometry::MakeFillPath(flutter::DlPath::MakeRect(expected));
1355  fill->SetGeometry(geom.get());
1356 
1357  auto coverage = fill->GetCoverage({});
1358  ASSERT_TRUE(coverage.has_value());
1359  ASSERT_RECT_NEAR(coverage.value(), expected);
1360  }
1361 
1362  // Entity transform
1363  {
1364  auto fill = std::make_shared<SolidColorContents>();
1365  auto geom = Geometry::MakeFillPath(
1366  flutter::DlPath::MakeRect(Rect::MakeLTRB(100, 110, 200, 220)));
1367  fill->SetColor(Color::CornflowerBlue());
1368  fill->SetGeometry(geom.get());
1369 
1370  Entity entity;
1372  entity.SetContents(std::move(fill));
1373 
1374  auto coverage = entity.GetCoverage();
1375  auto expected = Rect::MakeLTRB(104, 115, 204, 225);
1376  ASSERT_TRUE(coverage.has_value());
1377  ASSERT_RECT_NEAR(coverage.value(), expected);
1378  }
1379 
1380  // No coverage for fully transparent colors
1381  {
1382  auto fill = std::make_shared<SolidColorContents>();
1383  auto geom = Geometry::MakeFillPath(
1384  flutter::DlPath::MakeRect(Rect::MakeLTRB(100, 110, 200, 220)));
1385  fill->SetColor(Color::WhiteTransparent());
1386  fill->SetGeometry(geom.get());
1387 
1388  auto coverage = fill->GetCoverage({});
1389  ASSERT_FALSE(coverage.has_value());
1390  }
1391 }
1392 
1393 TEST_P(EntityTest, RRectShadowTest) {
1394  auto callback = [&](ContentContext& context, RenderPass& pass) {
1395  static Color color = Color::Red();
1396  static float corner_radius = 100;
1397  static float blur_radius = 100;
1398  static bool show_coverage = false;
1399  static Color coverage_color = Color::Green().WithAlpha(0.2);
1400  static PlaygroundPoint top_left_point(Point(200, 200), 30, Color::White());
1401  static PlaygroundPoint bottom_right_point(Point(600, 400), 30,
1402  Color::White());
1403 
1404  ImGui::Begin("Controls", nullptr, ImGuiWindowFlags_AlwaysAutoResize);
1405  ImGui::SliderFloat("Corner radius", &corner_radius, 0, 300);
1406  ImGui::SliderFloat("Blur radius", &blur_radius, 0, 300);
1407  ImGui::ColorEdit4("Color", reinterpret_cast<Scalar*>(&color));
1408  ImGui::Checkbox("Show coverage", &show_coverage);
1409  if (show_coverage) {
1410  ImGui::ColorEdit4("Coverage color",
1411  reinterpret_cast<Scalar*>(&coverage_color));
1412  }
1413  ImGui::End();
1414 
1415  auto [top_left, bottom_right] =
1416  DrawPlaygroundLine(top_left_point, bottom_right_point);
1417  auto rect =
1418  Rect::MakeLTRB(top_left.x, top_left.y, bottom_right.x, bottom_right.y);
1419 
1420  auto contents = std::make_unique<SolidRRectBlurContents>();
1421  contents->SetShape(rect, corner_radius);
1422  contents->SetColor(color);
1423  contents->SetSigma(Radius(blur_radius));
1424 
1425  Entity entity;
1426  entity.SetTransform(Matrix::MakeScale(GetContentScale()));
1427  entity.SetContents(std::move(contents));
1428  entity.Render(context, pass);
1429 
1430  auto coverage = entity.GetCoverage();
1431  if (show_coverage && coverage.has_value()) {
1432  auto bounds_contents = std::make_unique<SolidColorContents>();
1433  auto geom = Geometry::MakeFillPath(
1434  flutter::DlPath::MakeRect(entity.GetCoverage().value()));
1435  bounds_contents->SetGeometry(geom.get());
1436  bounds_contents->SetColor(coverage_color.Premultiply());
1437  Entity bounds_entity;
1438  bounds_entity.SetContents(std::move(bounds_contents));
1439  bounds_entity.Render(context, pass);
1440  }
1441 
1442  return true;
1443  };
1444  ASSERT_TRUE(OpenPlaygroundHere(callback));
1445 }
1446 
1447 TEST_P(EntityTest, ColorMatrixFilterCoverageIsCorrect) {
1448  // Set up a simple color background.
1449  auto fill = std::make_shared<SolidColorContents>();
1450  auto geom = Geometry::MakeFillPath(
1451  flutter::DlPath::MakeRect(Rect::MakeXYWH(0, 0, 300, 400)));
1452  fill->SetGeometry(geom.get());
1453  fill->SetColor(Color::Coral());
1454 
1455  // Set the color matrix filter.
1456  ColorMatrix matrix = {
1457  1, 1, 1, 1, 1, //
1458  1, 1, 1, 1, 1, //
1459  1, 1, 1, 1, 1, //
1460  1, 1, 1, 1, 1, //
1461  };
1462 
1463  auto filter =
1465 
1466  Entity e;
1467  e.SetTransform(Matrix());
1468 
1469  // Confirm that the actual filter coverage matches the expected coverage.
1470  auto actual = filter->GetCoverage(e);
1471  auto expected = Rect::MakeXYWH(0, 0, 300, 400);
1472 
1473  ASSERT_TRUE(actual.has_value());
1474  ASSERT_RECT_NEAR(actual.value(), expected);
1475 }
1476 
1477 TEST_P(EntityTest, ColorMatrixFilterEditable) {
1478  auto bay_bridge = CreateTextureForFixture("bay_bridge.jpg");
1479  ASSERT_TRUE(bay_bridge);
1480 
1481  auto callback = [&](ContentContext& context, RenderPass& pass) -> bool {
1482  // UI state.
1483  static ColorMatrix color_matrix = {
1484  1, 0, 0, 0, 0, //
1485  0, 3, 0, 0, 0, //
1486  0, 0, 1, 0, 0, //
1487  0, 0, 0, 1, 0, //
1488  };
1489  static float offset[2] = {500, 400};
1490  static float rotation = 0;
1491  static float scale[2] = {0.65, 0.65};
1492  static float skew[2] = {0, 0};
1493 
1494  // Define the ImGui
1495  ImGui::Begin("Color Matrix", nullptr, ImGuiWindowFlags_AlwaysAutoResize);
1496  {
1497  std::string label = "##1";
1498  for (int i = 0; i < 20; i += 5) {
1499  ImGui::InputScalarN(label.c_str(), ImGuiDataType_Float,
1500  &(color_matrix.array[i]), 5, nullptr, nullptr,
1501  "%.2f", 0);
1502  label[2]++;
1503  }
1504 
1505  ImGui::SliderFloat2("Translation", &offset[0], 0,
1506  pass.GetRenderTargetSize().width);
1507  ImGui::SliderFloat("Rotation", &rotation, 0, kPi * 2);
1508  ImGui::SliderFloat2("Scale", &scale[0], 0, 3);
1509  ImGui::SliderFloat2("Skew", &skew[0], -3, 3);
1510  }
1511  ImGui::End();
1512 
1513  // Set the color matrix filter.
1515  FilterInput::Make(bay_bridge), color_matrix);
1516 
1517  // Define the entity with the color matrix filter.
1518  Entity entity;
1519  entity.SetTransform(
1520  Matrix::MakeScale(GetContentScale()) *
1521  Matrix::MakeTranslation(Vector3(offset[0], offset[1])) *
1522  Matrix::MakeRotationZ(Radians(rotation)) *
1523  Matrix::MakeScale(Vector2(scale[0], scale[1])) *
1524  Matrix::MakeSkew(skew[0], skew[1]) *
1525  Matrix::MakeTranslation(-Point(bay_bridge->GetSize()) / 2));
1526  entity.SetContents(filter);
1527  entity.Render(context, pass);
1528 
1529  return true;
1530  };
1531 
1532  ASSERT_TRUE(OpenPlaygroundHere(callback));
1533 }
1534 
1535 TEST_P(EntityTest, LinearToSrgbFilterCoverageIsCorrect) {
1536  // Set up a simple color background.
1537  auto geom = Geometry::MakeFillPath(
1538  flutter::DlPath::MakeRect(Rect::MakeXYWH(0, 0, 300, 400)));
1539  auto fill = std::make_shared<SolidColorContents>();
1540  fill->SetGeometry(geom.get());
1541  fill->SetColor(Color::MintCream());
1542 
1543  auto filter =
1545 
1546  Entity e;
1547  e.SetTransform(Matrix());
1548 
1549  // Confirm that the actual filter coverage matches the expected coverage.
1550  auto actual = filter->GetCoverage(e);
1551  auto expected = Rect::MakeXYWH(0, 0, 300, 400);
1552 
1553  ASSERT_TRUE(actual.has_value());
1554  ASSERT_RECT_NEAR(actual.value(), expected);
1555 }
1556 
1557 TEST_P(EntityTest, LinearToSrgbFilter) {
1558  auto image = CreateTextureForFixture("kalimba.jpg");
1559  ASSERT_TRUE(image);
1560 
1561  auto callback = [&](ContentContext& context, RenderPass& pass) -> bool {
1562  auto filtered =
1564 
1565  // Define the entity that will serve as the control image as a Gaussian blur
1566  // filter with no filter at all.
1567  Entity entity_left;
1568  entity_left.SetTransform(Matrix::MakeScale(GetContentScale()) *
1569  Matrix::MakeTranslation({100, 300}) *
1570  Matrix::MakeScale(Vector2{0.5, 0.5}));
1571  auto unfiltered = FilterContents::MakeGaussianBlur(FilterInput::Make(image),
1572  Sigma{0}, Sigma{0});
1573  entity_left.SetContents(unfiltered);
1574 
1575  // Define the entity that will be filtered from linear to sRGB.
1576  Entity entity_right;
1577  entity_right.SetTransform(Matrix::MakeScale(GetContentScale()) *
1578  Matrix::MakeTranslation({500, 300}) *
1579  Matrix::MakeScale(Vector2{0.5, 0.5}));
1580  entity_right.SetContents(filtered);
1581  return entity_left.Render(context, pass) &&
1582  entity_right.Render(context, pass);
1583  };
1584 
1585  ASSERT_TRUE(OpenPlaygroundHere(callback));
1586 }
1587 
1588 TEST_P(EntityTest, SrgbToLinearFilterCoverageIsCorrect) {
1589  // Set up a simple color background.
1590  auto fill = std::make_shared<SolidColorContents>();
1591  auto geom = Geometry::MakeFillPath(
1592  flutter::DlPath::MakeRect(Rect::MakeXYWH(0, 0, 300, 400)));
1593  fill->SetGeometry(geom.get());
1594  fill->SetColor(Color::DeepPink());
1595 
1596  auto filter =
1598 
1599  Entity e;
1600  e.SetTransform(Matrix());
1601 
1602  // Confirm that the actual filter coverage matches the expected coverage.
1603  auto actual = filter->GetCoverage(e);
1604  auto expected = Rect::MakeXYWH(0, 0, 300, 400);
1605 
1606  ASSERT_TRUE(actual.has_value());
1607  ASSERT_RECT_NEAR(actual.value(), expected);
1608 }
1609 
1610 TEST_P(EntityTest, SrgbToLinearFilter) {
1611  auto image = CreateTextureForFixture("embarcadero.jpg");
1612  ASSERT_TRUE(image);
1613 
1614  auto callback = [&](ContentContext& context, RenderPass& pass) -> bool {
1615  auto filtered =
1617 
1618  // Define the entity that will serve as the control image as a Gaussian blur
1619  // filter with no filter at all.
1620  Entity entity_left;
1621  entity_left.SetTransform(Matrix::MakeScale(GetContentScale()) *
1622  Matrix::MakeTranslation({100, 300}) *
1623  Matrix::MakeScale(Vector2{0.5, 0.5}));
1624  auto unfiltered = FilterContents::MakeGaussianBlur(FilterInput::Make(image),
1625  Sigma{0}, Sigma{0});
1626  entity_left.SetContents(unfiltered);
1627 
1628  // Define the entity that will be filtered from sRGB to linear.
1629  Entity entity_right;
1630  entity_right.SetTransform(Matrix::MakeScale(GetContentScale()) *
1631  Matrix::MakeTranslation({500, 300}) *
1632  Matrix::MakeScale(Vector2{0.5, 0.5}));
1633  entity_right.SetContents(filtered);
1634  return entity_left.Render(context, pass) &&
1635  entity_right.Render(context, pass);
1636  };
1637 
1638  ASSERT_TRUE(OpenPlaygroundHere(callback));
1639 }
1640 
1641 static Vector3 RGBToYUV(Vector3 rgb, YUVColorSpace yuv_color_space) {
1642  Vector3 yuv;
1643  switch (yuv_color_space) {
1645  yuv.x = rgb.x * 0.299 + rgb.y * 0.587 + rgb.z * 0.114;
1646  yuv.y = rgb.x * -0.169 + rgb.y * -0.331 + rgb.z * 0.5 + 0.5;
1647  yuv.z = rgb.x * 0.5 + rgb.y * -0.419 + rgb.z * -0.081 + 0.5;
1648  break;
1650  yuv.x = rgb.x * 0.257 + rgb.y * 0.516 + rgb.z * 0.100 + 0.063;
1651  yuv.y = rgb.x * -0.145 + rgb.y * -0.291 + rgb.z * 0.439 + 0.5;
1652  yuv.z = rgb.x * 0.429 + rgb.y * -0.368 + rgb.z * -0.071 + 0.5;
1653  break;
1654  }
1655  return yuv;
1656 }
1657 
1658 static std::vector<std::shared_ptr<Texture>> CreateTestYUVTextures(
1659  Context* context,
1660  YUVColorSpace yuv_color_space) {
1661  Vector3 red = {244.0 / 255.0, 67.0 / 255.0, 54.0 / 255.0};
1662  Vector3 green = {76.0 / 255.0, 175.0 / 255.0, 80.0 / 255.0};
1663  Vector3 blue = {33.0 / 255.0, 150.0 / 255.0, 243.0 / 255.0};
1664  Vector3 white = {1.0, 1.0, 1.0};
1665  Vector3 red_yuv = RGBToYUV(red, yuv_color_space);
1666  Vector3 green_yuv = RGBToYUV(green, yuv_color_space);
1667  Vector3 blue_yuv = RGBToYUV(blue, yuv_color_space);
1668  Vector3 white_yuv = RGBToYUV(white, yuv_color_space);
1669  std::vector<Vector3> yuvs{red_yuv, green_yuv, blue_yuv, white_yuv};
1670  std::vector<uint8_t> y_data;
1671  std::vector<uint8_t> uv_data;
1672  for (int i = 0; i < 4; i++) {
1673  auto yuv = yuvs[i];
1674  uint8_t y = std::round(yuv.x * 255.0);
1675  uint8_t u = std::round(yuv.y * 255.0);
1676  uint8_t v = std::round(yuv.z * 255.0);
1677  for (int j = 0; j < 16; j++) {
1678  y_data.push_back(y);
1679  }
1680  for (int j = 0; j < 8; j++) {
1681  uv_data.push_back(j % 2 == 0 ? u : v);
1682  }
1683  }
1684  auto cmd_buffer = context->CreateCommandBuffer();
1685  auto blit_pass = cmd_buffer->CreateBlitPass();
1686 
1687  impeller::TextureDescriptor y_texture_descriptor;
1688  y_texture_descriptor.storage_mode = impeller::StorageMode::kHostVisible;
1689  y_texture_descriptor.format = PixelFormat::kR8UNormInt;
1690  y_texture_descriptor.size = {8, 8};
1691  auto y_texture =
1692  context->GetResourceAllocator()->CreateTexture(y_texture_descriptor);
1693  auto y_mapping = std::make_shared<fml::DataMapping>(y_data);
1694  auto y_mapping_buffer =
1695  context->GetResourceAllocator()->CreateBufferWithCopy(*y_mapping);
1696 
1697  blit_pass->AddCopy(DeviceBuffer::AsBufferView(y_mapping_buffer), y_texture);
1698 
1699  impeller::TextureDescriptor uv_texture_descriptor;
1700  uv_texture_descriptor.storage_mode = impeller::StorageMode::kHostVisible;
1701  uv_texture_descriptor.format = PixelFormat::kR8G8UNormInt;
1702  uv_texture_descriptor.size = {4, 4};
1703  auto uv_texture =
1704  context->GetResourceAllocator()->CreateTexture(uv_texture_descriptor);
1705  auto uv_mapping = std::make_shared<fml::DataMapping>(uv_data);
1706  auto uv_mapping_buffer =
1707  context->GetResourceAllocator()->CreateBufferWithCopy(*uv_mapping);
1708 
1709  blit_pass->AddCopy(DeviceBuffer::AsBufferView(uv_mapping_buffer), uv_texture);
1710 
1711  if (!blit_pass->EncodeCommands() ||
1712  !context->GetCommandQueue()->Submit({cmd_buffer}).ok()) {
1713  FML_DLOG(ERROR) << "Could not copy contents into Y/UV texture.";
1714  }
1715 
1716  return {y_texture, uv_texture};
1717 }
1718 
1719 TEST_P(EntityTest, YUVToRGBFilter) {
1720  if (GetParam() == PlaygroundBackend::kOpenGLES) {
1721  // TODO(114588) : Support YUV to RGB filter on OpenGLES backend.
1722  GTEST_SKIP()
1723  << "YUV to RGB filter is not supported on OpenGLES backend yet.";
1724  }
1725 
1726  auto callback = [&](ContentContext& context, RenderPass& pass) -> bool {
1727  YUVColorSpace yuv_color_space_array[2]{YUVColorSpace::kBT601FullRange,
1729  for (int i = 0; i < 2; i++) {
1730  auto yuv_color_space = yuv_color_space_array[i];
1731  auto textures =
1732  CreateTestYUVTextures(GetContext().get(), yuv_color_space);
1733  auto filter_contents = FilterContents::MakeYUVToRGBFilter(
1734  textures[0], textures[1], yuv_color_space);
1735  Entity filter_entity;
1736  filter_entity.SetContents(filter_contents);
1737  auto snapshot = filter_contents->RenderToSnapshot(context, filter_entity);
1738 
1739  Entity entity;
1740  auto contents = TextureContents::MakeRect(Rect::MakeLTRB(0, 0, 256, 256));
1741  contents->SetTexture(snapshot->texture);
1742  contents->SetSourceRect(Rect::MakeSize(snapshot->texture->GetSize()));
1743  entity.SetContents(contents);
1744  entity.SetTransform(
1745  Matrix::MakeTranslation({static_cast<Scalar>(100 + 400 * i), 300}));
1746  entity.Render(context, pass);
1747  }
1748  return true;
1749  };
1750  ASSERT_TRUE(OpenPlaygroundHere(callback));
1751 }
1752 
1753 TEST_P(EntityTest, RuntimeEffect) {
1754  auto runtime_stages =
1755  OpenAssetAsRuntimeStage("runtime_stage_example.frag.iplr");
1756  auto runtime_stage =
1757  runtime_stages[PlaygroundBackendToRuntimeStageBackend(GetBackend())];
1758  ASSERT_TRUE(runtime_stage);
1759  ASSERT_TRUE(runtime_stage->IsDirty());
1760 
1761  bool expect_dirty = true;
1762 
1763  PipelineRef first_pipeline;
1764  std::unique_ptr<Geometry> geom = Geometry::MakeCover();
1765 
1766  auto callback = [&](ContentContext& context, RenderPass& pass) -> bool {
1767  EXPECT_EQ(runtime_stage->IsDirty(), expect_dirty);
1768 
1769  auto contents = std::make_shared<RuntimeEffectContents>();
1770  contents->SetGeometry(geom.get());
1771  contents->SetRuntimeStage(runtime_stage);
1772 
1773  struct FragUniforms {
1774  Vector2 iResolution;
1775  Scalar iTime;
1776  } frag_uniforms = {
1777  .iResolution = Vector2(GetWindowSize().width, GetWindowSize().height),
1778  .iTime = static_cast<Scalar>(GetSecondsElapsed()),
1779  };
1780  auto uniform_data = std::make_shared<std::vector<uint8_t>>();
1781  uniform_data->resize(sizeof(FragUniforms));
1782  memcpy(uniform_data->data(), &frag_uniforms, sizeof(FragUniforms));
1783  contents->SetUniformData(uniform_data);
1784 
1785  Entity entity;
1786  entity.SetContents(contents);
1787  bool result = contents->Render(context, entity, pass);
1788 
1789  if (expect_dirty) {
1790  first_pipeline = pass.GetCommands().back().pipeline;
1791  } else {
1792  EXPECT_EQ(pass.GetCommands().back().pipeline, first_pipeline);
1793  }
1794  expect_dirty = false;
1795  return result;
1796  };
1797 
1798  // Simulate some renders and hot reloading of the shader.
1799  auto content_context = GetContentContext();
1800  {
1801  RenderTarget target =
1802  content_context->GetRenderTargetCache()->CreateOffscreen(
1803  *content_context->GetContext(), {1, 1}, 1u);
1804 
1805  testing::MockRenderPass mock_pass(GetContext(), target);
1806  callback(*content_context, mock_pass);
1807  callback(*content_context, mock_pass);
1808 
1809  // Dirty the runtime stage.
1810  runtime_stages = OpenAssetAsRuntimeStage("runtime_stage_example.frag.iplr");
1811  runtime_stage =
1812  runtime_stages[PlaygroundBackendToRuntimeStageBackend(GetBackend())];
1813 
1814  ASSERT_TRUE(runtime_stage->IsDirty());
1815  expect_dirty = true;
1816 
1817  callback(*content_context, mock_pass);
1818  }
1819 }
1820 
1821 TEST_P(EntityTest, RuntimeEffectCanSuccessfullyRender) {
1822  auto runtime_stages =
1823  OpenAssetAsRuntimeStage("runtime_stage_example.frag.iplr");
1824  auto runtime_stage =
1825  runtime_stages[PlaygroundBackendToRuntimeStageBackend(GetBackend())];
1826  ASSERT_TRUE(runtime_stage);
1827  ASSERT_TRUE(runtime_stage->IsDirty());
1828 
1829  auto contents = std::make_shared<RuntimeEffectContents>();
1830  auto geom = Geometry::MakeCover();
1831  contents->SetGeometry(geom.get());
1832  contents->SetRuntimeStage(runtime_stage);
1833 
1834  struct FragUniforms {
1835  Vector2 iResolution;
1836  Scalar iTime;
1837  } frag_uniforms = {
1838  .iResolution = Vector2(GetWindowSize().width, GetWindowSize().height),
1839  .iTime = static_cast<Scalar>(GetSecondsElapsed()),
1840  };
1841  auto uniform_data = std::make_shared<std::vector<uint8_t>>();
1842  uniform_data->resize(sizeof(FragUniforms));
1843  memcpy(uniform_data->data(), &frag_uniforms, sizeof(FragUniforms));
1844  contents->SetUniformData(uniform_data);
1845 
1846  Entity entity;
1847  entity.SetContents(contents);
1848 
1849  // Create a render target with a depth-stencil, similar to how EntityPass
1850  // does.
1851  RenderTarget target =
1852  GetContentContext()->GetRenderTargetCache()->CreateOffscreenMSAA(
1853  *GetContext(), {GetWindowSize().width, GetWindowSize().height}, 1,
1854  "RuntimeEffect Texture");
1855  testing::MockRenderPass pass(GetContext(), target);
1856 
1857  ASSERT_TRUE(contents->Render(*GetContentContext(), entity, pass));
1858  ASSERT_EQ(pass.GetCommands().size(), 1u);
1859  const auto& command = pass.GetCommands()[0];
1860  ASSERT_TRUE(command.pipeline->GetDescriptor()
1861  .GetDepthStencilAttachmentDescriptor()
1862  .has_value());
1863  ASSERT_TRUE(command.pipeline->GetDescriptor()
1864  .GetFrontStencilAttachmentDescriptor()
1865  .has_value());
1866 }
1867 
1868 TEST_P(EntityTest, RuntimeEffectCanPrecache) {
1869  auto runtime_stages =
1870  OpenAssetAsRuntimeStage("runtime_stage_example.frag.iplr");
1871  auto runtime_stage =
1872  runtime_stages[PlaygroundBackendToRuntimeStageBackend(GetBackend())];
1873  ASSERT_TRUE(runtime_stage);
1874  ASSERT_TRUE(runtime_stage->IsDirty());
1875 
1876  auto contents = std::make_shared<RuntimeEffectContents>();
1877  contents->SetRuntimeStage(runtime_stage);
1878 
1879  EXPECT_TRUE(contents->BootstrapShader(*GetContentContext()));
1880 }
1881 
1882 TEST_P(EntityTest, RuntimeEffectSetsRightSizeWhenUniformIsStruct) {
1883  if (GetBackend() != PlaygroundBackend::kVulkan) {
1884  GTEST_SKIP() << "Test only applies to Vulkan";
1885  }
1886 
1887  auto runtime_stages =
1888  OpenAssetAsRuntimeStage("runtime_stage_example.frag.iplr");
1889  auto runtime_stage =
1890  runtime_stages[PlaygroundBackendToRuntimeStageBackend(GetBackend())];
1891  ASSERT_TRUE(runtime_stage);
1892  ASSERT_TRUE(runtime_stage->IsDirty());
1893 
1894  auto contents = std::make_shared<RuntimeEffectContents>();
1895  auto geom = Geometry::MakeCover();
1896  contents->SetGeometry(geom.get());
1897  contents->SetRuntimeStage(runtime_stage);
1898 
1899  struct FragUniforms {
1900  Vector2 iResolution;
1901  Scalar iTime;
1902  } frag_uniforms = {
1903  .iResolution = Vector2(GetWindowSize().width, GetWindowSize().height),
1904  .iTime = static_cast<Scalar>(GetSecondsElapsed()),
1905  };
1906  auto uniform_data = std::make_shared<std::vector<uint8_t>>();
1907  uniform_data->resize(sizeof(FragUniforms));
1908  memcpy(uniform_data->data(), &frag_uniforms, sizeof(FragUniforms));
1909 
1911  uniform_data, GetContentContext()->GetTransientsDataBuffer(),
1912  runtime_stage->GetUniforms()[0],
1913  GetContentContext()
1914  ->GetTransientsDataBuffer()
1915  .GetMinimumUniformAlignment());
1916 
1917  // 16 bytes:
1918  // 8 bytes for iResolution
1919  // 4 bytes for iTime
1920  // 4 bytes padding
1921  EXPECT_EQ(buffer_view.GetRange().length, 16u);
1922 }
1923 
1924 TEST_P(EntityTest, ColorFilterWithForegroundColorAdvancedBlend) {
1925  auto image = CreateTextureForFixture("boston.jpg");
1926  auto filter = ColorFilterContents::MakeBlend(
1928 
1929  auto callback = [&](ContentContext& context, RenderPass& pass) -> bool {
1930  Entity entity;
1931  entity.SetTransform(Matrix::MakeScale(GetContentScale()) *
1932  Matrix::MakeTranslation({500, 300}) *
1933  Matrix::MakeScale(Vector2{0.5, 0.5}));
1934  entity.SetContents(filter);
1935  return entity.Render(context, pass);
1936  };
1937  ASSERT_TRUE(OpenPlaygroundHere(callback));
1938 }
1939 
1940 TEST_P(EntityTest, ColorFilterWithForegroundColorClearBlend) {
1941  auto image = CreateTextureForFixture("boston.jpg");
1942  auto filter = ColorFilterContents::MakeBlend(
1944 
1945  auto callback = [&](ContentContext& context, RenderPass& pass) -> bool {
1946  Entity entity;
1947  entity.SetTransform(Matrix::MakeScale(GetContentScale()) *
1948  Matrix::MakeTranslation({500, 300}) *
1949  Matrix::MakeScale(Vector2{0.5, 0.5}));
1950  entity.SetContents(filter);
1951  return entity.Render(context, pass);
1952  };
1953  ASSERT_TRUE(OpenPlaygroundHere(callback));
1954 }
1955 
1956 TEST_P(EntityTest, ColorFilterWithForegroundColorSrcBlend) {
1957  auto image = CreateTextureForFixture("boston.jpg");
1958  auto filter = ColorFilterContents::MakeBlend(
1960 
1961  auto callback = [&](ContentContext& context, RenderPass& pass) -> bool {
1962  Entity entity;
1963  entity.SetTransform(Matrix::MakeScale(GetContentScale()) *
1964  Matrix::MakeTranslation({500, 300}) *
1965  Matrix::MakeScale(Vector2{0.5, 0.5}));
1966  entity.SetContents(filter);
1967  return entity.Render(context, pass);
1968  };
1969  ASSERT_TRUE(OpenPlaygroundHere(callback));
1970 }
1971 
1972 TEST_P(EntityTest, ColorFilterWithForegroundColorDstBlend) {
1973  auto image = CreateTextureForFixture("boston.jpg");
1974  auto filter = ColorFilterContents::MakeBlend(
1976 
1977  auto callback = [&](ContentContext& context, RenderPass& pass) -> bool {
1978  Entity entity;
1979  entity.SetTransform(Matrix::MakeScale(GetContentScale()) *
1980  Matrix::MakeTranslation({500, 300}) *
1981  Matrix::MakeScale(Vector2{0.5, 0.5}));
1982  entity.SetContents(filter);
1983  return entity.Render(context, pass);
1984  };
1985  ASSERT_TRUE(OpenPlaygroundHere(callback));
1986 }
1987 
1988 TEST_P(EntityTest, ColorFilterWithForegroundColorSrcInBlend) {
1989  auto image = CreateTextureForFixture("boston.jpg");
1990  auto filter = ColorFilterContents::MakeBlend(
1992 
1993  auto callback = [&](ContentContext& context, RenderPass& pass) -> bool {
1994  Entity entity;
1995  entity.SetTransform(Matrix::MakeScale(GetContentScale()) *
1996  Matrix::MakeTranslation({500, 300}) *
1997  Matrix::MakeScale(Vector2{0.5, 0.5}));
1998  entity.SetContents(filter);
1999  return entity.Render(context, pass);
2000  };
2001  ASSERT_TRUE(OpenPlaygroundHere(callback));
2002 }
2003 
2004 TEST_P(EntityTest, CoverageForStrokePathWithNegativeValuesInTransform) {
2005  auto arrow_head = flutter::DlPathBuilder{}
2006  .MoveTo({50, 120})
2007  .LineTo({120, 190})
2008  .LineTo({190, 120})
2009  .TakePath();
2010  auto geometry = Geometry::MakeStrokePath(arrow_head, //
2011  {
2012  .width = 15.0f,
2013  .cap = Cap::kRound,
2014  .join = Join::kRound,
2015  .miter_limit = 4.0f,
2016  });
2017 
2018  auto transform = Matrix::MakeTranslation({300, 300}) *
2020  // Note that e[0][0] used to be tested here, but it was -epsilon solely
2021  // due to floating point inaccuracy in the transcendental trig functions.
2022  // e[1][0] is the intended negative value that we care about (-1.0) as it
2023  // comes from the rotation of pi/2.
2024  EXPECT_LT(transform.e[1][0], 0.0f);
2025  auto coverage = geometry->GetCoverage(transform);
2026  ASSERT_RECT_NEAR(coverage.value(), Rect::MakeXYWH(102.5, 342.5, 85, 155));
2027 }
2028 
2029 TEST_P(EntityTest, SolidColorContentsIsOpaque) {
2030  Matrix matrix;
2031  SolidColorContents contents;
2032  auto geom = Geometry::MakeRect(Rect::MakeLTRB(0, 0, 10, 10));
2033  contents.SetGeometry(geom.get());
2034 
2035  contents.SetColor(Color::CornflowerBlue());
2036  EXPECT_TRUE(contents.IsOpaque(matrix));
2037  contents.SetColor(Color::CornflowerBlue().WithAlpha(0.5));
2038  EXPECT_FALSE(contents.IsOpaque(matrix));
2039 
2040  // Create stroked path that required alpha coverage.
2041  geom = Geometry::MakeStrokePath(flutter::DlPath::MakeLine({0, 0}, {100, 100}),
2042  {.width = 0.05});
2043  contents.SetGeometry(geom.get());
2044  contents.SetColor(Color::CornflowerBlue());
2045 
2046  EXPECT_FALSE(contents.IsOpaque(matrix));
2047 }
2048 
2049 TEST_P(EntityTest, ConicalGradientContentsIsOpaque) {
2050  Matrix matrix;
2051  ConicalGradientContents contents;
2052  auto geom = Geometry::MakeRect(Rect::MakeLTRB(0, 0, 10, 10));
2053  contents.SetGeometry(geom.get());
2054 
2055  contents.SetColors({Color::CornflowerBlue()});
2056  EXPECT_FALSE(contents.IsOpaque(matrix));
2057  contents.SetColors({Color::CornflowerBlue().WithAlpha(0.5)});
2058  EXPECT_FALSE(contents.IsOpaque(matrix));
2059 
2060  // Create stroked path that required alpha coverage.
2061  geom = Geometry::MakeStrokePath(
2062  flutter::DlPathBuilder{}.MoveTo({0, 0}).LineTo({100, 100}).TakePath(),
2063  {.width = 0.05f});
2064  contents.SetGeometry(geom.get());
2065  contents.SetColors({Color::CornflowerBlue()});
2066 
2067  EXPECT_FALSE(contents.IsOpaque(matrix));
2068 }
2069 
2070 TEST_P(EntityTest, LinearGradientContentsIsOpaque) {
2071  Matrix matrix;
2072  LinearGradientContents contents;
2073  auto geom = Geometry::MakeRect(Rect::MakeLTRB(0, 0, 10, 10));
2074  contents.SetGeometry(geom.get());
2075 
2076  contents.SetColors({Color::CornflowerBlue()});
2077  EXPECT_TRUE(contents.IsOpaque(matrix));
2078  contents.SetColors({Color::CornflowerBlue().WithAlpha(0.5)});
2079  EXPECT_FALSE(contents.IsOpaque(matrix));
2080  contents.SetColors({Color::CornflowerBlue()});
2082  EXPECT_FALSE(contents.IsOpaque(matrix));
2083 
2084  // Create stroked path that required alpha coverage.
2085  geom = Geometry::MakeStrokePath(
2086  flutter::DlPathBuilder{}.MoveTo({0, 0}).LineTo({100, 100}).TakePath(),
2087  {.width = 0.05f});
2088  contents.SetGeometry(geom.get());
2089  contents.SetColors({Color::CornflowerBlue()});
2090 
2091  EXPECT_FALSE(contents.IsOpaque(matrix));
2092 }
2093 
2094 TEST_P(EntityTest, RadialGradientContentsIsOpaque) {
2095  Matrix matrix;
2096  RadialGradientContents contents;
2097  auto geom = Geometry::MakeRect(Rect::MakeLTRB(0, 0, 10, 10));
2098  contents.SetGeometry(geom.get());
2099 
2100  contents.SetColors({Color::CornflowerBlue()});
2101  EXPECT_TRUE(contents.IsOpaque(matrix));
2102  contents.SetColors({Color::CornflowerBlue().WithAlpha(0.5)});
2103  EXPECT_FALSE(contents.IsOpaque(matrix));
2104  contents.SetColors({Color::CornflowerBlue()});
2106  EXPECT_FALSE(contents.IsOpaque(matrix));
2107 
2108  // Create stroked path that required alpha coverage.
2109  geom = Geometry::MakeStrokePath(
2110  flutter::DlPathBuilder{}.MoveTo({0, 0}).LineTo({100, 100}).TakePath(),
2111  {.width = 0.05});
2112  contents.SetGeometry(geom.get());
2113  contents.SetColors({Color::CornflowerBlue()});
2114 
2115  EXPECT_FALSE(contents.IsOpaque(matrix));
2116 }
2117 
2118 TEST_P(EntityTest, SweepGradientContentsIsOpaque) {
2119  Matrix matrix;
2120  RadialGradientContents contents;
2121  auto geom = Geometry::MakeRect(Rect::MakeLTRB(0, 0, 10, 10));
2122  contents.SetGeometry(geom.get());
2123 
2124  contents.SetColors({Color::CornflowerBlue()});
2125  EXPECT_TRUE(contents.IsOpaque(matrix));
2126  contents.SetColors({Color::CornflowerBlue().WithAlpha(0.5)});
2127  EXPECT_FALSE(contents.IsOpaque(matrix));
2128  contents.SetColors({Color::CornflowerBlue()});
2130  EXPECT_FALSE(contents.IsOpaque(matrix));
2131 
2132  // Create stroked path that required alpha coverage.
2133  geom = Geometry::MakeStrokePath(
2134  flutter::DlPathBuilder{}.MoveTo({0, 0}).LineTo({100, 100}).TakePath(),
2135  {.width = 0.05f});
2136  contents.SetGeometry(geom.get());
2137  contents.SetColors({Color::CornflowerBlue()});
2138 
2139  EXPECT_FALSE(contents.IsOpaque(matrix));
2140 }
2141 
2142 TEST_P(EntityTest, TiledTextureContentsIsOpaque) {
2143  Matrix matrix;
2144  auto bay_bridge = CreateTextureForFixture("bay_bridge.jpg");
2145  TiledTextureContents contents;
2146  contents.SetTexture(bay_bridge);
2147  // This is a placeholder test. Images currently never decompress as opaque
2148  // (whether in Flutter or the playground), and so this should currently always
2149  // return false in practice.
2150  EXPECT_FALSE(contents.IsOpaque(matrix));
2151 }
2152 
2153 TEST_P(EntityTest, PointFieldGeometryCoverage) {
2154  std::vector<Point> points = {{10, 20}, {100, 200}};
2155  PointFieldGeometry geometry(points.data(), 2, 5.0, false);
2156  ASSERT_EQ(geometry.GetCoverage(Matrix()), Rect::MakeLTRB(5, 15, 105, 205));
2157  ASSERT_EQ(geometry.GetCoverage(Matrix::MakeTranslation({30, 0, 0})),
2158  Rect::MakeLTRB(35, 15, 135, 205));
2159 }
2160 
2161 TEST_P(EntityTest, ColorFilterContentsWithLargeGeometry) {
2162  Entity entity;
2163  entity.SetTransform(Matrix::MakeScale(GetContentScale()));
2164  auto src_contents = std::make_shared<SolidColorContents>();
2165  auto src_geom = Geometry::MakeRect(Rect::MakeLTRB(-300, -500, 30000, 50000));
2166  src_contents->SetGeometry(src_geom.get());
2167  src_contents->SetColor(Color::Red());
2168 
2169  auto dst_contents = std::make_shared<SolidColorContents>();
2170  auto dst_geom = Geometry::MakeRect(Rect::MakeLTRB(300, 500, 20000, 30000));
2171  dst_contents->SetGeometry(dst_geom.get());
2172  dst_contents->SetColor(Color::Blue());
2173 
2174  auto contents = ColorFilterContents::MakeBlend(
2175  BlendMode::kSrcOver, {FilterInput::Make(dst_contents, false),
2176  FilterInput::Make(src_contents, false)});
2177  entity.SetContents(std::move(contents));
2178  ASSERT_TRUE(OpenPlaygroundHere(std::move(entity)));
2179 }
2180 
2181 TEST_P(EntityTest, TextContentsCeilsGlyphScaleToDecimal) {
2182  ASSERT_EQ(TextFrame::RoundScaledFontSize(0.4321111f), Rational(43, 100));
2183  ASSERT_EQ(TextFrame::RoundScaledFontSize(0.5321111f), Rational(53, 100));
2184  ASSERT_EQ(TextFrame::RoundScaledFontSize(2.1f), Rational(21, 10));
2185  ASSERT_EQ(TextFrame::RoundScaledFontSize(0.0f), Rational(0, 1));
2186  ASSERT_EQ(TextFrame::RoundScaledFontSize(100000000.0f), Rational(48, 1));
2187 }
2188 
2189 TEST_P(EntityTest, SpecializationConstantsAreAppliedToVariants) {
2190  auto content_context = GetContentContext();
2191 
2192  auto default_gyph = content_context->GetGlyphAtlasPipeline({
2193  .color_attachment_pixel_format = PixelFormat::kR8G8B8A8UNormInt,
2194  .has_depth_stencil_attachments = false,
2195  });
2196  auto alt_gyph = content_context->GetGlyphAtlasPipeline(
2197  {.color_attachment_pixel_format = PixelFormat::kR8G8B8A8UNormInt,
2198  .has_depth_stencil_attachments = true});
2199 
2200  EXPECT_NE(default_gyph, alt_gyph);
2201  EXPECT_EQ(default_gyph->GetDescriptor().GetSpecializationConstants(),
2202  alt_gyph->GetDescriptor().GetSpecializationConstants());
2203 
2204  auto use_a8 = GetContext()->GetCapabilities()->GetDefaultGlyphAtlasFormat() ==
2206 
2207  std::vector<Scalar> expected_constants = {static_cast<Scalar>(use_a8)};
2208  EXPECT_EQ(default_gyph->GetDescriptor().GetSpecializationConstants(),
2209  expected_constants);
2210 }
2211 
2212 TEST_P(EntityTest, DecalSpecializationAppliedToMorphologyFilter) {
2213  auto content_context = GetContentContext();
2214  auto default_color_burn = content_context->GetMorphologyFilterPipeline({
2215  .color_attachment_pixel_format = PixelFormat::kR8G8B8A8UNormInt,
2216  });
2217 
2218  auto decal_supported = static_cast<Scalar>(
2219  GetContext()->GetCapabilities()->SupportsDecalSamplerAddressMode());
2220  std::vector<Scalar> expected_constants = {decal_supported};
2221  ASSERT_EQ(default_color_burn->GetDescriptor().GetSpecializationConstants(),
2222  expected_constants);
2223 }
2224 
2225 // This doesn't really tell you if the hashes will have frequent
2226 // collisions, but since this type is only used to hash a bounded
2227 // set of options, we can just compare benchmarks.
2228 TEST_P(EntityTest, ContentContextOptionsHasReasonableHashFunctions) {
2229  ContentContextOptions opts;
2230  auto hash_a = opts.ToKey();
2231 
2233  auto hash_b = opts.ToKey();
2234 
2235  opts.has_depth_stencil_attachments = false;
2236  auto hash_c = opts.ToKey();
2237 
2239  auto hash_d = opts.ToKey();
2240 
2241  EXPECT_NE(hash_a, hash_b);
2242  EXPECT_NE(hash_b, hash_c);
2243  EXPECT_NE(hash_c, hash_d);
2244 }
2245 
2246 #ifdef FML_OS_LINUX
2247 TEST_P(EntityTest, FramebufferFetchVulkanBindingOffsetIsTheSame) {
2248  // Using framebuffer fetch on Vulkan requires that we maintain a subpass input
2249  // binding that we don't have a good route for configuring with the
2250  // current metadata approach. This test verifies that the binding value
2251  // doesn't change
2252  // from the expected constant.
2253  // See also:
2254  // * impeller/renderer/backend/vulkan/binding_helpers_vk.cc
2255  // * impeller/entity/shaders/blending/framebuffer_blend.frag
2256  // This test only works on Linux because macOS hosts incorrectly
2257  // populate the
2258  // Vulkan descriptor sets based on the MSL compiler settings.
2259 
2260  bool expected_layout = false;
2262  FragmentShader::kDescriptorSetLayouts) {
2263  if (layout.binding == 64 &&
2264  layout.descriptor_type == DescriptorType::kInputAttachment) {
2265  expected_layout = true;
2266  }
2267  }
2268  EXPECT_TRUE(expected_layout);
2269 }
2270 #endif
2271 
2272 TEST_P(EntityTest, FillPathGeometryGetPositionBufferReturnsExpectedMode) {
2273  RenderTarget target;
2274  testing::MockRenderPass mock_pass(GetContext(), target);
2275 
2276  auto get_result = [this, &mock_pass](const flutter::DlPath& path) {
2277  auto geometry = Geometry::MakeFillPath(
2278  path, /* inner rect */ Rect::MakeLTRB(0, 0, 100, 100));
2279  return geometry->GetPositionBuffer(*GetContentContext(), {}, mock_pass);
2280  };
2281 
2282  // Convex path
2283  {
2284  GeometryResult result =
2285  get_result(flutter::DlPath::MakeRect(Rect::MakeLTRB(0, 0, 100, 100)));
2286  EXPECT_EQ(result.mode, GeometryResult::Mode::kNormal);
2287  }
2288 
2289  // Concave path
2290  {
2291  flutter::DlPath path = flutter::DlPathBuilder{}
2292  .MoveTo({0, 0})
2293  .LineTo({100, 0})
2294  .LineTo({100, 100})
2295  .LineTo({51, 50})
2296  .Close()
2297  .TakePath();
2298  GeometryResult result = get_result(path);
2299  EXPECT_EQ(result.mode, GeometryResult::Mode::kNonZero);
2300  }
2301 }
2302 
2303 TEST_P(EntityTest, FailOnValidationError) {
2304  if (GetParam() != PlaygroundBackend::kVulkan) {
2305  GTEST_SKIP() << "Validation is only fatal on Vulkan backend.";
2306  }
2307  EXPECT_DEATH(
2308  // The easiest way to trigger a validation error is to try to compile
2309  // a shader with an unsupported pixel format.
2310  GetContentContext()->GetBlendColorBurnPipeline({
2311  .color_attachment_pixel_format = PixelFormat::kUnknown,
2312  .has_depth_stencil_attachments = false,
2313  }),
2314  "");
2315 }
2316 
2317 TEST_P(EntityTest, CanComputeGeometryForEmptyPathsWithoutCrashing) {
2318  flutter::DlPath path = flutter::DlPath::MakeRect(Rect::MakeLTRB(0, 0, 0, 0));
2319 
2320  EXPECT_TRUE(path.GetBounds().IsEmpty());
2321 
2322  auto geom = Geometry::MakeFillPath(path);
2323 
2324  Entity entity;
2325  RenderTarget target =
2326  GetContentContext()->GetRenderTargetCache()->CreateOffscreen(
2327  *GetContext(), {1, 1}, 1u);
2328  testing::MockRenderPass render_pass(GetContext(), target);
2329  auto position_result =
2330  geom->GetPositionBuffer(*GetContentContext(), entity, render_pass);
2331 
2332  EXPECT_EQ(position_result.vertex_buffer.vertex_count, 0u);
2333 
2334  EXPECT_EQ(geom->GetResultMode(), GeometryResult::Mode::kNormal);
2335 }
2336 
2337 TEST_P(EntityTest, CanRenderEmptyPathsWithoutCrashing) {
2338  flutter::DlPath path = flutter::DlPath::MakeRect(Rect::MakeLTRB(0, 0, 0, 0));
2339 
2340  EXPECT_TRUE(path.GetBounds().IsEmpty());
2341 
2342  auto contents = std::make_shared<SolidColorContents>();
2343  std::unique_ptr<Geometry> geom = Geometry::MakeFillPath(path);
2344  contents->SetGeometry(geom.get());
2345  contents->SetColor(Color::Red());
2346 
2347  Entity entity;
2348  entity.SetTransform(Matrix::MakeScale(GetContentScale()));
2349  entity.SetContents(contents);
2350 
2351  ASSERT_TRUE(OpenPlaygroundHere(std::move(entity)));
2352 }
2353 
2354 TEST_P(EntityTest, DrawSuperEllipse) {
2355  auto callback = [&](ContentContext& context, RenderPass& pass) -> bool {
2356  // UI state.
2357  static float alpha = 10;
2358  static float beta = 10;
2359  static float radius = 40;
2360  static int degree = 4;
2361  static Color color = Color::Red();
2362 
2363  ImGui::Begin("Controls", nullptr, ImGuiWindowFlags_AlwaysAutoResize);
2364  ImGui::SliderFloat("Alpha", &alpha, 0, 100);
2365  ImGui::SliderFloat("Beta", &beta, 0, 100);
2366  ImGui::SliderInt("Degreee", &degree, 1, 20);
2367  ImGui::SliderFloat("Radius", &radius, 0, 400);
2368  ImGui::ColorEdit4("Color", reinterpret_cast<float*>(&color));
2369  ImGui::End();
2370 
2371  auto contents = std::make_shared<SolidColorContents>();
2372  std::unique_ptr<SuperellipseGeometry> geom =
2373  std::make_unique<SuperellipseGeometry>(Point{400, 400}, radius, degree,
2374  alpha, beta);
2375  contents->SetColor(color);
2376  contents->SetGeometry(geom.get());
2377 
2378  Entity entity;
2379  entity.SetContents(contents);
2380 
2381  return entity.Render(context, pass);
2382  };
2383 
2384  ASSERT_TRUE(OpenPlaygroundHere(callback));
2385 }
2386 
2387 TEST_P(EntityTest, DrawRoundSuperEllipse) {
2388  auto callback = [&](ContentContext& context, RenderPass& pass) -> bool {
2389  // UI state.
2390  static int style_index = 0;
2391  static float center[2] = {830, 830};
2392  static float size[2] = {600, 600};
2393  static bool horizontal_symmetry = true;
2394  static bool vertical_symmetry = true;
2395  static bool corner_symmetry = true;
2396 
2397  const char* style_options[] = {"Fill", "Stroke"};
2398 
2399  // Initially radius_tl[0] will be mirrored to all 8 values since all 3
2400  // symmetries are enabled.
2401  static std::array<float, 2> radius_tl = {200};
2402  static std::array<float, 2> radius_tr;
2403  static std::array<float, 2> radius_bl;
2404  static std::array<float, 2> radius_br;
2405 
2406  auto AddRadiusControl = [](std::array<float, 2>& radii, const char* tb_name,
2407  const char* lr_name) {
2408  std::string name = "Radius";
2409  if (!horizontal_symmetry || !vertical_symmetry) {
2410  name += ":";
2411  }
2412  if (!vertical_symmetry) {
2413  name = name + " " + tb_name;
2414  }
2415  if (!horizontal_symmetry) {
2416  name = name + " " + lr_name;
2417  }
2418  if (corner_symmetry) {
2419  ImGui::SliderFloat(name.c_str(), radii.data(), 0, 1000);
2420  } else {
2421  ImGui::SliderFloat2(name.c_str(), radii.data(), 0, 1000);
2422  }
2423  };
2424 
2425  if (corner_symmetry) {
2426  radius_tl[1] = radius_tl[0];
2427  radius_tr[1] = radius_tr[0];
2428  radius_bl[1] = radius_bl[0];
2429  radius_br[1] = radius_br[0];
2430  }
2431 
2432  ImGui::Begin("Controls", nullptr, ImGuiWindowFlags_AlwaysAutoResize);
2433  {
2434  ImGui::Combo("Style", &style_index, style_options,
2435  sizeof(style_options) / sizeof(char*));
2436  ImGui::SliderFloat2("Center", center, 0, 1000);
2437  ImGui::SliderFloat2("Size", size, 0, 1000);
2438  ImGui::Checkbox("Symmetry: Horizontal", &horizontal_symmetry);
2439  ImGui::Checkbox("Symmetry: Vertical", &vertical_symmetry);
2440  ImGui::Checkbox("Symmetry: Corners", &corner_symmetry);
2441  AddRadiusControl(radius_tl, "Top", "Left");
2442  if (!horizontal_symmetry) {
2443  AddRadiusControl(radius_tr, "Top", "Right");
2444  } else {
2445  radius_tr = radius_tl;
2446  }
2447  if (!vertical_symmetry) {
2448  AddRadiusControl(radius_bl, "Bottom", "Left");
2449  } else {
2450  radius_bl = radius_tl;
2451  }
2452  if (!horizontal_symmetry && !vertical_symmetry) {
2453  AddRadiusControl(radius_br, "Bottom", "Right");
2454  } else {
2455  if (horizontal_symmetry) {
2456  radius_br = radius_bl;
2457  } else {
2458  radius_br = radius_tr;
2459  }
2460  }
2461  }
2462 
2463  ImGui::End();
2464 
2465  RoundingRadii radii{
2466  .top_left = {radius_tl[0], radius_tl[1]},
2467  .top_right = {radius_tr[0], radius_tr[1]},
2468  .bottom_left = {radius_bl[0], radius_bl[1]},
2469  .bottom_right = {radius_br[0], radius_br[1]},
2470  };
2471 
2473  RectMakeCenterSize({center[0], center[1]}, {size[0], size[1]}), radii);
2474 
2475  flutter::DlPath path;
2476  std::unique_ptr<Geometry> geom;
2477  if (style_index == 0) {
2478  geom = std::make_unique<RoundSuperellipseGeometry>(
2479  RectMakeCenterSize({center[0], center[1]}, {size[0], size[1]}),
2480  radii);
2481  } else {
2482  path = flutter::DlPath::MakeRoundSuperellipse(rse);
2483  geom = Geometry::MakeStrokePath(path, {.width = 2.0f});
2484  }
2485 
2486  auto contents = std::make_shared<SolidColorContents>();
2487  contents->SetColor(Color::Red());
2488  contents->SetGeometry(geom.get());
2489 
2490  Entity entity;
2491  entity.SetContents(contents);
2492 
2493  return entity.Render(context, pass);
2494  };
2495 
2496  ASSERT_TRUE(OpenPlaygroundHere(callback));
2497 }
2498 
2499 TEST_P(EntityTest, SolidColorApplyColorFilter) {
2500  auto contents = SolidColorContents();
2501  contents.SetColor(Color::CornflowerBlue().WithAlpha(0.75));
2502  auto result = contents.ApplyColorFilter([](const Color& color) {
2503  return color.Blend(Color::LimeGreen().WithAlpha(0.75), BlendMode::kScreen);
2504  });
2505  ASSERT_TRUE(result);
2506  ASSERT_COLOR_NEAR(contents.GetColor(),
2507  Color(0.424452, 0.828743, 0.79105, 0.9375));
2508 }
2509 
2510 #define APPLY_COLOR_FILTER_GRADIENT_TEST(name) \
2511  TEST_P(EntityTest, name##GradientApplyColorFilter) { \
2512  auto contents = name##GradientContents(); \
2513  contents.SetColors({Color::CornflowerBlue().WithAlpha(0.75)}); \
2514  auto result = contents.ApplyColorFilter([](const Color& color) { \
2515  return color.Blend(Color::LimeGreen().WithAlpha(0.75), \
2516  BlendMode::kScreen); \
2517  }); \
2518  ASSERT_TRUE(result); \
2519  \
2520  std::vector<Color> expected = {Color(0.433247, 0.879523, 0.825324, 0.75)}; \
2521  ASSERT_COLORS_NEAR(contents.GetColors(), expected); \
2522  }
2523 
2528 
2529 TEST_P(EntityTest, GiantStrokePathAllocation) {
2530  flutter::DlPathBuilder builder;
2531  for (int i = 0; i < 10000; i++) {
2532  builder.LineTo(Point(i, i));
2533  }
2534  flutter::DlPath path = builder.TakePath();
2535  auto geom = Geometry::MakeStrokePath(path, {.width = 10.0f});
2536 
2537  ContentContext content_context(GetContext(), /*typographer_context=*/nullptr);
2538  Entity entity;
2539 
2540  auto cmd_buffer = content_context.GetContext()->CreateCommandBuffer();
2541 
2542  RenderTargetAllocator allocator(
2543  content_context.GetContext()->GetResourceAllocator());
2544 
2545  auto render_target = allocator.CreateOffscreen(
2546  *content_context.GetContext(), /*size=*/{10, 10}, /*mip_count=*/1);
2547  auto pass = cmd_buffer->CreateRenderPass(render_target);
2548 
2549  GeometryResult result =
2550  geom->GetPositionBuffer(content_context, entity, *pass);
2551 
2552  // Validate the buffer data overflowed the small buffer
2553  EXPECT_GT(result.vertex_buffer.vertex_count, kPointArenaSize);
2554 
2555  // Validate that there are no uninitialized points near the gap.
2556  Point* written_data = reinterpret_cast<Point*>(
2559 
2560  std::vector<Point> expected = {
2561  Point(2043.46, 2050.54), //
2562  Point(2050.54, 2043.46), //
2563  Point(2044.46, 2051.54), //
2564  Point(2051.54, 2044.46), //
2565  Point(2045.46, 2052.54) //
2566  };
2567 
2568  Point point = written_data[kPointArenaSize - 2];
2569  EXPECT_NEAR(point.x, expected[0].x, 0.1);
2570  EXPECT_NEAR(point.y, expected[0].y, 0.1);
2571 
2572  point = written_data[kPointArenaSize - 1];
2573  EXPECT_NEAR(point.x, expected[1].x, 0.1);
2574  EXPECT_NEAR(point.y, expected[1].y, 0.1);
2575 
2576  point = written_data[kPointArenaSize];
2577  EXPECT_NEAR(point.x, expected[2].x, 0.1);
2578  EXPECT_NEAR(point.y, expected[2].y, 0.1);
2579 
2580  point = written_data[kPointArenaSize + 1];
2581  EXPECT_NEAR(point.x, expected[3].x, 0.1);
2582  EXPECT_NEAR(point.y, expected[3].y, 0.1);
2583 
2584  point = written_data[kPointArenaSize + 2];
2585  EXPECT_NEAR(point.x, expected[4].x, 0.1);
2586  EXPECT_NEAR(point.y, expected[4].y, 0.1);
2587 }
2588 
2590  public:
2592  : DeviceBuffer(desc), storage_(desc.size) {}
2593 
2594  bool SetLabel(std::string_view label) override { return true; }
2595  bool SetLabel(std::string_view label, Range range) override { return true; }
2596  bool OnCopyHostBuffer(const uint8_t* source,
2597  Range source_range,
2598  size_t offset) {
2599  return true;
2600  }
2601 
2602  uint8_t* OnGetContents() const override {
2603  return const_cast<uint8_t*>(storage_.data());
2604  }
2605 
2606  void Flush(std::optional<Range> range) const override {
2607  flush_called_ = true;
2608  }
2609 
2610  bool flush_called() const { return flush_called_; }
2611 
2612  private:
2613  std::vector<uint8_t> storage_;
2614  mutable bool flush_called_ = false;
2615 };
2616 
2618  public:
2620  return ISize(1024, 1024);
2621  };
2622 
2623  std::shared_ptr<DeviceBuffer> OnCreateBuffer(
2624  const DeviceBufferDescriptor& desc) override {
2625  return std::make_shared<FlushTestDeviceBuffer>(desc);
2626  };
2627 
2628  std::shared_ptr<Texture> OnCreateTexture(const TextureDescriptor& desc,
2629  bool threadsafe) override {
2630  return nullptr;
2631  }
2632 };
2633 
2635  public:
2637  const std::shared_ptr<Context>& context,
2638  const std::shared_ptr<TypographerContext>& typographer_context,
2639  const std::shared_ptr<Allocator>& allocator)
2640  : ContentContext(context, typographer_context) {
2642  allocator, context->GetIdleWaiter(),
2643  context->GetCapabilities()->GetMinimumUniformAlignment()));
2645  allocator, context->GetIdleWaiter(),
2646  context->GetCapabilities()->GetMinimumUniformAlignment()));
2647  }
2648 };
2649 
2650 TEST_P(EntityTest, RoundSuperellipseGetPositionBufferFlushes) {
2651  RenderTarget target;
2652  testing::MockRenderPass mock_pass(GetContext(), target);
2653 
2654  auto content_context = std::make_shared<FlushTestContentContext>(
2655  GetContext(), GetTypographerContext(),
2656  std::make_shared<FlushTestAllocator>());
2657  auto geometry =
2658  Geometry::MakeRoundSuperellipse(Rect::MakeLTRB(0, 0, 100, 100), 5);
2659  auto result = geometry->GetPositionBuffer(*content_context, {}, mock_pass);
2660 
2661  auto device_buffer = reinterpret_cast<const FlushTestDeviceBuffer*>(
2662  result.vertex_buffer.vertex_buffer.GetBuffer());
2663  EXPECT_TRUE(device_buffer->flush_called());
2664 }
2665 
2666 } // namespace testing
2667 } // namespace impeller
2668 
2669 // NOLINTEND(bugprone-unchecked-optional-access)
BufferView buffer_view
An object that allocates device memory.
Definition: allocator.h:24
static std::shared_ptr< ColorFilterContents > MakeColorMatrix(FilterInput::Ref input, const ColorMatrix &color_matrix)
static std::shared_ptr< ColorFilterContents > MakeSrgbToLinearFilter(FilterInput::Ref input)
static std::shared_ptr< ColorFilterContents > MakeLinearToSrgbFilter(FilterInput::Ref input)
static std::shared_ptr< ColorFilterContents > MakeBlend(BlendMode blend_mode, FilterInput::Vector inputs, std::optional< Color > foreground_color=std::nullopt)
the [inputs] are expected to be in the order of dst, src.
void SetGeometry(const Geometry *geometry)
Set the geometry that this contents will use to render.
void SetColors(std::vector< Color > colors)
void SetTransientsDataBuffer(std::shared_ptr< HostBuffer > host_buffer)
PipelineRef GetSolidFillPipeline(ContentContextOptions opts) const
HostBuffer & GetTransientsIndexesBuffer() const
Retrieve the current host buffer for transient storage of indexes used for indexed draws.
HostBuffer & GetTransientsDataBuffer() const
Retrieve the current host buffer for transient storage of other non-index data.
void SetTransientsIndexesBuffer(std::shared_ptr< HostBuffer > host_buffer)
virtual bool IsOpaque(const Matrix &transform) const
Whether this Contents only emits opaque source colors from the fragment stage. This value does not ac...
Definition: contents.cc:52
To do anything rendering related with Impeller, you need a context.
Definition: context.h:65
virtual std::shared_ptr< CommandBuffer > CreateCommandBuffer() const =0
Create a new command buffer. Command buffers can be used to encode graphics, blit,...
virtual std::shared_ptr< CommandQueue > GetCommandQueue() const =0
Return the graphics queue for submitting command buffers.
virtual std::shared_ptr< Allocator > GetResourceAllocator() const =0
Returns the allocator used to create textures and buffers on the device.
static BufferView AsBufferView(std::shared_ptr< DeviceBuffer > buffer)
Create a buffer view of this entire buffer.
virtual uint8_t * OnGetContents() const =0
void SetTransform(const Matrix &transform)
Set the global transform matrix for this Entity.
Definition: entity.cc:60
std::optional< Rect > GetCoverage() const
Definition: entity.cc:64
BlendMode GetBlendMode() const
Definition: entity.cc:101
void SetContents(std::shared_ptr< Contents > contents)
Definition: entity.cc:72
void SetBlendMode(BlendMode blend_mode)
Definition: entity.cc:97
bool Render(const ContentContext &renderer, RenderPass &parent_pass) const
Definition: entity.cc:144
const Matrix & GetTransform() const
Get the global transform matrix for this Entity.
Definition: entity.cc:44
static constexpr BlendMode kLastPipelineBlendMode
Definition: entity.h:28
@ kNormal
Blurred inside and outside.
@ kOuter
Nothing inside, blurred outside.
@ kInner
Blurred inside, nothing outside.
@ kSolid
Solid inside, blurred outside.
static std::shared_ptr< FilterContents > MakeMorphology(FilterInput::Ref input, Radius radius_x, Radius radius_y, MorphType morph_type)
static std::shared_ptr< FilterContents > MakeBorderMaskBlur(FilterInput::Ref input, Sigma sigma_x, Sigma sigma_y, BlurStyle blur_style=BlurStyle::kNormal)
static std::shared_ptr< FilterContents > MakeGaussianBlur(const FilterInput::Ref &input, Sigma sigma_x, Sigma sigma_y, Entity::TileMode tile_mode=Entity::TileMode::kDecal, BlurStyle mask_blur_style=BlurStyle::kNormal, const Geometry *mask_geometry=nullptr)
static std::shared_ptr< FilterContents > MakeYUVToRGBFilter(std::shared_ptr< Texture > y_texture, std::shared_ptr< Texture > uv_texture, YUVColorSpace yuv_color_space)
static FilterInput::Ref Make(Variant input, bool msaa_enabled=true)
Definition: filter_input.cc:19
static std::unique_ptr< Geometry > MakeFillPath(const flutter::DlPath &path, std::optional< Rect > inner_rect=std::nullopt)
Definition: geometry.cc:62
static std::unique_ptr< Geometry > MakeRect(const Rect &rect)
Definition: geometry.cc:83
static std::unique_ptr< Geometry > MakeStrokePath(const flutter::DlPath &path, const StrokeParameters &stroke={})
Definition: geometry.cc:68
static std::unique_ptr< Geometry > MakeRoundSuperellipse(const Rect &rect, Scalar corner_radius)
Definition: geometry.cc:130
static std::unique_ptr< Geometry > MakeCover()
Definition: geometry.cc:79
static std::shared_ptr< HostBuffer > Create(const std::shared_ptr< Allocator > &allocator, const std::shared_ptr< const IdleWaiter > &idle_waiter, size_t minimum_uniform_alignment)
Definition: host_buffer.cc:21
BufferView EmplaceUniform(const UniformType &uniform)
Emplace uniform data onto the host buffer. Ensure that backend specific uniform alignment requirement...
Definition: host_buffer.h:47
void SetTileMode(Entity::TileMode tile_mode)
void SetColors(std::vector< Color > colors)
bool IsOpaque(const Matrix &transform) const override
Whether this Contents only emits opaque source colors from the fragment stage. This value does not ac...
A geometry class specialized for Canvas::DrawPoints.
std::optional< Rect > GetCoverage(const Matrix &transform) const override
bool IsOpaque(const Matrix &transform) const override
Whether this Contents only emits opaque source colors from the fragment stage. This value does not ac...
void SetTileMode(Entity::TileMode tile_mode)
void SetColors(std::vector< Color > colors)
Render passes encode render commands directed as one specific render target into an underlying comman...
Definition: render_pass.h:30
FragmentShader_ FragmentShader
Definition: pipeline.h:164
a wrapper around the impeller [Allocator] instance that can be used to provide caching of allocated r...
virtual RenderTarget CreateOffscreen(const Context &context, ISize size, int mip_count, std::string_view label="Offscreen", RenderTarget::AttachmentConfig color_attachment_config=RenderTarget::kDefaultColorAttachmentConfig, std::optional< RenderTarget::AttachmentConfig > stencil_attachment_config=RenderTarget::kDefaultStencilAttachmentConfig, const std::shared_ptr< Texture > &existing_color_texture=nullptr, const std::shared_ptr< Texture > &existing_depth_stencil_texture=nullptr)
static BufferView EmplaceVulkanUniform(const std::shared_ptr< const std::vector< uint8_t >> &input_data, HostBuffer &host_buffer, const RuntimeUniformDescription &uniform, size_t minimum_uniform_alignment)
bool IsOpaque(const Matrix &transform) const override
Whether this Contents only emits opaque source colors from the fragment stage. This value does not ac...
A Geometry that produces fillable vertices representing the stroked outline of a |DlPath| object usin...
static Rational RoundScaledFontSize(Scalar scale)
Definition: text_frame.cc:55
static std::shared_ptr< TextureContents > MakeRect(Rect destination)
bool IsOpaque(const Matrix &transform) const override
Whether this Contents only emits opaque source colors from the fragment stage. This value does not ac...
void SetTexture(std::shared_ptr< Texture > texture)
VertexBuffer CreateVertexBuffer(HostBuffer &data_host_buffer, HostBuffer &indexes_host_buffer) const
VertexBufferBuilder & AddVertices(std::initializer_list< VertexType_ > vertices)
std::shared_ptr< Texture > OnCreateTexture(const TextureDescriptor &desc, bool threadsafe) override
std::shared_ptr< DeviceBuffer > OnCreateBuffer(const DeviceBufferDescriptor &desc) override
ISize GetMaxTextureSizeSupported() const override
FlushTestContentContext(const std::shared_ptr< Context > &context, const std::shared_ptr< TypographerContext > &typographer_context, const std::shared_ptr< Allocator > &allocator)
bool OnCopyHostBuffer(const uint8_t *source, Range source_range, size_t offset)
void Flush(std::optional< Range > range) const override
bool SetLabel(std::string_view label, Range range) override
bool SetLabel(std::string_view label) override
FlushTestDeviceBuffer(const DeviceBufferDescriptor &desc)
Vector2 blur_radius
Blur radius in source pixels based on scaled_sigma.
Vector2 padding
The halo padding in source space.
#define ASSERT_RECT_NEAR(a, b)
#define ASSERT_COLOR_NEAR(a, b)
Rect RectMakeCenterSize(Point center, Size size)
TEST_P(AiksTest, DrawAtlasNoColor)
APPLY_COLOR_FILTER_GRADIENT_TEST(Linear)
INSTANTIATE_PLAYGROUND_SUITE(AiksTest)
static Vector3 RGBToYUV(Vector3 rgb, YUVColorSpace yuv_color_space)
static std::vector< std::shared_ptr< Texture > > CreateTestYUVTextures(Context *context, YUVColorSpace yuv_color_space)
YUVColorSpace
Definition: color.h:54
Join
An enum that describes ways to join two segments of a path.
@ kPoint
Draws a point at each input vertex.
Point Vector2
Definition: point.h:331
constexpr float kPi
Definition: constants.h:26
float Scalar
Definition: scalar.h:19
Point DrawPlaygroundPoint(PlaygroundPoint &point)
Definition: widgets.cc:11
std::tuple< Point, Point > DrawPlaygroundLine(PlaygroundPoint &point_a, PlaygroundPoint &point_b)
Definition: widgets.cc:51
constexpr RuntimeStageBackend PlaygroundBackendToRuntimeStageBackend(PlaygroundBackend backend)
Definition: playground.h:33
TPoint< Scalar > Point
Definition: point.h:327
Cap
An enum that describes ways to decorate the end of a path contour.
LinePipeline::FragmentShader FS
constexpr float kPiOver2
Definition: constants.h:32
flutter::DlPath DlPath
Definition: dl_dispatcher.h:29
BlendMode
Definition: color.h:58
LinePipeline::VertexShader VS
void MoveTo(PathBuilder *builder, Scalar x, Scalar y)
Definition: tessellator.cc:20
static constexpr size_t kPointArenaSize
The size of the point arena buffer stored on the tessellator.
Definition: tessellator.h:25
void LineTo(PathBuilder *builder, Scalar x, Scalar y)
Definition: tessellator.cc:24
ContentContextOptions OptionsFromPass(const RenderPass &pass)
Definition: contents.cc:19
ISize64 ISize
Definition: size.h:162
void Close(PathBuilder *builder)
Definition: tessellator.cc:38
Range GetRange() const
Definition: buffer_view.h:27
const DeviceBuffer * GetBuffer() const
Definition: buffer_view.cc:17
static constexpr Color LimeGreen()
Definition: color.h:602
static constexpr Color MintCream()
Definition: color.h:658
Scalar alpha
Definition: color.h:143
static constexpr Color DeepPink()
Definition: color.h:434
static constexpr Color Black()
Definition: color.h:266
static constexpr Color CornflowerBlue()
Definition: color.h:342
static constexpr Color White()
Definition: color.h:264
constexpr Color WithAlpha(Scalar new_alpha) const
Definition: color.h:278
static constexpr Color WhiteTransparent()
Definition: color.h:268
static constexpr Color Coral()
Definition: color.h:338
static constexpr Color Red()
Definition: color.h:272
constexpr Color Premultiply() const
Definition: color.h:212
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
static constexpr Color Blue()
Definition: color.h:276
static constexpr Color Green()
Definition: color.h:274
Scalar array[20]
Definition: color.h:118
constexpr uint64_t ToKey() const
@ kNormal
The geometry has no overlapping triangles.
VertexBuffer vertex_buffer
Definition: geometry.h:38
A 4x4 matrix using column-major storage.
Definition: matrix.h:37
static constexpr Matrix MakeTranslation(const Vector3 &t)
Definition: matrix.h:95
constexpr bool IsIdentity() const
Definition: matrix.h:467
static Matrix MakeRotationY(Radians r)
Definition: matrix.h:208
static constexpr Matrix MakeSkew(Scalar sx, Scalar sy)
Definition: matrix.h:127
static Matrix MakeRotationZ(Radians r)
Definition: matrix.h:223
static constexpr Matrix MakeScale(const Vector3 &s)
Definition: matrix.h:104
static Matrix MakeRotationX(Radians r)
Definition: matrix.h:193
For convolution filters, the "radius" is the size of the convolution kernel to use on the local space...
Definition: sigma.h:48
size_t offset
Definition: range.h:14
static RoundSuperellipse MakeRectRadii(const Rect &rect, const RoundingRadii &radii)
In filters that use Gaussian distributions, "sigma" is a size of one standard deviation in terms of t...
Definition: sigma.h:32
constexpr static TRect MakeXYWH(Type x, Type y, Type width, Type height)
Definition: rect.h:136
constexpr TRect< T > Shift(T dx, T dy) const
Returns a new rectangle translated by the given offset.
Definition: rect.h:602
constexpr static TRect MakeSize(const TSize< U > &size)
Definition: rect.h:150
constexpr static TRect MakeLTRB(Type left, Type top, Type right, Type bottom)
Definition: rect.h:129
A lightweight object that describes the attributes of a texture that can then used an allocator to cr...
std::vector< Point > points