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