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