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 <unordered_map>
10 #include <utility>
11 #include <vector>
12 
13 #include "flutter/testing/testing.h"
14 #include "fml/logging.h"
15 #include "fml/time/time_point.h"
16 #include "gtest/gtest.h"
36 #include "impeller/entity/entity.h"
57 #include "include/core/SkBlendMode.h"
58 #include "third_party/imgui/imgui.h"
59 #include "third_party/skia/include/core/SkTextBlob.h"
60 
61 // TODO(zanderso): https://github.com/flutter/flutter/issues/127701
62 // NOLINTBEGIN(bugprone-unchecked-optional-access)
63 
64 namespace impeller {
65 namespace testing {
66 
69 
70 TEST_P(EntityTest, CanCreateEntity) {
71  Entity entity;
72  ASSERT_TRUE(entity.GetTransformation().IsIdentity());
73 }
74 
75 class TestPassDelegate final : public EntityPassDelegate {
76  public:
77  explicit TestPassDelegate(bool collapse = false) : collapse_(collapse) {}
78 
79  // |EntityPassDelegate|
80  ~TestPassDelegate() override = default;
81 
82  // |EntityPassDelgate|
83  bool CanElide() override { return false; }
84 
85  // |EntityPassDelgate|
86  bool CanCollapseIntoParentPass(EntityPass* entity_pass) override {
87  return collapse_;
88  }
89 
90  // |EntityPassDelgate|
91  std::shared_ptr<Contents> CreateContentsForSubpassTarget(
92  std::shared_ptr<Texture> target,
93  const Matrix& transform) override {
94  return nullptr;
95  }
96 
97  // |EntityPassDelegate|
98  std::shared_ptr<FilterContents> WithImageFilter(
99  const FilterInput::Variant& input,
100  const Matrix& effect_transform) const override {
101  return nullptr;
102  }
103 
104  private:
105  const std::optional<Rect> coverage_;
106  const bool collapse_;
107 };
108 
110  std::optional<Rect> bounds_hint,
111  bool collapse = false) {
112  auto subpass = std::make_unique<EntityPass>();
113  Entity entity;
115  PathBuilder{}.AddRect(rect).TakePath(), Color::Red()));
116  subpass->AddEntity(entity);
117  subpass->SetDelegate(std::make_unique<TestPassDelegate>(collapse));
118  subpass->SetBoundsLimit(bounds_hint);
119  return subpass;
120 }
121 
122 TEST_P(EntityTest, EntityPassRespectsSubpassBoundsLimit) {
123  EntityPass pass;
124 
125  auto subpass0 = CreatePassWithRectPath(Rect::MakeLTRB(0, 0, 100, 100),
126  Rect::MakeLTRB(50, 50, 150, 150));
127  auto subpass1 = CreatePassWithRectPath(Rect::MakeLTRB(500, 500, 1000, 1000),
128  Rect::MakeLTRB(800, 800, 900, 900));
129 
130  auto subpass0_coverage =
131  pass.GetSubpassCoverage(*subpass0.get(), std::nullopt);
132  ASSERT_TRUE(subpass0_coverage.has_value());
133  ASSERT_RECT_NEAR(subpass0_coverage.value(), Rect::MakeLTRB(50, 50, 100, 100));
134 
135  auto subpass1_coverage =
136  pass.GetSubpassCoverage(*subpass1.get(), std::nullopt);
137  ASSERT_TRUE(subpass1_coverage.has_value());
138  ASSERT_RECT_NEAR(subpass1_coverage.value(),
139  Rect::MakeLTRB(800, 800, 900, 900));
140 
141  pass.AddSubpass(std::move(subpass0));
142  pass.AddSubpass(std::move(subpass1));
143 
144  auto coverage = pass.GetElementsCoverage(std::nullopt);
145  ASSERT_TRUE(coverage.has_value());
146  ASSERT_RECT_NEAR(coverage.value(), Rect::MakeLTRB(50, 50, 900, 900));
147 }
148 
149 TEST_P(EntityTest, EntityPassCanMergeSubpassIntoParent) {
150  // Both a red and a blue box should appear if the pass merging has worked
151  // correctly.
152 
153  EntityPass pass;
154  auto subpass = CreatePassWithRectPath(Rect::MakeLTRB(0, 0, 100, 100),
155  Rect::MakeLTRB(50, 50, 150, 150), true);
156  pass.AddSubpass(std::move(subpass));
157 
158  Entity entity;
159  entity.SetTransformation(Matrix::MakeScale(GetContentScale()));
160  auto contents = std::make_unique<SolidColorContents>();
161  contents->SetGeometry(Geometry::MakeRect(Rect::MakeLTRB(100, 100, 200, 200)));
162  contents->SetColor(Color::Blue());
163  entity.SetContents(std::move(contents));
164 
165  pass.AddEntity(entity);
166 
167  ASSERT_TRUE(OpenPlaygroundHere(pass));
168 }
169 
170 TEST_P(EntityTest, EntityPassCoverageRespectsCoverageLimit) {
171  // Rect is drawn entirely in negative area.
172  auto pass = CreatePassWithRectPath(Rect::MakeLTRB(-200, -200, -100, -100),
173  std::nullopt);
174 
175  // Without coverage limit.
176  {
177  auto pass_coverage = pass->GetElementsCoverage(std::nullopt);
178  ASSERT_TRUE(pass_coverage.has_value());
179  ASSERT_RECT_NEAR(pass_coverage.value(),
180  Rect::MakeLTRB(-200, -200, -100, -100));
181  }
182 
183  // With limit that doesn't overlap.
184  {
185  auto pass_coverage =
186  pass->GetElementsCoverage(Rect::MakeLTRB(0, 0, 100, 100));
187  ASSERT_FALSE(pass_coverage.has_value());
188  }
189 
190  // With limit that partially overlaps.
191  {
192  auto pass_coverage =
193  pass->GetElementsCoverage(Rect::MakeLTRB(-150, -150, 0, 0));
194  ASSERT_TRUE(pass_coverage.has_value());
195  ASSERT_RECT_NEAR(pass_coverage.value(),
196  Rect::MakeLTRB(-150, -150, -100, -100));
197  }
198 }
199 
200 TEST_P(EntityTest, FilterCoverageRespectsCropRect) {
201  auto image = CreateTextureForFixture("boston.jpg");
203  FilterInput::Make({image}));
204 
205  // Without the crop rect (default behavior).
206  {
207  auto actual = filter->GetCoverage({});
208  auto expected = Rect::MakeSize(image->GetSize());
209 
210  ASSERT_TRUE(actual.has_value());
211  ASSERT_RECT_NEAR(actual.value(), expected);
212  }
213 
214  // With the crop rect.
215  {
216  auto expected = Rect::MakeLTRB(50, 50, 100, 100);
217  filter->SetCoverageHint(expected);
218  auto actual = filter->GetCoverage({});
219 
220  ASSERT_TRUE(actual.has_value());
221  ASSERT_RECT_NEAR(actual.value(), expected);
222  }
223 }
224 
225 TEST_P(EntityTest, CanDrawRect) {
226  auto contents = std::make_shared<SolidColorContents>();
227  contents->SetGeometry(Geometry::MakeRect({100, 100, 100, 100}));
228  contents->SetColor(Color::Red());
229 
230  Entity entity;
231  entity.SetTransformation(Matrix::MakeScale(GetContentScale()));
232  entity.SetContents(contents);
233 
234  ASSERT_TRUE(OpenPlaygroundHere(entity));
235 }
236 
237 TEST_P(EntityTest, CanDrawRRect) {
238  auto contents = std::make_shared<SolidColorContents>();
239  auto path = PathBuilder{}
241  .AddRoundedRect({100, 100, 100, 100}, 10.0)
242  .TakePath();
243  contents->SetGeometry(Geometry::MakeFillPath(path));
244  contents->SetColor(Color::Red());
245 
246  Entity entity;
247  entity.SetTransformation(Matrix::MakeScale(GetContentScale()));
248  entity.SetContents(contents);
249 
250  ASSERT_TRUE(OpenPlaygroundHere(entity));
251 }
252 
253 TEST_P(EntityTest, GeometryBoundsAreTransformed) {
254  auto geometry = Geometry::MakeRect({100, 100, 100, 100});
255  auto transform = Matrix::MakeScale({2.0, 2.0, 2.0});
256 
257  ASSERT_RECT_NEAR(geometry->GetCoverage(transform).value(),
258  Rect(200, 200, 200, 200));
259 }
260 
261 TEST_P(EntityTest, ThreeStrokesInOnePath) {
262  Path path = PathBuilder{}
263  .MoveTo({100, 100})
264  .LineTo({100, 200})
265  .MoveTo({100, 300})
266  .LineTo({100, 400})
267  .MoveTo({100, 500})
268  .LineTo({100, 600})
269  .TakePath();
270 
271  Entity entity;
272  entity.SetTransformation(Matrix::MakeScale(GetContentScale()));
273  auto contents = std::make_unique<SolidColorContents>();
274  contents->SetGeometry(Geometry::MakeStrokePath(path, 5.0));
275  contents->SetColor(Color::Red());
276  entity.SetContents(std::move(contents));
277  ASSERT_TRUE(OpenPlaygroundHere(entity));
278 }
279 
280 TEST_P(EntityTest, StrokeWithTextureContents) {
281  auto bridge = CreateTextureForFixture("bay_bridge.jpg");
282  Path path = PathBuilder{}
283  .MoveTo({100, 100})
284  .LineTo({100, 200})
285  .MoveTo({100, 300})
286  .LineTo({100, 400})
287  .MoveTo({100, 500})
288  .LineTo({100, 600})
289  .TakePath();
290 
291  Entity entity;
292  entity.SetTransformation(Matrix::MakeScale(GetContentScale()));
293  auto contents = std::make_unique<TiledTextureContents>();
294  contents->SetGeometry(Geometry::MakeStrokePath(path, 100.0));
295  contents->SetTexture(bridge);
296  contents->SetTileModes(Entity::TileMode::kClamp, Entity::TileMode::kClamp);
297  entity.SetContents(std::move(contents));
298  ASSERT_TRUE(OpenPlaygroundHere(entity));
299 }
300 
301 TEST_P(EntityTest, TriangleInsideASquare) {
302  auto callback = [&](ContentContext& context, RenderPass& pass) {
303  Point offset(100, 100);
304 
305  Point a =
306  IMPELLER_PLAYGROUND_POINT(Point(10, 10) + offset, 20, Color::White());
307  Point b =
308  IMPELLER_PLAYGROUND_POINT(Point(210, 10) + offset, 20, Color::White());
309  Point c =
310  IMPELLER_PLAYGROUND_POINT(Point(210, 210) + offset, 20, Color::White());
311  Point d =
312  IMPELLER_PLAYGROUND_POINT(Point(10, 210) + offset, 20, Color::White());
313  Point e =
314  IMPELLER_PLAYGROUND_POINT(Point(50, 50) + offset, 20, Color::White());
315  Point f =
316  IMPELLER_PLAYGROUND_POINT(Point(100, 50) + offset, 20, Color::White());
317  Point g =
318  IMPELLER_PLAYGROUND_POINT(Point(50, 150) + offset, 20, Color::White());
319  Path path = PathBuilder{}
320  .MoveTo(a)
321  .LineTo(b)
322  .LineTo(c)
323  .LineTo(d)
324  .Close()
325  .MoveTo(e)
326  .LineTo(f)
327  .LineTo(g)
328  .Close()
329  .TakePath();
330 
331  Entity entity;
332  entity.SetTransformation(Matrix::MakeScale(GetContentScale()));
333  auto contents = std::make_unique<SolidColorContents>();
334  contents->SetGeometry(Geometry::MakeStrokePath(path, 20.0));
335  contents->SetColor(Color::Red());
336  entity.SetContents(std::move(contents));
337 
338  return entity.Render(context, pass);
339  };
340  ASSERT_TRUE(OpenPlaygroundHere(callback));
341 }
342 
343 TEST_P(EntityTest, StrokeCapAndJoinTest) {
344  const Point padding(300, 250);
345  const Point margin(140, 180);
346 
347  auto callback = [&](ContentContext& context, RenderPass& pass) {
348  // Slightly above sqrt(2) by default, so that right angles are just below
349  // the limit and acute angles are over the limit (causing them to get
350  // beveled).
351  static Scalar miter_limit = 1.41421357;
352  static Scalar width = 30;
353 
354  ImGui::Begin("Controls", nullptr, ImGuiWindowFlags_AlwaysAutoResize);
355  {
356  ImGui::SliderFloat("Miter limit", &miter_limit, 0, 30);
357  ImGui::SliderFloat("Stroke width", &width, 0, 100);
358  if (ImGui::Button("Reset")) {
359  miter_limit = 1.41421357;
360  width = 30;
361  }
362  }
363  ImGui::End();
364 
365  auto world_matrix = Matrix::MakeScale(GetContentScale());
366  auto render_path = [width = width, &context, &pass, &world_matrix](
367  const Path& path, Cap cap, Join join) {
368  auto contents = std::make_unique<SolidColorContents>();
369  contents->SetGeometry(
370  Geometry::MakeStrokePath(path, width, miter_limit, cap, join));
371  contents->SetColor(Color::Red());
372 
373  Entity entity;
374  entity.SetTransformation(world_matrix);
375  entity.SetContents(std::move(contents));
376 
377  auto coverage = entity.GetCoverage();
378  if (coverage.has_value()) {
379  auto bounds_contents = std::make_unique<SolidColorContents>();
380  bounds_contents->SetGeometry(Geometry::MakeFillPath(
381  PathBuilder{}.AddRect(entity.GetCoverage().value()).TakePath()));
382  bounds_contents->SetColor(Color::Green().WithAlpha(0.5));
383  Entity bounds_entity;
384  bounds_entity.SetContents(std::move(bounds_contents));
385  bounds_entity.Render(context, pass);
386  }
387 
388  entity.Render(context, pass);
389  };
390 
391  const Point a_def(0, 0), b_def(0, 100), c_def(150, 0), d_def(150, -100),
392  e_def(75, 75);
393  const Scalar r = 30;
394  // Cap::kButt demo.
395  {
396  Point off = Point(0, 0) * padding + margin;
397  auto [a, b] = IMPELLER_PLAYGROUND_LINE(off + a_def, off + b_def, r,
399  auto [c, d] = IMPELLER_PLAYGROUND_LINE(off + c_def, off + d_def, r,
401  render_path(PathBuilder{}.AddCubicCurve(a, b, d, c).TakePath(),
403  }
404 
405  // Cap::kSquare demo.
406  {
407  Point off = Point(1, 0) * padding + margin;
408  auto [a, b] = IMPELLER_PLAYGROUND_LINE(off + a_def, off + b_def, r,
410  auto [c, d] = IMPELLER_PLAYGROUND_LINE(off + c_def, off + d_def, r,
412  render_path(PathBuilder{}.AddCubicCurve(a, b, d, c).TakePath(),
414  }
415 
416  // Cap::kRound demo.
417  {
418  Point off = Point(2, 0) * padding + margin;
419  auto [a, b] = IMPELLER_PLAYGROUND_LINE(off + a_def, off + b_def, r,
421  auto [c, d] = IMPELLER_PLAYGROUND_LINE(off + c_def, off + d_def, r,
423  render_path(PathBuilder{}.AddCubicCurve(a, b, d, c).TakePath(),
425  }
426 
427  // Join::kBevel demo.
428  {
429  Point off = Point(0, 1) * padding + margin;
430  Point a = IMPELLER_PLAYGROUND_POINT(off + a_def, r, Color::White());
431  Point b = IMPELLER_PLAYGROUND_POINT(off + e_def, r, Color::White());
432  Point c = IMPELLER_PLAYGROUND_POINT(off + c_def, r, Color::White());
433  render_path(
434  PathBuilder{}.MoveTo(a).LineTo(b).LineTo(c).Close().TakePath(),
436  }
437 
438  // Join::kMiter demo.
439  {
440  Point off = Point(1, 1) * padding + margin;
441  Point a = IMPELLER_PLAYGROUND_POINT(off + a_def, r, Color::White());
442  Point b = IMPELLER_PLAYGROUND_POINT(off + e_def, r, Color::White());
443  Point c = IMPELLER_PLAYGROUND_POINT(off + c_def, r, Color::White());
444  render_path(
445  PathBuilder{}.MoveTo(a).LineTo(b).LineTo(c).Close().TakePath(),
447  }
448 
449  // Join::kRound demo.
450  {
451  Point off = Point(2, 1) * padding + margin;
452  Point a = IMPELLER_PLAYGROUND_POINT(off + a_def, r, Color::White());
453  Point b = IMPELLER_PLAYGROUND_POINT(off + e_def, r, Color::White());
454  Point c = IMPELLER_PLAYGROUND_POINT(off + c_def, r, Color::White());
455  render_path(
456  PathBuilder{}.MoveTo(a).LineTo(b).LineTo(c).Close().TakePath(),
458  }
459 
460  return true;
461  };
462  ASSERT_TRUE(OpenPlaygroundHere(callback));
463 }
464 
465 TEST_P(EntityTest, CubicCurveTest) {
466  // Compare with https://fiddle.skia.org/c/b3625f26122c9de7afe7794fcf25ead3
467  Path path =
468  PathBuilder{}
469  .MoveTo({237.164, 125.003})
470  .CubicCurveTo({236.709, 125.184}, {236.262, 125.358},
471  {235.81, 125.538})
472  .CubicCurveTo({235.413, 125.68}, {234.994, 125.832},
473  {234.592, 125.977})
474  .CubicCurveTo({234.592, 125.977}, {234.591, 125.977},
475  {234.59, 125.977})
476  .CubicCurveTo({222.206, 130.435}, {207.708, 135.753},
477  {192.381, 141.429})
478  .CubicCurveTo({162.77, 151.336}, {122.17, 156.894}, {84.1123, 160})
479  .Close()
480  .TakePath();
481  Entity entity;
482  entity.SetTransformation(Matrix::MakeScale(GetContentScale()));
484  ASSERT_TRUE(OpenPlaygroundHere(entity));
485 }
486 
487 TEST_P(EntityTest, CanDrawCorrectlyWithRotatedTransformation) {
488  auto callback = [&](ContentContext& context, RenderPass& pass) -> bool {
489  const char* input_axis[] = {"X", "Y", "Z"};
490  static int rotation_axis_index = 0;
491  static float rotation = 0;
492  ImGui::Begin("Controls", nullptr, ImGuiWindowFlags_AlwaysAutoResize);
493  ImGui::SliderFloat("Rotation", &rotation, -kPi, kPi);
494  ImGui::Combo("Rotation Axis", &rotation_axis_index, input_axis,
495  sizeof(input_axis) / sizeof(char*));
496  Matrix rotation_matrix;
497  switch (rotation_axis_index) {
498  case 0:
499  rotation_matrix = Matrix::MakeRotationX(Radians(rotation));
500  break;
501  case 1:
502  rotation_matrix = Matrix::MakeRotationY(Radians(rotation));
503  break;
504  case 2:
505  rotation_matrix = Matrix::MakeRotationZ(Radians(rotation));
506  break;
507  default:
508  rotation_matrix = Matrix{};
509  break;
510  }
511 
512  if (ImGui::Button("Reset")) {
513  rotation = 0;
514  }
515  ImGui::End();
516  Matrix current_transform =
517  Matrix::MakeScale(GetContentScale())
519  Vector3(Point(pass.GetRenderTargetSize().width / 2.0,
520  pass.GetRenderTargetSize().height / 2.0)));
521  Matrix result_transform = current_transform * rotation_matrix;
522  Path path =
523  PathBuilder{}.AddRect(Rect::MakeXYWH(-300, -400, 600, 800)).TakePath();
524 
525  Entity entity;
526  entity.SetTransformation(result_transform);
528  return entity.Render(context, pass);
529  };
530  ASSERT_TRUE(OpenPlaygroundHere(callback));
531 }
532 
533 TEST_P(EntityTest, CubicCurveAndOverlapTest) {
534  // Compare with https://fiddle.skia.org/c/7a05a3e186c65a8dfb732f68020aae06
535  Path path =
536  PathBuilder{}
537  .MoveTo({359.934, 96.6335})
538  .CubicCurveTo({358.189, 96.7055}, {356.436, 96.7908},
539  {354.673, 96.8895})
540  .CubicCurveTo({354.571, 96.8953}, {354.469, 96.9016},
541  {354.367, 96.9075})
542  .CubicCurveTo({352.672, 97.0038}, {350.969, 97.113},
543  {349.259, 97.2355})
544  .CubicCurveTo({349.048, 97.2506}, {348.836, 97.2678},
545  {348.625, 97.2834})
546  .CubicCurveTo({347.019, 97.4014}, {345.407, 97.5299},
547  {343.789, 97.6722})
548  .CubicCurveTo({343.428, 97.704}, {343.065, 97.7402},
549  {342.703, 97.7734})
550  .CubicCurveTo({341.221, 97.9086}, {339.736, 98.0505},
551  {338.246, 98.207})
552  .CubicCurveTo({337.702, 98.2642}, {337.156, 98.3292},
553  {336.612, 98.3894})
554  .CubicCurveTo({335.284, 98.5356}, {333.956, 98.6837},
555  {332.623, 98.8476})
556  .CubicCurveTo({332.495, 98.8635}, {332.366, 98.8818},
557  {332.237, 98.8982})
558  .LineTo({332.237, 102.601})
559  .LineTo({321.778, 102.601})
560  .LineTo({321.778, 100.382})
561  .CubicCurveTo({321.572, 100.413}, {321.367, 100.442},
562  {321.161, 100.476})
563  .CubicCurveTo({319.22, 100.79}, {317.277, 101.123},
564  {315.332, 101.479})
565  .CubicCurveTo({315.322, 101.481}, {315.311, 101.482},
566  {315.301, 101.484})
567  .LineTo({310.017, 105.94})
568  .LineTo({309.779, 105.427})
569  .LineTo({314.403, 101.651})
570  .CubicCurveTo({314.391, 101.653}, {314.379, 101.656},
571  {314.368, 101.658})
572  .CubicCurveTo({312.528, 102.001}, {310.687, 102.366},
573  {308.846, 102.748})
574  .CubicCurveTo({307.85, 102.955}, {306.855, 103.182}, {305.859, 103.4})
575  .CubicCurveTo({305.048, 103.579}, {304.236, 103.75},
576  {303.425, 103.936})
577  .LineTo({299.105, 107.578})
578  .LineTo({298.867, 107.065})
579  .LineTo({302.394, 104.185})
580  .LineTo({302.412, 104.171})
581  .CubicCurveTo({301.388, 104.409}, {300.366, 104.67},
582  {299.344, 104.921})
583  .CubicCurveTo({298.618, 105.1}, {297.89, 105.269}, {297.165, 105.455})
584  .CubicCurveTo({295.262, 105.94}, {293.36, 106.445},
585  {291.462, 106.979})
586  .CubicCurveTo({291.132, 107.072}, {290.802, 107.163},
587  {290.471, 107.257})
588  .CubicCurveTo({289.463, 107.544}, {288.455, 107.839},
589  {287.449, 108.139})
590  .CubicCurveTo({286.476, 108.431}, {285.506, 108.73},
591  {284.536, 109.035})
592  .CubicCurveTo({283.674, 109.304}, {282.812, 109.579},
593  {281.952, 109.859})
594  .CubicCurveTo({281.177, 110.112}, {280.406, 110.377},
595  {279.633, 110.638})
596  .CubicCurveTo({278.458, 111.037}, {277.256, 111.449},
597  {276.803, 111.607})
598  .CubicCurveTo({276.76, 111.622}, {276.716, 111.637},
599  {276.672, 111.653})
600  .CubicCurveTo({275.017, 112.239}, {273.365, 112.836},
601  {271.721, 113.463})
602  .LineTo({271.717, 113.449})
603  .CubicCurveTo({271.496, 113.496}, {271.238, 113.559},
604  {270.963, 113.628})
605  .CubicCurveTo({270.893, 113.645}, {270.822, 113.663},
606  {270.748, 113.682})
607  .CubicCurveTo({270.468, 113.755}, {270.169, 113.834},
608  {269.839, 113.926})
609  .CubicCurveTo({269.789, 113.94}, {269.732, 113.957},
610  {269.681, 113.972})
611  .CubicCurveTo({269.391, 114.053}, {269.081, 114.143},
612  {268.756, 114.239})
613  .CubicCurveTo({268.628, 114.276}, {268.5, 114.314},
614  {268.367, 114.354})
615  .CubicCurveTo({268.172, 114.412}, {267.959, 114.478},
616  {267.752, 114.54})
617  .CubicCurveTo({263.349, 115.964}, {258.058, 117.695},
618  {253.564, 119.252})
619  .CubicCurveTo({253.556, 119.255}, {253.547, 119.258},
620  {253.538, 119.261})
621  .CubicCurveTo({251.844, 119.849}, {250.056, 120.474},
622  {248.189, 121.131})
623  .CubicCurveTo({248, 121.197}, {247.812, 121.264}, {247.621, 121.331})
624  .CubicCurveTo({247.079, 121.522}, {246.531, 121.715},
625  {245.975, 121.912})
626  .CubicCurveTo({245.554, 122.06}, {245.126, 122.212},
627  {244.698, 122.364})
628  .CubicCurveTo({244.071, 122.586}, {243.437, 122.811},
629  {242.794, 123.04})
630  .CubicCurveTo({242.189, 123.255}, {241.58, 123.472},
631  {240.961, 123.693})
632  .CubicCurveTo({240.659, 123.801}, {240.357, 123.909},
633  {240.052, 124.018})
634  .CubicCurveTo({239.12, 124.351}, {238.18, 124.687}, {237.22, 125.032})
635  .LineTo({237.164, 125.003})
636  .CubicCurveTo({236.709, 125.184}, {236.262, 125.358},
637  {235.81, 125.538})
638  .CubicCurveTo({235.413, 125.68}, {234.994, 125.832},
639  {234.592, 125.977})
640  .CubicCurveTo({234.592, 125.977}, {234.591, 125.977},
641  {234.59, 125.977})
642  .CubicCurveTo({222.206, 130.435}, {207.708, 135.753},
643  {192.381, 141.429})
644  .CubicCurveTo({162.77, 151.336}, {122.17, 156.894}, {84.1123, 160})
645  .LineTo({360, 160})
646  .LineTo({360, 119.256})
647  .LineTo({360, 106.332})
648  .LineTo({360, 96.6307})
649  .CubicCurveTo({359.978, 96.6317}, {359.956, 96.6326},
650  {359.934, 96.6335})
651  .Close()
652  .MoveTo({337.336, 124.143})
653  .CubicCurveTo({337.274, 122.359}, {338.903, 121.511},
654  {338.903, 121.511})
655  .CubicCurveTo({338.903, 121.511}, {338.96, 123.303},
656  {337.336, 124.143})
657  .Close()
658  .MoveTo({340.082, 121.849})
659  .CubicCurveTo({340.074, 121.917}, {340.062, 121.992},
660  {340.046, 122.075})
661  .CubicCurveTo({340.039, 122.109}, {340.031, 122.142},
662  {340.023, 122.177})
663  .CubicCurveTo({340.005, 122.26}, {339.98, 122.346},
664  {339.952, 122.437})
665  .CubicCurveTo({339.941, 122.473}, {339.931, 122.507},
666  {339.918, 122.544})
667  .CubicCurveTo({339.873, 122.672}, {339.819, 122.804},
668  {339.75, 122.938})
669  .CubicCurveTo({339.747, 122.944}, {339.743, 122.949},
670  {339.74, 122.955})
671  .CubicCurveTo({339.674, 123.08}, {339.593, 123.205},
672  {339.501, 123.328})
673  .CubicCurveTo({339.473, 123.366}, {339.441, 123.401},
674  {339.41, 123.438})
675  .CubicCurveTo({339.332, 123.534}, {339.243, 123.625},
676  {339.145, 123.714})
677  .CubicCurveTo({339.105, 123.75}, {339.068, 123.786},
678  {339.025, 123.821})
679  .CubicCurveTo({338.881, 123.937}, {338.724, 124.048},
680  {338.539, 124.143})
681  .CubicCurveTo({338.532, 123.959}, {338.554, 123.79},
682  {338.58, 123.626})
683  .CubicCurveTo({338.58, 123.625}, {338.58, 123.625}, {338.58, 123.625})
684  .CubicCurveTo({338.607, 123.455}, {338.65, 123.299},
685  {338.704, 123.151})
686  .CubicCurveTo({338.708, 123.14}, {338.71, 123.127},
687  {338.714, 123.117})
688  .CubicCurveTo({338.769, 122.971}, {338.833, 122.838},
689  {338.905, 122.712})
690  .CubicCurveTo({338.911, 122.702}, {338.916, 122.69200000000001},
691  {338.922, 122.682})
692  .CubicCurveTo({338.996, 122.557}, {339.072, 122.444},
693  {339.155, 122.34})
694  .CubicCurveTo({339.161, 122.333}, {339.166, 122.326},
695  {339.172, 122.319})
696  .CubicCurveTo({339.256, 122.215}, {339.339, 122.12},
697  {339.425, 122.037})
698  .CubicCurveTo({339.428, 122.033}, {339.431, 122.03},
699  {339.435, 122.027})
700  .CubicCurveTo({339.785, 121.687}, {340.106, 121.511},
701  {340.106, 121.511})
702  .CubicCurveTo({340.106, 121.511}, {340.107, 121.645},
703  {340.082, 121.849})
704  .Close()
705  .MoveTo({340.678, 113.245})
706  .CubicCurveTo({340.594, 113.488}, {340.356, 113.655},
707  {340.135, 113.775})
708  .CubicCurveTo({339.817, 113.948}, {339.465, 114.059},
709  {339.115, 114.151})
710  .CubicCurveTo({338.251, 114.379}, {337.34, 114.516},
711  {336.448, 114.516})
712  .CubicCurveTo({335.761, 114.516}, {335.072, 114.527},
713  {334.384, 114.513})
714  .CubicCurveTo({334.125, 114.508}, {333.862, 114.462},
715  {333.605, 114.424})
716  .CubicCurveTo({332.865, 114.318}, {332.096, 114.184},
717  {331.41, 113.883})
718  .CubicCurveTo({330.979, 113.695}, {330.442, 113.34},
719  {330.672, 112.813})
720  .CubicCurveTo({331.135, 111.755}, {333.219, 112.946},
721  {334.526, 113.833})
722  .CubicCurveTo({334.54, 113.816}, {334.554, 113.8}, {334.569, 113.784})
723  .CubicCurveTo({333.38, 112.708}, {331.749, 110.985},
724  {332.76, 110.402})
725  .CubicCurveTo({333.769, 109.82}, {334.713, 111.93},
726  {335.228, 113.395})
727  .CubicCurveTo({334.915, 111.889}, {334.59, 109.636},
728  {335.661, 109.592})
729  .CubicCurveTo({336.733, 109.636}, {336.408, 111.889},
730  {336.07, 113.389})
731  .CubicCurveTo({336.609, 111.93}, {337.553, 109.82},
732  {338.563, 110.402})
733  .CubicCurveTo({339.574, 110.984}, {337.942, 112.708},
734  {336.753, 113.784})
735  .CubicCurveTo({336.768, 113.8}, {336.782, 113.816},
736  {336.796, 113.833})
737  .CubicCurveTo({338.104, 112.946}, {340.187, 111.755},
738  {340.65, 112.813})
739  .CubicCurveTo({340.71, 112.95}, {340.728, 113.102},
740  {340.678, 113.245})
741  .Close()
742  .MoveTo({346.357, 106.771})
743  .CubicCurveTo({346.295, 104.987}, {347.924, 104.139},
744  {347.924, 104.139})
745  .CubicCurveTo({347.924, 104.139}, {347.982, 105.931},
746  {346.357, 106.771})
747  .Close()
748  .MoveTo({347.56, 106.771})
749  .CubicCurveTo({347.498, 104.987}, {349.127, 104.139},
750  {349.127, 104.139})
751  .CubicCurveTo({349.127, 104.139}, {349.185, 105.931},
752  {347.56, 106.771})
753  .Close()
754  .TakePath();
755  Entity entity;
756  entity.SetTransformation(Matrix::MakeScale(GetContentScale()));
758  ASSERT_TRUE(OpenPlaygroundHere(entity));
759 }
760 
761 TEST_P(EntityTest, SolidColorContentsStrokeSetStrokeCapsAndJoins) {
762  {
763  auto geometry = Geometry::MakeStrokePath(Path{});
764  auto path_geometry = static_cast<StrokePathGeometry*>(geometry.get());
765  // Defaults.
766  ASSERT_EQ(path_geometry->GetStrokeCap(), Cap::kButt);
767  ASSERT_EQ(path_geometry->GetStrokeJoin(), Join::kMiter);
768  }
769 
770  {
771  auto geometry = Geometry::MakeStrokePath(Path{}, 1.0, 4.0, Cap::kSquare);
772  auto path_geometry = static_cast<StrokePathGeometry*>(geometry.get());
773  ASSERT_EQ(path_geometry->GetStrokeCap(), Cap::kSquare);
774  }
775 
776  {
777  auto geometry = Geometry::MakeStrokePath(Path{}, 1.0, 4.0, Cap::kRound);
778  auto path_geometry = static_cast<StrokePathGeometry*>(geometry.get());
779  ASSERT_EQ(path_geometry->GetStrokeCap(), Cap::kRound);
780  }
781 }
782 
783 TEST_P(EntityTest, SolidColorContentsStrokeSetMiterLimit) {
784  {
785  auto geometry = Geometry::MakeStrokePath(Path{});
786  auto path_geometry = static_cast<StrokePathGeometry*>(geometry.get());
787  ASSERT_FLOAT_EQ(path_geometry->GetMiterLimit(), 4);
788  }
789 
790  {
791  auto geometry = Geometry::MakeStrokePath(Path{}, 1.0, /*miter_limit=*/8.0);
792  auto path_geometry = static_cast<StrokePathGeometry*>(geometry.get());
793  ASSERT_FLOAT_EQ(path_geometry->GetMiterLimit(), 8);
794  }
795 
796  {
797  auto geometry = Geometry::MakeStrokePath(Path{}, 1.0, /*miter_limit=*/-1.0);
798  auto path_geometry = static_cast<StrokePathGeometry*>(geometry.get());
799  ASSERT_FLOAT_EQ(path_geometry->GetMiterLimit(), 4);
800  }
801 }
802 
803 TEST_P(EntityTest, BlendingModeOptions) {
804  std::vector<const char*> blend_mode_names;
805  std::vector<BlendMode> blend_mode_values;
806  {
807  // Force an exhausiveness check with a switch. When adding blend modes,
808  // update this switch with a new name/value to make it selectable in the
809  // test GUI.
810 
811  const BlendMode b{};
812  static_assert(b == BlendMode::kClear); // Ensure the first item in
813  // the switch is the first
814  // item in the enum.
816  switch (b) {
817  case BlendMode::kClear:
818  blend_mode_names.push_back("Clear");
819  blend_mode_values.push_back(BlendMode::kClear);
820  case BlendMode::kSource:
821  blend_mode_names.push_back("Source");
822  blend_mode_values.push_back(BlendMode::kSource);
824  blend_mode_names.push_back("Destination");
825  blend_mode_values.push_back(BlendMode::kDestination);
827  blend_mode_names.push_back("SourceOver");
828  blend_mode_values.push_back(BlendMode::kSourceOver);
830  blend_mode_names.push_back("DestinationOver");
831  blend_mode_values.push_back(BlendMode::kDestinationOver);
833  blend_mode_names.push_back("SourceIn");
834  blend_mode_values.push_back(BlendMode::kSourceIn);
836  blend_mode_names.push_back("DestinationIn");
837  blend_mode_values.push_back(BlendMode::kDestinationIn);
839  blend_mode_names.push_back("SourceOut");
840  blend_mode_values.push_back(BlendMode::kSourceOut);
842  blend_mode_names.push_back("DestinationOut");
843  blend_mode_values.push_back(BlendMode::kDestinationOut);
845  blend_mode_names.push_back("SourceATop");
846  blend_mode_values.push_back(BlendMode::kSourceATop);
848  blend_mode_names.push_back("DestinationATop");
849  blend_mode_values.push_back(BlendMode::kDestinationATop);
850  case BlendMode::kXor:
851  blend_mode_names.push_back("Xor");
852  blend_mode_values.push_back(BlendMode::kXor);
853  case BlendMode::kPlus:
854  blend_mode_names.push_back("Plus");
855  blend_mode_values.push_back(BlendMode::kPlus);
857  blend_mode_names.push_back("Modulate");
858  blend_mode_values.push_back(BlendMode::kModulate);
859  };
860  }
861 
862  auto callback = [&](ContentContext& context, RenderPass& pass) {
863  auto world_matrix = Matrix::MakeScale(GetContentScale());
864  auto draw_rect = [&context, &pass, &world_matrix](
865  Rect rect, Color color, BlendMode blend_mode) -> bool {
867 
869  {
870  auto r = rect.GetLTRB();
871  vtx_builder.AddVertices({
872  {Point(r[0], r[1])},
873  {Point(r[2], r[1])},
874  {Point(r[2], r[3])},
875  {Point(r[0], r[1])},
876  {Point(r[2], r[3])},
877  {Point(r[0], r[3])},
878  });
879  }
880 
881  Command cmd;
882  DEBUG_COMMAND_INFO(cmd, "Blended Rectangle");
883  auto options = OptionsFromPass(pass);
884  options.blend_mode = blend_mode;
885  options.primitive_type = PrimitiveType::kTriangle;
886  cmd.pipeline = context.GetSolidFillPipeline(options);
887  cmd.BindVertices(
888  vtx_builder.CreateVertexBuffer(pass.GetTransientsBuffer()));
889 
890  VS::FrameInfo frame_info;
891  frame_info.mvp =
892  Matrix::MakeOrthographic(pass.GetRenderTargetSize()) * world_matrix;
893  frame_info.color = color.Premultiply();
894  VS::BindFrameInfo(cmd,
895  pass.GetTransientsBuffer().EmplaceUniform(frame_info));
896 
897  return pass.AddCommand(std::move(cmd));
898  };
899 
900  ImGui::Begin("Controls", nullptr, ImGuiWindowFlags_AlwaysAutoResize);
901  static Color color1(1, 0, 0, 0.5), color2(0, 1, 0, 0.5);
902  ImGui::ColorEdit4("Color 1", reinterpret_cast<float*>(&color1));
903  ImGui::ColorEdit4("Color 2", reinterpret_cast<float*>(&color2));
904  static int current_blend_index = 3;
905  ImGui::ListBox("Blending mode", &current_blend_index,
906  blend_mode_names.data(), blend_mode_names.size());
907  ImGui::End();
908 
909  BlendMode selected_mode = blend_mode_values[current_blend_index];
910 
911  Point a, b, c, d;
912  std::tie(a, b) = IMPELLER_PLAYGROUND_LINE(
913  Point(400, 100), Point(200, 300), 20, Color::White(), Color::White());
914  std::tie(c, d) = IMPELLER_PLAYGROUND_LINE(
915  Point(470, 190), Point(270, 390), 20, Color::White(), Color::White());
916 
917  bool result = true;
918  result = result && draw_rect(Rect(0, 0, pass.GetRenderTargetSize().width,
919  pass.GetRenderTargetSize().height),
921  result = result && draw_rect(Rect::MakeLTRB(a.x, a.y, b.x, b.y), color1,
923  result = result && draw_rect(Rect::MakeLTRB(c.x, c.y, d.x, d.y), color2,
924  selected_mode);
925  return result;
926  };
927  ASSERT_TRUE(OpenPlaygroundHere(callback));
928 }
929 
930 TEST_P(EntityTest, BezierCircleScaled) {
931  auto callback = [&](ContentContext& context, RenderPass& pass) -> bool {
932  static float scale = 20;
933 
934  ImGui::Begin("Controls", nullptr, ImGuiWindowFlags_AlwaysAutoResize);
935  ImGui::SliderFloat("Scale", &scale, 1, 100);
936  ImGui::End();
937 
938  Entity entity;
939  entity.SetTransformation(Matrix::MakeScale(GetContentScale()));
940  auto path = PathBuilder{}
941  .MoveTo({97.325, 34.818})
942  .CubicCurveTo({98.50862885295136, 34.81812293973836},
943  {99.46822048142015, 33.85863261475589},
944  {99.46822048142015, 32.67499810206613})
945  .CubicCurveTo({99.46822048142015, 31.491363589376355},
946  {98.50862885295136, 30.53187326439389},
947  {97.32499434685802, 30.531998226542708})
948  .CubicCurveTo({96.14153655073771, 30.532123170035373},
949  {95.18222070648729, 31.491540299350355},
950  {95.18222070648729, 32.67499810206613})
951  .CubicCurveTo({95.18222070648729, 33.85845590478189},
952  {96.14153655073771, 34.81787303409686},
953  {97.32499434685802, 34.81799797758954})
954  .Close()
955  .TakePath();
956  entity.SetTransformation(
957  Matrix::MakeScale({scale, scale, 1.0}).Translate({-90, -20, 0}));
959  return entity.Render(context, pass);
960  };
961  ASSERT_TRUE(OpenPlaygroundHere(callback));
962 }
963 
964 TEST_P(EntityTest, Filters) {
965  auto bridge = CreateTextureForFixture("bay_bridge.jpg");
966  auto boston = CreateTextureForFixture("boston.jpg");
967  auto kalimba = CreateTextureForFixture("kalimba.jpg");
968  ASSERT_TRUE(bridge && boston && kalimba);
969 
970  auto callback = [&](ContentContext& context, RenderPass& pass) -> bool {
971  auto fi_bridge = FilterInput::Make(bridge);
972  auto fi_boston = FilterInput::Make(boston);
973  auto fi_kalimba = FilterInput::Make(kalimba);
974 
975  std::shared_ptr<FilterContents> blend0 = ColorFilterContents::MakeBlend(
976  BlendMode::kModulate, {fi_kalimba, fi_boston});
977 
978  auto blend1 = ColorFilterContents::MakeBlend(
980  {FilterInput::Make(blend0), fi_bridge, fi_bridge, fi_bridge});
981 
982  Entity entity;
983  entity.SetTransformation(Matrix::MakeScale(GetContentScale()) *
984  Matrix::MakeTranslation({500, 300}) *
985  Matrix::MakeScale(Vector2{0.5, 0.5}));
986  entity.SetContents(blend1);
987  return entity.Render(context, pass);
988  };
989  ASSERT_TRUE(OpenPlaygroundHere(callback));
990 }
991 
992 TEST_P(EntityTest, GaussianBlurFilter) {
993  auto boston = CreateTextureForFixture("boston.jpg");
994  ASSERT_TRUE(boston);
995 
996  auto callback = [&](ContentContext& context, RenderPass& pass) -> bool {
997  const char* input_type_names[] = {"Texture", "Solid Color"};
998  const char* blur_type_names[] = {"Image blur", "Mask blur"};
999  const char* pass_variation_names[] = {"Two pass", "Directional"};
1000  const char* blur_style_names[] = {"Normal", "Solid", "Outer", "Inner"};
1001  const char* tile_mode_names[] = {"Clamp", "Repeat", "Mirror", "Decal"};
1002  const FilterContents::BlurStyle blur_styles[] = {
1005  const Entity::TileMode tile_modes[] = {
1008 
1009  // UI state.
1010  static int selected_input_type = 0;
1011  static Color input_color = Color::Black();
1012  static int selected_blur_type = 0;
1013  static int selected_pass_variation = 0;
1014  static float blur_amount_coarse[2] = {0, 0};
1015  static float blur_amount_fine[2] = {10, 10};
1016  static int selected_blur_style = 0;
1017  static int selected_tile_mode = 3;
1018  static Color cover_color(1, 0, 0, 0.2);
1019  static Color bounds_color(0, 1, 0, 0.1);
1020  static float offset[2] = {500, 400};
1021  static float rotation = 0;
1022  static float scale[2] = {0.65, 0.65};
1023  static float skew[2] = {0, 0};
1024  static float path_rect[4] = {0, 0,
1025  static_cast<float>(boston->GetSize().width),
1026  static_cast<float>(boston->GetSize().height)};
1027 
1028  ImGui::Begin("Controls", nullptr, ImGuiWindowFlags_AlwaysAutoResize);
1029  {
1030  ImGui::Combo("Input type", &selected_input_type, input_type_names,
1031  sizeof(input_type_names) / sizeof(char*));
1032  if (selected_input_type == 0) {
1033  ImGui::SliderFloat("Input opacity", &input_color.alpha, 0, 1);
1034  } else {
1035  ImGui::ColorEdit4("Input color",
1036  reinterpret_cast<float*>(&input_color));
1037  }
1038  ImGui::Combo("Blur type", &selected_blur_type, blur_type_names,
1039  sizeof(blur_type_names) / sizeof(char*));
1040  if (selected_blur_type == 0) {
1041  ImGui::Combo("Pass variation", &selected_pass_variation,
1042  pass_variation_names,
1043  sizeof(pass_variation_names) / sizeof(char*));
1044  }
1045  ImGui::SliderFloat2("Sigma (coarse)", blur_amount_coarse, 0, 1000);
1046  ImGui::SliderFloat2("Sigma (fine)", blur_amount_fine, 0, 10);
1047  ImGui::Combo("Blur style", &selected_blur_style, blur_style_names,
1048  sizeof(blur_style_names) / sizeof(char*));
1049  ImGui::Combo("Tile mode", &selected_tile_mode, tile_mode_names,
1050  sizeof(tile_mode_names) / sizeof(char*));
1051  ImGui::ColorEdit4("Cover color", reinterpret_cast<float*>(&cover_color));
1052  ImGui::ColorEdit4("Bounds color",
1053  reinterpret_cast<float*>(&bounds_color));
1054  ImGui::SliderFloat2("Translation", offset, 0,
1055  pass.GetRenderTargetSize().width);
1056  ImGui::SliderFloat("Rotation", &rotation, 0, kPi * 2);
1057  ImGui::SliderFloat2("Scale", scale, 0, 3);
1058  ImGui::SliderFloat2("Skew", skew, -3, 3);
1059  ImGui::SliderFloat4("Path XYWH", path_rect, -1000, 1000);
1060  }
1061  ImGui::End();
1062 
1063  auto blur_sigma_x = Sigma{blur_amount_coarse[0] + blur_amount_fine[0]};
1064  auto blur_sigma_y = Sigma{blur_amount_coarse[1] + blur_amount_fine[1]};
1065 
1066  std::shared_ptr<Contents> input;
1067  Size input_size;
1068 
1069  auto input_rect =
1070  Rect::MakeXYWH(path_rect[0], path_rect[1], path_rect[2], path_rect[3]);
1071  if (selected_input_type == 0) {
1072  auto texture = std::make_shared<TextureContents>();
1073  texture->SetSourceRect(Rect::MakeSize(boston->GetSize()));
1074  texture->SetDestinationRect(input_rect);
1075  texture->SetTexture(boston);
1076  texture->SetOpacity(input_color.alpha);
1077 
1078  input = texture;
1079  input_size = input_rect.size;
1080  } else {
1081  auto fill = std::make_shared<SolidColorContents>();
1082  fill->SetColor(input_color);
1083  fill->SetGeometry(
1084  Geometry::MakeFillPath(PathBuilder{}.AddRect(input_rect).TakePath()));
1085 
1086  input = fill;
1087  input_size = input_rect.size;
1088  }
1089 
1090  std::shared_ptr<FilterContents> blur;
1091  if (selected_pass_variation == 0) {
1093  FilterInput::Make(input), blur_sigma_x, blur_sigma_y,
1094  blur_styles[selected_blur_style], tile_modes[selected_tile_mode]);
1095  } else {
1096  Vector2 blur_vector(blur_sigma_x.sigma, blur_sigma_y.sigma);
1098  FilterInput::Make(input), Sigma{blur_vector.GetLength()},
1099  blur_vector.Normalize());
1100  }
1101 
1102  auto mask_blur = FilterContents::MakeBorderMaskBlur(
1103  FilterInput::Make(input), blur_sigma_x, blur_sigma_y,
1104  blur_styles[selected_blur_style]);
1105 
1106  auto ctm = Matrix::MakeScale(GetContentScale()) *
1107  Matrix::MakeTranslation(Vector3(offset[0], offset[1])) *
1108  Matrix::MakeRotationZ(Radians(rotation)) *
1109  Matrix::MakeScale(Vector2(scale[0], scale[1])) *
1110  Matrix::MakeSkew(skew[0], skew[1]) *
1111  Matrix::MakeTranslation(-Point(input_size) / 2);
1112 
1113  auto target_contents = selected_blur_type == 0 ? blur : mask_blur;
1114 
1115  Entity entity;
1116  entity.SetContents(target_contents);
1117  entity.SetTransformation(ctm);
1118 
1119  entity.Render(context, pass);
1120 
1121  // Renders a red "cover" rectangle that shows the original position of the
1122  // unfiltered input.
1123  Entity cover_entity;
1125  PathBuilder{}.AddRect(input_rect).TakePath(), cover_color));
1126  cover_entity.SetTransformation(ctm);
1127 
1128  cover_entity.Render(context, pass);
1129 
1130  // Renders a green bounding rect of the target filter.
1131  Entity bounds_entity;
1132  bounds_entity.SetContents(SolidColorContents::Make(
1133  PathBuilder{}
1134  .AddRect(target_contents->GetCoverage(entity).value())
1135  .TakePath(),
1136  bounds_color));
1137  bounds_entity.SetTransformation(Matrix());
1138 
1139  bounds_entity.Render(context, pass);
1140 
1141  return true;
1142  };
1143  ASSERT_TRUE(OpenPlaygroundHere(callback));
1144 }
1145 
1146 TEST_P(EntityTest, MorphologyFilter) {
1147  auto boston = CreateTextureForFixture("boston.jpg");
1148  ASSERT_TRUE(boston);
1149 
1150  auto callback = [&](ContentContext& context, RenderPass& pass) -> bool {
1151  const char* morphology_type_names[] = {"Dilate", "Erode"};
1152  const FilterContents::MorphType morphology_types[] = {
1154  static Color input_color = Color::Black();
1155  // UI state.
1156  static int selected_morphology_type = 0;
1157  static float radius[2] = {20, 20};
1158  static Color cover_color(1, 0, 0, 0.2);
1159  static Color bounds_color(0, 1, 0, 0.1);
1160  static float offset[2] = {500, 400};
1161  static float rotation = 0;
1162  static float scale[2] = {0.65, 0.65};
1163  static float skew[2] = {0, 0};
1164  static float path_rect[4] = {0, 0,
1165  static_cast<float>(boston->GetSize().width),
1166  static_cast<float>(boston->GetSize().height)};
1167  static float effect_transform_scale = 1;
1168 
1169  ImGui::Begin("Controls", nullptr, ImGuiWindowFlags_AlwaysAutoResize);
1170  {
1171  ImGui::Combo("Morphology type", &selected_morphology_type,
1172  morphology_type_names,
1173  sizeof(morphology_type_names) / sizeof(char*));
1174  ImGui::SliderFloat2("Radius", radius, 0, 200);
1175  ImGui::SliderFloat("Input opacity", &input_color.alpha, 0, 1);
1176  ImGui::ColorEdit4("Cover color", reinterpret_cast<float*>(&cover_color));
1177  ImGui::ColorEdit4("Bounds color",
1178  reinterpret_cast<float*>(&bounds_color));
1179  ImGui::SliderFloat2("Translation", offset, 0,
1180  pass.GetRenderTargetSize().width);
1181  ImGui::SliderFloat("Rotation", &rotation, 0, kPi * 2);
1182  ImGui::SliderFloat2("Scale", scale, 0, 3);
1183  ImGui::SliderFloat2("Skew", skew, -3, 3);
1184  ImGui::SliderFloat4("Path XYWH", path_rect, -1000, 1000);
1185  ImGui::SliderFloat("Effect transform scale", &effect_transform_scale, 0,
1186  3);
1187  }
1188  ImGui::End();
1189 
1190  std::shared_ptr<Contents> input;
1191  Size input_size;
1192 
1193  auto input_rect =
1194  Rect::MakeXYWH(path_rect[0], path_rect[1], path_rect[2], path_rect[3]);
1195  auto texture = std::make_shared<TextureContents>();
1196  texture->SetSourceRect(Rect::MakeSize(boston->GetSize()));
1197  texture->SetDestinationRect(input_rect);
1198  texture->SetTexture(boston);
1199  texture->SetOpacity(input_color.alpha);
1200 
1201  input = texture;
1202  input_size = input_rect.size;
1203 
1204  auto contents = FilterContents::MakeMorphology(
1205  FilterInput::Make(input), Radius{radius[0]}, Radius{radius[1]},
1206  morphology_types[selected_morphology_type]);
1207  contents->SetEffectTransform(Matrix::MakeScale(
1208  Vector2{effect_transform_scale, effect_transform_scale}));
1209 
1210  auto ctm = Matrix::MakeScale(GetContentScale()) *
1211  Matrix::MakeTranslation(Vector3(offset[0], offset[1])) *
1212  Matrix::MakeRotationZ(Radians(rotation)) *
1213  Matrix::MakeScale(Vector2(scale[0], scale[1])) *
1214  Matrix::MakeSkew(skew[0], skew[1]) *
1215  Matrix::MakeTranslation(-Point(input_size) / 2);
1216 
1217  Entity entity;
1218  entity.SetContents(contents);
1219  entity.SetTransformation(ctm);
1220 
1221  entity.Render(context, pass);
1222 
1223  // Renders a red "cover" rectangle that shows the original position of the
1224  // unfiltered input.
1225  Entity cover_entity;
1227  PathBuilder{}.AddRect(input_rect).TakePath(), cover_color));
1228  cover_entity.SetTransformation(ctm);
1229 
1230  cover_entity.Render(context, pass);
1231 
1232  // Renders a green bounding rect of the target filter.
1233  Entity bounds_entity;
1234  bounds_entity.SetContents(SolidColorContents::Make(
1235  PathBuilder{}.AddRect(contents->GetCoverage(entity).value()).TakePath(),
1236  bounds_color));
1237  bounds_entity.SetTransformation(Matrix());
1238 
1239  bounds_entity.Render(context, pass);
1240 
1241  return true;
1242  };
1243  ASSERT_TRUE(OpenPlaygroundHere(callback));
1244 }
1245 
1246 TEST_P(EntityTest, SetBlendMode) {
1247  Entity entity;
1248  ASSERT_EQ(entity.GetBlendMode(), BlendMode::kSourceOver);
1250  ASSERT_EQ(entity.GetBlendMode(), BlendMode::kClear);
1251 }
1252 
1253 TEST_P(EntityTest, ContentsGetBoundsForEmptyPathReturnsNullopt) {
1254  Entity entity;
1255  entity.SetContents(std::make_shared<SolidColorContents>());
1256  ASSERT_FALSE(entity.GetCoverage().has_value());
1257 }
1258 
1259 TEST_P(EntityTest, SolidStrokeCoverageIsCorrect) {
1260  {
1261  auto geometry = Geometry::MakeStrokePath(
1262  PathBuilder{}.AddLine({0, 0}, {10, 10}).TakePath(), 4.0, 4.0,
1264 
1265  Entity entity;
1266  auto contents = std::make_unique<SolidColorContents>();
1267  contents->SetGeometry(std::move(geometry));
1268  contents->SetColor(Color::Black());
1269  entity.SetContents(std::move(contents));
1270  auto actual = entity.GetCoverage();
1271  auto expected = Rect::MakeLTRB(-2, -2, 12, 12);
1272  ASSERT_TRUE(actual.has_value());
1273  ASSERT_RECT_NEAR(actual.value(), expected);
1274  }
1275 
1276  // Cover the Cap::kSquare case.
1277  {
1278  auto geometry = Geometry::MakeStrokePath(
1279  PathBuilder{}.AddLine({0, 0}, {10, 10}).TakePath(), 4.0, 4.0,
1281 
1282  Entity entity;
1283  auto contents = std::make_unique<SolidColorContents>();
1284  contents->SetGeometry(std::move(geometry));
1285  contents->SetColor(Color::Black());
1286  entity.SetContents(std::move(contents));
1287  auto actual = entity.GetCoverage();
1288  auto expected =
1289  Rect::MakeLTRB(-sqrt(8), -sqrt(8), 10 + sqrt(8), 10 + sqrt(8));
1290  ASSERT_TRUE(actual.has_value());
1291  ASSERT_RECT_NEAR(actual.value(), expected);
1292  }
1293 
1294  // Cover the Join::kMiter case.
1295  {
1296  auto geometry = Geometry::MakeStrokePath(
1297  PathBuilder{}.AddLine({0, 0}, {10, 10}).TakePath(), 4.0, 2.0,
1299 
1300  Entity entity;
1301  auto contents = std::make_unique<SolidColorContents>();
1302  contents->SetGeometry(std::move(geometry));
1303  contents->SetColor(Color::Black());
1304  entity.SetContents(std::move(contents));
1305  auto actual = entity.GetCoverage();
1306  auto expected = Rect::MakeLTRB(-4, -4, 14, 14);
1307  ASSERT_TRUE(actual.has_value());
1308  ASSERT_RECT_NEAR(actual.value(), expected);
1309  }
1310 }
1311 
1312 TEST_P(EntityTest, BorderMaskBlurCoverageIsCorrect) {
1313  auto fill = std::make_shared<SolidColorContents>();
1314  fill->SetGeometry(Geometry::MakeFillPath(
1315  PathBuilder{}.AddRect(Rect::MakeXYWH(0, 0, 300, 400)).TakePath()));
1316  fill->SetColor(Color::CornflowerBlue());
1317  auto border_mask_blur = FilterContents::MakeBorderMaskBlur(
1318  FilterInput::Make(fill), Radius{3}, Radius{4});
1319 
1320  {
1321  Entity e;
1323  auto actual = border_mask_blur->GetCoverage(e);
1324  auto expected = Rect::MakeXYWH(-3, -4, 306, 408);
1325  ASSERT_TRUE(actual.has_value());
1326  ASSERT_RECT_NEAR(actual.value(), expected);
1327  }
1328 
1329  {
1330  Entity e;
1332  auto actual = border_mask_blur->GetCoverage(e);
1333  auto expected = Rect::MakeXYWH(-287.792, -4.94975, 504.874, 504.874);
1334  ASSERT_TRUE(actual.has_value());
1335  ASSERT_RECT_NEAR(actual.value(), expected);
1336  }
1337 }
1338 
1339 TEST_P(EntityTest, DrawAtlasNoColor) {
1340  // Draws the image as four squares stiched together.
1341  auto atlas = CreateTextureForFixture("bay_bridge.jpg");
1342  auto size = atlas->GetSize();
1343  // Divide image into four quadrants.
1344  Scalar half_width = size.width / 2;
1345  Scalar half_height = size.height / 2;
1346  std::vector<Rect> texture_coordinates = {
1347  Rect::MakeLTRB(0, 0, half_width, half_height),
1348  Rect::MakeLTRB(half_width, 0, size.width, half_height),
1349  Rect::MakeLTRB(0, half_height, half_width, size.height),
1350  Rect::MakeLTRB(half_width, half_height, size.width, size.height)};
1351  // Position quadrants adjacent to eachother.
1352  std::vector<Matrix> transforms = {
1353  Matrix::MakeTranslation({0, 0, 0}),
1354  Matrix::MakeTranslation({half_width, 0, 0}),
1355  Matrix::MakeTranslation({0, half_height, 0}),
1356  Matrix::MakeTranslation({half_width, half_height, 0})};
1357  std::shared_ptr<AtlasContents> contents = std::make_shared<AtlasContents>();
1358 
1359  contents->SetTransforms(std::move(transforms));
1360  contents->SetTextureCoordinates(std::move(texture_coordinates));
1361  contents->SetTexture(atlas);
1362  contents->SetBlendMode(BlendMode::kSource);
1363 
1364  Entity e;
1365  e.SetTransformation(Matrix::MakeScale(GetContentScale()));
1366  e.SetContents(contents);
1367 
1368  ASSERT_TRUE(OpenPlaygroundHere(e));
1369 }
1370 
1371 TEST_P(EntityTest, DrawAtlasWithColorAdvanced) {
1372  // Draws the image as four squares stiched together.
1373  auto atlas = CreateTextureForFixture("bay_bridge.jpg");
1374  auto size = atlas->GetSize();
1375  // Divide image into four quadrants.
1376  Scalar half_width = size.width / 2;
1377  Scalar half_height = size.height / 2;
1378  std::vector<Rect> texture_coordinates = {
1379  Rect::MakeLTRB(0, 0, half_width, half_height),
1380  Rect::MakeLTRB(half_width, 0, size.width, half_height),
1381  Rect::MakeLTRB(0, half_height, half_width, size.height),
1382  Rect::MakeLTRB(half_width, half_height, size.width, size.height)};
1383  // Position quadrants adjacent to eachother.
1384  std::vector<Matrix> transforms = {
1385  Matrix::MakeTranslation({0, 0, 0}),
1386  Matrix::MakeTranslation({half_width, 0, 0}),
1387  Matrix::MakeTranslation({0, half_height, 0}),
1388  Matrix::MakeTranslation({half_width, half_height, 0})};
1389  std::vector<Color> colors = {Color::Red(), Color::Green(), Color::Blue(),
1390  Color::Yellow()};
1391  std::shared_ptr<AtlasContents> contents = std::make_shared<AtlasContents>();
1392 
1393  contents->SetTransforms(std::move(transforms));
1394  contents->SetTextureCoordinates(std::move(texture_coordinates));
1395  contents->SetTexture(atlas);
1396  contents->SetColors(colors);
1397  contents->SetBlendMode(BlendMode::kModulate);
1398 
1399  Entity e;
1400  e.SetTransformation(Matrix::MakeScale(GetContentScale()));
1401  e.SetContents(contents);
1402 
1403  ASSERT_TRUE(OpenPlaygroundHere(e));
1404 }
1405 
1406 TEST_P(EntityTest, DrawAtlasWithColorSimple) {
1407  // Draws the image as four squares stiched together. Because blend modes
1408  // aren't implented this ends up as four solid color blocks.
1409  auto atlas = CreateTextureForFixture("bay_bridge.jpg");
1410  auto size = atlas->GetSize();
1411  // Divide image into four quadrants.
1412  Scalar half_width = size.width / 2;
1413  Scalar half_height = size.height / 2;
1414  std::vector<Rect> texture_coordinates = {
1415  Rect::MakeLTRB(0, 0, half_width, half_height),
1416  Rect::MakeLTRB(half_width, 0, size.width, half_height),
1417  Rect::MakeLTRB(0, half_height, half_width, size.height),
1418  Rect::MakeLTRB(half_width, half_height, size.width, size.height)};
1419  // Position quadrants adjacent to eachother.
1420  std::vector<Matrix> transforms = {
1421  Matrix::MakeTranslation({0, 0, 0}),
1422  Matrix::MakeTranslation({half_width, 0, 0}),
1423  Matrix::MakeTranslation({0, half_height, 0}),
1424  Matrix::MakeTranslation({half_width, half_height, 0})};
1425  std::vector<Color> colors = {Color::Red(), Color::Green(), Color::Blue(),
1426  Color::Yellow()};
1427  std::shared_ptr<AtlasContents> contents = std::make_shared<AtlasContents>();
1428 
1429  contents->SetTransforms(std::move(transforms));
1430  contents->SetTextureCoordinates(std::move(texture_coordinates));
1431  contents->SetTexture(atlas);
1432  contents->SetColors(colors);
1433  contents->SetBlendMode(BlendMode::kSourceATop);
1434 
1435  Entity e;
1436  e.SetTransformation(Matrix::MakeScale(GetContentScale()));
1437  e.SetContents(contents);
1438 
1439  ASSERT_TRUE(OpenPlaygroundHere(e));
1440 }
1441 
1442 TEST_P(EntityTest, DrawAtlasUsesProvidedCullRectForCoverage) {
1443  auto atlas = CreateTextureForFixture("bay_bridge.jpg");
1444  auto size = atlas->GetSize();
1445 
1446  Scalar half_width = size.width / 2;
1447  Scalar half_height = size.height / 2;
1448  std::vector<Rect> texture_coordinates = {
1449  Rect::MakeLTRB(0, 0, half_width, half_height),
1450  Rect::MakeLTRB(half_width, 0, size.width, half_height),
1451  Rect::MakeLTRB(0, half_height, half_width, size.height),
1452  Rect::MakeLTRB(half_width, half_height, size.width, size.height)};
1453  std::vector<Matrix> transforms = {
1454  Matrix::MakeTranslation({0, 0, 0}),
1455  Matrix::MakeTranslation({half_width, 0, 0}),
1456  Matrix::MakeTranslation({0, half_height, 0}),
1457  Matrix::MakeTranslation({half_width, half_height, 0})};
1458 
1459  std::shared_ptr<AtlasContents> contents = std::make_shared<AtlasContents>();
1460 
1461  contents->SetTransforms(std::move(transforms));
1462  contents->SetTextureCoordinates(std::move(texture_coordinates));
1463  contents->SetTexture(atlas);
1464  contents->SetBlendMode(BlendMode::kSource);
1465 
1466  auto transform = Matrix::MakeScale(GetContentScale());
1467  Entity e;
1468  e.SetTransformation(transform);
1469  e.SetContents(contents);
1470 
1471  ASSERT_EQ(contents->GetCoverage(e).value(),
1472  Rect::MakeSize(size).TransformBounds(transform));
1473 
1474  contents->SetCullRect(Rect::MakeLTRB(0, 0, 10, 10));
1475 
1476  ASSERT_EQ(contents->GetCoverage(e).value(),
1477  Rect::MakeLTRB(0, 0, 10, 10).TransformBounds(transform));
1478 }
1479 
1480 TEST_P(EntityTest, DrawAtlasWithOpacity) {
1481  // Draws the image as four squares stiched together slightly
1482  // opaque
1483  auto atlas = CreateTextureForFixture("bay_bridge.jpg");
1484  auto size = atlas->GetSize();
1485  // Divide image into four quadrants.
1486  Scalar half_width = size.width / 2;
1487  Scalar half_height = size.height / 2;
1488  std::vector<Rect> texture_coordinates = {
1489  Rect::MakeLTRB(0, 0, half_width, half_height),
1490  Rect::MakeLTRB(half_width, 0, size.width, half_height),
1491  Rect::MakeLTRB(0, half_height, half_width, size.height),
1492  Rect::MakeLTRB(half_width, half_height, size.width, size.height)};
1493  // Position quadrants adjacent to eachother.
1494  std::vector<Matrix> transforms = {
1495  Matrix::MakeTranslation({0, 0, 0}),
1496  Matrix::MakeTranslation({half_width, 0, 0}),
1497  Matrix::MakeTranslation({0, half_height, 0}),
1498  Matrix::MakeTranslation({half_width, half_height, 0})};
1499 
1500  std::shared_ptr<AtlasContents> contents = std::make_shared<AtlasContents>();
1501 
1502  contents->SetTransforms(std::move(transforms));
1503  contents->SetTextureCoordinates(std::move(texture_coordinates));
1504  contents->SetTexture(atlas);
1505  contents->SetBlendMode(BlendMode::kSource);
1506  contents->SetAlpha(0.5);
1507 
1508  Entity e;
1509  e.SetTransformation(Matrix::MakeScale(GetContentScale()));
1510  e.SetContents(contents);
1511 
1512  ASSERT_TRUE(OpenPlaygroundHere(e));
1513 }
1514 
1515 TEST_P(EntityTest, DrawAtlasNoColorFullSize) {
1516  auto atlas = CreateTextureForFixture("bay_bridge.jpg");
1517  auto size = atlas->GetSize();
1518  std::vector<Rect> texture_coordinates = {
1519  Rect::MakeLTRB(0, 0, size.width, size.height)};
1520  std::vector<Matrix> transforms = {Matrix::MakeTranslation({0, 0, 0})};
1521  std::shared_ptr<AtlasContents> contents = std::make_shared<AtlasContents>();
1522 
1523  contents->SetTransforms(std::move(transforms));
1524  contents->SetTextureCoordinates(std::move(texture_coordinates));
1525  contents->SetTexture(atlas);
1526  contents->SetBlendMode(BlendMode::kSource);
1527 
1528  Entity e;
1529  e.SetTransformation(Matrix::MakeScale(GetContentScale()));
1530  e.SetContents(contents);
1531 
1532  ASSERT_TRUE(OpenPlaygroundHere(e));
1533 }
1534 
1535 TEST_P(EntityTest, SolidFillCoverageIsCorrect) {
1536  // No transform
1537  {
1538  auto fill = std::make_shared<SolidColorContents>();
1539  fill->SetColor(Color::CornflowerBlue());
1540  auto expected = Rect::MakeLTRB(100, 110, 200, 220);
1541  fill->SetGeometry(
1542  Geometry::MakeFillPath(PathBuilder{}.AddRect(expected).TakePath()));
1543 
1544  auto coverage = fill->GetCoverage({});
1545  ASSERT_TRUE(coverage.has_value());
1546  ASSERT_RECT_NEAR(coverage.value(), expected);
1547  }
1548 
1549  // Entity transform
1550  {
1551  auto fill = std::make_shared<SolidColorContents>();
1552  fill->SetColor(Color::CornflowerBlue());
1553  fill->SetGeometry(Geometry::MakeFillPath(
1554  PathBuilder{}.AddRect(Rect::MakeLTRB(100, 110, 200, 220)).TakePath()));
1555 
1556  Entity entity;
1558  entity.SetContents(std::move(fill));
1559 
1560  auto coverage = entity.GetCoverage();
1561  auto expected = Rect::MakeLTRB(104, 115, 204, 225);
1562  ASSERT_TRUE(coverage.has_value());
1563  ASSERT_RECT_NEAR(coverage.value(), expected);
1564  }
1565 
1566  // No coverage for fully transparent colors
1567  {
1568  auto fill = std::make_shared<SolidColorContents>();
1569  fill->SetColor(Color::WhiteTransparent());
1570  fill->SetGeometry(Geometry::MakeFillPath(
1571  PathBuilder{}.AddRect(Rect::MakeLTRB(100, 110, 200, 220)).TakePath()));
1572 
1573  auto coverage = fill->GetCoverage({});
1574  ASSERT_FALSE(coverage.has_value());
1575  }
1576 }
1577 
1578 TEST_P(EntityTest, SolidFillShouldRenderIsCorrect) {
1579  // No path.
1580  {
1581  auto fill = std::make_shared<SolidColorContents>();
1582  fill->SetColor(Color::CornflowerBlue());
1583  ASSERT_FALSE(fill->ShouldRender(Entity{}, Rect::MakeSize(Size{100, 100})));
1584  ASSERT_FALSE(
1585  fill->ShouldRender(Entity{}, Rect::MakeLTRB(-100, -100, -50, -50)));
1586  }
1587 
1588  // With path.
1589  {
1590  auto fill = std::make_shared<SolidColorContents>();
1591  fill->SetColor(Color::CornflowerBlue());
1592  fill->SetGeometry(Geometry::MakeFillPath(
1593  PathBuilder{}.AddRect(Rect::MakeLTRB(0, 0, 100, 100)).TakePath()));
1594  ASSERT_TRUE(fill->ShouldRender(Entity{}, Rect::MakeSize(Size{100, 100})));
1595  ASSERT_FALSE(
1596  fill->ShouldRender(Entity{}, Rect::MakeLTRB(-100, -100, -50, -50)));
1597  }
1598 
1599  // With paint cover.
1600  {
1601  auto fill = std::make_shared<SolidColorContents>();
1602  fill->SetColor(Color::CornflowerBlue());
1603  fill->SetGeometry(Geometry::MakeCover());
1604  ASSERT_TRUE(fill->ShouldRender(Entity{}, Rect::MakeSize(Size{100, 100})));
1605  ASSERT_TRUE(
1606  fill->ShouldRender(Entity{}, Rect::MakeLTRB(-100, -100, -50, -50)));
1607  }
1608 }
1609 
1610 TEST_P(EntityTest, DoesNotCullEntitiesByDefault) {
1611  auto fill = std::make_shared<SolidColorContents>();
1612  fill->SetColor(Color::CornflowerBlue());
1613  fill->SetGeometry(
1614  Geometry::MakeRect(Rect::MakeLTRB(-1000, -1000, -900, -900)));
1615 
1616  Entity entity;
1617  entity.SetContents(fill);
1618 
1619  // Even though the entity is offscreen, this should still render because we do
1620  // not compute the coverage intersection by default.
1621  EXPECT_TRUE(entity.ShouldRender(Rect::MakeLTRB(0, 0, 100, 100)));
1622 }
1623 
1624 TEST_P(EntityTest, ClipContentsShouldRenderIsCorrect) {
1625  // For clip ops, `ShouldRender` should always return true.
1626 
1627  // Clip.
1628  {
1629  auto clip = std::make_shared<ClipContents>();
1630  ASSERT_TRUE(clip->ShouldRender(Entity{}, Rect::MakeSize(Size{100, 100})));
1631  clip->SetGeometry(Geometry::MakeFillPath(
1632  PathBuilder{}.AddRect(Rect::MakeLTRB(0, 0, 100, 100)).TakePath()));
1633  ASSERT_TRUE(clip->ShouldRender(Entity{}, Rect::MakeSize(Size{100, 100})));
1634  ASSERT_TRUE(
1635  clip->ShouldRender(Entity{}, Rect::MakeLTRB(-100, -100, -50, -50)));
1636  }
1637 
1638  // Clip restore.
1639  {
1640  auto restore = std::make_shared<ClipRestoreContents>();
1641  ASSERT_TRUE(
1642  restore->ShouldRender(Entity{}, Rect::MakeSize(Size{100, 100})));
1643  ASSERT_TRUE(
1644  restore->ShouldRender(Entity{}, Rect::MakeLTRB(-100, -100, -50, -50)));
1645  }
1646 }
1647 
1648 TEST_P(EntityTest, ClipContentsGetStencilCoverageIsCorrect) {
1649  // Intersection: No stencil coverage, no geometry.
1650  {
1651  auto clip = std::make_shared<ClipContents>();
1652  clip->SetClipOperation(Entity::ClipOperation::kIntersect);
1653  auto result = clip->GetStencilCoverage(Entity{}, Rect{});
1654 
1655  ASSERT_FALSE(result.coverage.has_value());
1656  }
1657 
1658  // Intersection: No stencil coverage, with geometry.
1659  {
1660  auto clip = std::make_shared<ClipContents>();
1661  clip->SetClipOperation(Entity::ClipOperation::kIntersect);
1662  clip->SetGeometry(Geometry::MakeFillPath(
1663  PathBuilder{}.AddRect(Rect::MakeLTRB(0, 0, 100, 100)).TakePath()));
1664  auto result = clip->GetStencilCoverage(Entity{}, Rect{});
1665 
1666  ASSERT_FALSE(result.coverage.has_value());
1667  }
1668 
1669  // Intersection: With stencil coverage, no geometry.
1670  {
1671  auto clip = std::make_shared<ClipContents>();
1672  clip->SetClipOperation(Entity::ClipOperation::kIntersect);
1673  auto result =
1674  clip->GetStencilCoverage(Entity{}, Rect::MakeLTRB(0, 0, 100, 100));
1675 
1676  ASSERT_FALSE(result.coverage.has_value());
1677  }
1678 
1679  // Intersection: With stencil coverage, with geometry.
1680  {
1681  auto clip = std::make_shared<ClipContents>();
1682  clip->SetClipOperation(Entity::ClipOperation::kIntersect);
1683  clip->SetGeometry(Geometry::MakeFillPath(
1684  PathBuilder{}.AddRect(Rect::MakeLTRB(0, 0, 50, 50)).TakePath()));
1685  auto result =
1686  clip->GetStencilCoverage(Entity{}, Rect::MakeLTRB(0, 0, 100, 100));
1687 
1688  ASSERT_TRUE(result.coverage.has_value());
1689  ASSERT_RECT_NEAR(result.coverage.value(), Rect::MakeLTRB(0, 0, 50, 50));
1690  ASSERT_EQ(result.type, Contents::StencilCoverage::Type::kAppend);
1691  }
1692 
1693  // Difference: With stencil coverage, with geometry.
1694  {
1695  auto clip = std::make_shared<ClipContents>();
1696  clip->SetClipOperation(Entity::ClipOperation::kDifference);
1697  clip->SetGeometry(Geometry::MakeFillPath(
1698  PathBuilder{}.AddRect(Rect::MakeLTRB(0, 0, 50, 50)).TakePath()));
1699  auto result =
1700  clip->GetStencilCoverage(Entity{}, Rect::MakeLTRB(0, 0, 100, 100));
1701 
1702  ASSERT_TRUE(result.coverage.has_value());
1703  ASSERT_RECT_NEAR(result.coverage.value(), Rect::MakeLTRB(0, 0, 100, 100));
1704  ASSERT_EQ(result.type, Contents::StencilCoverage::Type::kAppend);
1705  }
1706 }
1707 
1708 TEST_P(EntityTest, RRectShadowTest) {
1709  auto callback = [&](ContentContext& context, RenderPass& pass) {
1710  static Color color = Color::Red();
1711  static float corner_radius = 100;
1712  static float blur_radius = 100;
1713  static bool show_coverage = false;
1714  static Color coverage_color = Color::Green().WithAlpha(0.2);
1715 
1716  ImGui::Begin("Controls", nullptr, ImGuiWindowFlags_AlwaysAutoResize);
1717  ImGui::SliderFloat("Corner radius", &corner_radius, 0, 300);
1718  ImGui::SliderFloat("Blur radius", &blur_radius, 0, 300);
1719  ImGui::ColorEdit4("Color", reinterpret_cast<Scalar*>(&color));
1720  ImGui::Checkbox("Show coverage", &show_coverage);
1721  if (show_coverage) {
1722  ImGui::ColorEdit4("Coverage color",
1723  reinterpret_cast<Scalar*>(&coverage_color));
1724  }
1725  ImGui::End();
1726 
1727  auto [top_left, bottom_right] = IMPELLER_PLAYGROUND_LINE(
1728  Point(200, 200), Point(600, 400), 30, Color::White(), Color::White());
1729  auto rect =
1730  Rect::MakeLTRB(top_left.x, top_left.y, bottom_right.x, bottom_right.y);
1731 
1732  auto contents = std::make_unique<SolidRRectBlurContents>();
1733  contents->SetRRect(rect, corner_radius);
1734  contents->SetColor(color);
1735  contents->SetSigma(Radius(blur_radius));
1736 
1737  Entity entity;
1738  entity.SetTransformation(Matrix::MakeScale(GetContentScale()));
1739  entity.SetContents(std::move(contents));
1740  entity.Render(context, pass);
1741 
1742  auto coverage = entity.GetCoverage();
1743  if (show_coverage && coverage.has_value()) {
1744  auto bounds_contents = std::make_unique<SolidColorContents>();
1745  bounds_contents->SetGeometry(Geometry::MakeFillPath(
1746  PathBuilder{}.AddRect(entity.GetCoverage().value()).TakePath()));
1747  bounds_contents->SetColor(coverage_color.Premultiply());
1748  Entity bounds_entity;
1749  bounds_entity.SetContents(std::move(bounds_contents));
1750  bounds_entity.Render(context, pass);
1751  }
1752 
1753  return true;
1754  };
1755  ASSERT_TRUE(OpenPlaygroundHere(callback));
1756 }
1757 
1758 TEST_P(EntityTest, ColorMatrixFilterCoverageIsCorrect) {
1759  // Set up a simple color background.
1760  auto fill = std::make_shared<SolidColorContents>();
1761  fill->SetGeometry(Geometry::MakeFillPath(
1762  PathBuilder{}.AddRect(Rect::MakeXYWH(0, 0, 300, 400)).TakePath()));
1763  fill->SetColor(Color::Coral());
1764 
1765  // Set the color matrix filter.
1766  ColorMatrix matrix = {
1767  1, 1, 1, 1, 1, //
1768  1, 1, 1, 1, 1, //
1769  1, 1, 1, 1, 1, //
1770  1, 1, 1, 1, 1, //
1771  };
1772 
1773  auto filter =
1775 
1776  Entity e;
1778 
1779  // Confirm that the actual filter coverage matches the expected coverage.
1780  auto actual = filter->GetCoverage(e);
1781  auto expected = Rect::MakeXYWH(0, 0, 300, 400);
1782 
1783  ASSERT_TRUE(actual.has_value());
1784  ASSERT_RECT_NEAR(actual.value(), expected);
1785 }
1786 
1787 TEST_P(EntityTest, ColorMatrixFilterEditable) {
1788  auto bay_bridge = CreateTextureForFixture("bay_bridge.jpg");
1789  ASSERT_TRUE(bay_bridge);
1790 
1791  auto callback = [&](ContentContext& context, RenderPass& pass) -> bool {
1792  // UI state.
1793  static ColorMatrix color_matrix = {
1794  1, 0, 0, 0, 0, //
1795  0, 3, 0, 0, 0, //
1796  0, 0, 1, 0, 0, //
1797  0, 0, 0, 1, 0, //
1798  };
1799  static float offset[2] = {500, 400};
1800  static float rotation = 0;
1801  static float scale[2] = {0.65, 0.65};
1802  static float skew[2] = {0, 0};
1803 
1804  // Define the ImGui
1805  ImGui::Begin("Color Matrix", nullptr, ImGuiWindowFlags_AlwaysAutoResize);
1806  {
1807  std::string label = "##1";
1808  for (int i = 0; i < 20; i += 5) {
1809  ImGui::InputScalarN(label.c_str(), ImGuiDataType_Float,
1810  &(color_matrix.array[i]), 5, nullptr, nullptr,
1811  "%.2f", 0);
1812  label[2]++;
1813  }
1814 
1815  ImGui::SliderFloat2("Translation", &offset[0], 0,
1816  pass.GetRenderTargetSize().width);
1817  ImGui::SliderFloat("Rotation", &rotation, 0, kPi * 2);
1818  ImGui::SliderFloat2("Scale", &scale[0], 0, 3);
1819  ImGui::SliderFloat2("Skew", &skew[0], -3, 3);
1820  }
1821  ImGui::End();
1822 
1823  // Set the color matrix filter.
1825  FilterInput::Make(bay_bridge), color_matrix);
1826 
1827  // Define the entity with the color matrix filter.
1828  Entity entity;
1829  entity.SetTransformation(
1830  Matrix::MakeScale(GetContentScale()) *
1831  Matrix::MakeTranslation(Vector3(offset[0], offset[1])) *
1832  Matrix::MakeRotationZ(Radians(rotation)) *
1833  Matrix::MakeScale(Vector2(scale[0], scale[1])) *
1834  Matrix::MakeSkew(skew[0], skew[1]) *
1835  Matrix::MakeTranslation(-Point(bay_bridge->GetSize()) / 2));
1836  entity.SetContents(filter);
1837  entity.Render(context, pass);
1838 
1839  return true;
1840  };
1841 
1842  ASSERT_TRUE(OpenPlaygroundHere(callback));
1843 }
1844 
1845 TEST_P(EntityTest, LinearToSrgbFilterCoverageIsCorrect) {
1846  // Set up a simple color background.
1847  auto fill = std::make_shared<SolidColorContents>();
1848  fill->SetGeometry(Geometry::MakeFillPath(
1849  PathBuilder{}.AddRect(Rect::MakeXYWH(0, 0, 300, 400)).TakePath()));
1850  fill->SetColor(Color::MintCream());
1851 
1852  auto filter =
1854 
1855  Entity e;
1857 
1858  // Confirm that the actual filter coverage matches the expected coverage.
1859  auto actual = filter->GetCoverage(e);
1860  auto expected = Rect::MakeXYWH(0, 0, 300, 400);
1861 
1862  ASSERT_TRUE(actual.has_value());
1863  ASSERT_RECT_NEAR(actual.value(), expected);
1864 }
1865 
1866 TEST_P(EntityTest, LinearToSrgbFilter) {
1867  auto image = CreateTextureForFixture("kalimba.jpg");
1868  ASSERT_TRUE(image);
1869 
1870  auto callback = [&](ContentContext& context, RenderPass& pass) -> bool {
1871  auto filtered =
1873 
1874  // Define the entity that will serve as the control image as a Gaussian blur
1875  // filter with no filter at all.
1876  Entity entity_left;
1877  entity_left.SetTransformation(Matrix::MakeScale(GetContentScale()) *
1878  Matrix::MakeTranslation({100, 300}) *
1879  Matrix::MakeScale(Vector2{0.5, 0.5}));
1880  auto unfiltered = FilterContents::MakeGaussianBlur(FilterInput::Make(image),
1881  Sigma{0}, Sigma{0});
1882  entity_left.SetContents(unfiltered);
1883 
1884  // Define the entity that will be filtered from linear to sRGB.
1885  Entity entity_right;
1886  entity_right.SetTransformation(Matrix::MakeScale(GetContentScale()) *
1887  Matrix::MakeTranslation({500, 300}) *
1888  Matrix::MakeScale(Vector2{0.5, 0.5}));
1889  entity_right.SetContents(filtered);
1890  return entity_left.Render(context, pass) &&
1891  entity_right.Render(context, pass);
1892  };
1893 
1894  ASSERT_TRUE(OpenPlaygroundHere(callback));
1895 }
1896 
1897 TEST_P(EntityTest, SrgbToLinearFilterCoverageIsCorrect) {
1898  // Set up a simple color background.
1899  auto fill = std::make_shared<SolidColorContents>();
1900  fill->SetGeometry(Geometry::MakeFillPath(
1901  PathBuilder{}.AddRect(Rect::MakeXYWH(0, 0, 300, 400)).TakePath()));
1902  fill->SetColor(Color::DeepPink());
1903 
1904  auto filter =
1906 
1907  Entity e;
1909 
1910  // Confirm that the actual filter coverage matches the expected coverage.
1911  auto actual = filter->GetCoverage(e);
1912  auto expected = Rect::MakeXYWH(0, 0, 300, 400);
1913 
1914  ASSERT_TRUE(actual.has_value());
1915  ASSERT_RECT_NEAR(actual.value(), expected);
1916 }
1917 
1918 TEST_P(EntityTest, SrgbToLinearFilter) {
1919  auto image = CreateTextureForFixture("embarcadero.jpg");
1920  ASSERT_TRUE(image);
1921 
1922  auto callback = [&](ContentContext& context, RenderPass& pass) -> bool {
1923  auto filtered =
1925 
1926  // Define the entity that will serve as the control image as a Gaussian blur
1927  // filter with no filter at all.
1928  Entity entity_left;
1929  entity_left.SetTransformation(Matrix::MakeScale(GetContentScale()) *
1930  Matrix::MakeTranslation({100, 300}) *
1931  Matrix::MakeScale(Vector2{0.5, 0.5}));
1932  auto unfiltered = FilterContents::MakeGaussianBlur(FilterInput::Make(image),
1933  Sigma{0}, Sigma{0});
1934  entity_left.SetContents(unfiltered);
1935 
1936  // Define the entity that will be filtered from sRGB to linear.
1937  Entity entity_right;
1938  entity_right.SetTransformation(Matrix::MakeScale(GetContentScale()) *
1939  Matrix::MakeTranslation({500, 300}) *
1940  Matrix::MakeScale(Vector2{0.5, 0.5}));
1941  entity_right.SetContents(filtered);
1942  return entity_left.Render(context, pass) &&
1943  entity_right.Render(context, pass);
1944  };
1945 
1946  ASSERT_TRUE(OpenPlaygroundHere(callback));
1947 }
1948 
1949 TEST_P(EntityTest, AtlasContentsSubAtlas) {
1950  auto boston = CreateTextureForFixture("boston.jpg");
1951 
1952  {
1953  auto contents = std::make_shared<AtlasContents>();
1954  contents->SetBlendMode(BlendMode::kSourceOver);
1955  contents->SetTexture(boston);
1956  contents->SetColors({
1957  Color::Red(),
1958  Color::Red(),
1959  Color::Red(),
1960  });
1961  contents->SetTextureCoordinates({
1962  Rect::MakeLTRB(0, 0, 10, 10),
1963  Rect::MakeLTRB(0, 0, 10, 10),
1964  Rect::MakeLTRB(0, 0, 10, 10),
1965  });
1966  contents->SetTransforms({
1968  Matrix::MakeTranslation(Vector2(100, 100)),
1969  Matrix::MakeTranslation(Vector2(200, 200)),
1970  });
1971 
1972  // Since all colors and sample rects are the same, there should
1973  // only be a single entry in the sub atlas.
1974  auto subatlas = contents->GenerateSubAtlas();
1975  ASSERT_EQ(subatlas->sub_texture_coords.size(), 1u);
1976  }
1977 
1978  {
1979  auto contents = std::make_shared<AtlasContents>();
1980  contents->SetBlendMode(BlendMode::kSourceOver);
1981  contents->SetTexture(boston);
1982  contents->SetColors({
1983  Color::Red(),
1984  Color::Green(),
1985  Color::Blue(),
1986  });
1987  contents->SetTextureCoordinates({
1988  Rect::MakeLTRB(0, 0, 10, 10),
1989  Rect::MakeLTRB(0, 0, 10, 10),
1990  Rect::MakeLTRB(0, 0, 10, 10),
1991  });
1992  contents->SetTransforms({
1994  Matrix::MakeTranslation(Vector2(100, 100)),
1995  Matrix::MakeTranslation(Vector2(200, 200)),
1996  });
1997 
1998  // Since all colors are different, there are three entires.
1999  auto subatlas = contents->GenerateSubAtlas();
2000  ASSERT_EQ(subatlas->sub_texture_coords.size(), 3u);
2001 
2002  // The translations are kept but the sample rects point into
2003  // different parts of the sub atlas.
2004  ASSERT_EQ(subatlas->result_texture_coords[0], Rect::MakeXYWH(0, 0, 10, 10));
2005  ASSERT_EQ(subatlas->result_texture_coords[1],
2006  Rect::MakeXYWH(11, 0, 10, 10));
2007  ASSERT_EQ(subatlas->result_texture_coords[2],
2008  Rect::MakeXYWH(22, 0, 10, 10));
2009  }
2010 }
2011 
2012 static Vector3 RGBToYUV(Vector3 rgb, YUVColorSpace yuv_color_space) {
2013  Vector3 yuv;
2014  switch (yuv_color_space) {
2016  yuv.x = rgb.x * 0.299 + rgb.y * 0.587 + rgb.z * 0.114;
2017  yuv.y = rgb.x * -0.169 + rgb.y * -0.331 + rgb.z * 0.5 + 0.5;
2018  yuv.z = rgb.x * 0.5 + rgb.y * -0.419 + rgb.z * -0.081 + 0.5;
2019  break;
2021  yuv.x = rgb.x * 0.257 + rgb.y * 0.516 + rgb.z * 0.100 + 0.063;
2022  yuv.y = rgb.x * -0.145 + rgb.y * -0.291 + rgb.z * 0.439 + 0.5;
2023  yuv.z = rgb.x * 0.429 + rgb.y * -0.368 + rgb.z * -0.071 + 0.5;
2024  break;
2025  }
2026  return yuv;
2027 }
2028 
2029 static std::vector<std::shared_ptr<Texture>> CreateTestYUVTextures(
2030  Context* context,
2031  YUVColorSpace yuv_color_space) {
2032  Vector3 red = {244.0 / 255.0, 67.0 / 255.0, 54.0 / 255.0};
2033  Vector3 green = {76.0 / 255.0, 175.0 / 255.0, 80.0 / 255.0};
2034  Vector3 blue = {33.0 / 255.0, 150.0 / 255.0, 243.0 / 255.0};
2035  Vector3 white = {1.0, 1.0, 1.0};
2036  Vector3 red_yuv = RGBToYUV(red, yuv_color_space);
2037  Vector3 green_yuv = RGBToYUV(green, yuv_color_space);
2038  Vector3 blue_yuv = RGBToYUV(blue, yuv_color_space);
2039  Vector3 white_yuv = RGBToYUV(white, yuv_color_space);
2040  std::vector<Vector3> yuvs{red_yuv, green_yuv, blue_yuv, white_yuv};
2041  std::vector<uint8_t> y_data;
2042  std::vector<uint8_t> uv_data;
2043  for (int i = 0; i < 4; i++) {
2044  auto yuv = yuvs[i];
2045  uint8_t y = std::round(yuv.x * 255.0);
2046  uint8_t u = std::round(yuv.y * 255.0);
2047  uint8_t v = std::round(yuv.z * 255.0);
2048  for (int j = 0; j < 16; j++) {
2049  y_data.push_back(y);
2050  }
2051  for (int j = 0; j < 8; j++) {
2052  uv_data.push_back(j % 2 == 0 ? u : v);
2053  }
2054  }
2055  impeller::TextureDescriptor y_texture_descriptor;
2056  y_texture_descriptor.storage_mode = impeller::StorageMode::kHostVisible;
2057  y_texture_descriptor.format = PixelFormat::kR8UNormInt;
2058  y_texture_descriptor.size = {8, 8};
2059  auto y_texture =
2060  context->GetResourceAllocator()->CreateTexture(y_texture_descriptor);
2061  auto y_mapping = std::make_shared<fml::DataMapping>(y_data);
2062  if (!y_texture->SetContents(y_mapping)) {
2063  FML_DLOG(ERROR) << "Could not copy contents into Y texture.";
2064  }
2065 
2066  impeller::TextureDescriptor uv_texture_descriptor;
2067  uv_texture_descriptor.storage_mode = impeller::StorageMode::kHostVisible;
2068  uv_texture_descriptor.format = PixelFormat::kR8G8UNormInt;
2069  uv_texture_descriptor.size = {4, 4};
2070  auto uv_texture =
2071  context->GetResourceAllocator()->CreateTexture(uv_texture_descriptor);
2072  auto uv_mapping = std::make_shared<fml::DataMapping>(uv_data);
2073  if (!uv_texture->SetContents(uv_mapping)) {
2074  FML_DLOG(ERROR) << "Could not copy contents into UV texture.";
2075  }
2076 
2077  return {y_texture, uv_texture};
2078 }
2079 
2080 TEST_P(EntityTest, YUVToRGBFilter) {
2081  if (GetParam() == PlaygroundBackend::kOpenGLES) {
2082  // TODO(114588) : Support YUV to RGB filter on OpenGLES backend.
2083  GTEST_SKIP_("YUV to RGB filter is not supported on OpenGLES backend yet.");
2084  }
2085 
2086  auto callback = [&](ContentContext& context, RenderPass& pass) -> bool {
2087  YUVColorSpace yuv_color_space_array[2]{YUVColorSpace::kBT601FullRange,
2089  for (int i = 0; i < 2; i++) {
2090  auto yuv_color_space = yuv_color_space_array[i];
2091  auto textures =
2092  CreateTestYUVTextures(GetContext().get(), yuv_color_space);
2093  auto filter_contents = FilterContents::MakeYUVToRGBFilter(
2094  textures[0], textures[1], yuv_color_space);
2095  Entity filter_entity;
2096  filter_entity.SetContents(filter_contents);
2097  auto snapshot = filter_contents->RenderToSnapshot(context, filter_entity);
2098 
2099  Entity entity;
2100  auto contents = TextureContents::MakeRect(Rect::MakeLTRB(0, 0, 256, 256));
2101  contents->SetTexture(snapshot->texture);
2102  contents->SetSourceRect(Rect::MakeSize(snapshot->texture->GetSize()));
2103  entity.SetContents(contents);
2104  entity.SetTransformation(
2105  Matrix::MakeTranslation({static_cast<Scalar>(100 + 400 * i), 300}));
2106  entity.Render(context, pass);
2107  }
2108  return true;
2109  };
2110  ASSERT_TRUE(OpenPlaygroundHere(callback));
2111 }
2112 
2113 TEST_P(EntityTest, RuntimeEffect) {
2114  if (GetParam() != PlaygroundBackend::kMetal) {
2115  GTEST_SKIP_("This backend doesn't support runtime effects.");
2116  }
2117 
2118  auto runtime_stage =
2119  OpenAssetAsRuntimeStage("runtime_stage_example.frag.iplr");
2120  ASSERT_TRUE(runtime_stage->IsDirty());
2121 
2122  bool first_frame = true;
2123  auto callback = [&](ContentContext& context, RenderPass& pass) -> bool {
2124  if (first_frame) {
2125  first_frame = false;
2126  } else {
2127  assert(runtime_stage->IsDirty() == false);
2128  }
2129 
2130  auto contents = std::make_shared<RuntimeEffectContents>();
2131  contents->SetGeometry(Geometry::MakeCover());
2132 
2133  contents->SetRuntimeStage(runtime_stage);
2134 
2135  struct FragUniforms {
2136  Vector2 iResolution;
2137  Scalar iTime;
2138  } frag_uniforms = {
2139  .iResolution = Vector2(GetWindowSize().width, GetWindowSize().height),
2140  .iTime = static_cast<Scalar>(GetSecondsElapsed()),
2141  };
2142  auto uniform_data = std::make_shared<std::vector<uint8_t>>();
2143  uniform_data->resize(sizeof(FragUniforms));
2144  memcpy(uniform_data->data(), &frag_uniforms, sizeof(FragUniforms));
2145  contents->SetUniformData(uniform_data);
2146 
2147  Entity entity;
2148  entity.SetContents(contents);
2149  return contents->Render(context, entity, pass);
2150  };
2151  ASSERT_TRUE(OpenPlaygroundHere(callback));
2152 }
2153 
2154 TEST_P(EntityTest, InheritOpacityTest) {
2155  Entity entity;
2156 
2157  // Texture contents can always accept opacity.
2158  auto texture_contents = std::make_shared<TextureContents>();
2159  texture_contents->SetOpacity(0.5);
2160  ASSERT_TRUE(texture_contents->CanInheritOpacity(entity));
2161 
2162  texture_contents->SetInheritedOpacity(0.5);
2163  ASSERT_EQ(texture_contents->GetOpacity(), 0.25);
2164  texture_contents->SetInheritedOpacity(0.5);
2165  ASSERT_EQ(texture_contents->GetOpacity(), 0.25);
2166 
2167  // Solid color contents can accept opacity if their geometry
2168  // doesn't overlap.
2169  auto solid_color = std::make_shared<SolidColorContents>();
2170  solid_color->SetGeometry(
2171  Geometry::MakeRect(Rect::MakeLTRB(100, 100, 200, 200)));
2172  solid_color->SetColor(Color::Blue().WithAlpha(0.5));
2173 
2174  ASSERT_TRUE(solid_color->CanInheritOpacity(entity));
2175 
2176  solid_color->SetInheritedOpacity(0.5);
2177  ASSERT_EQ(solid_color->GetColor().alpha, 0.25);
2178  solid_color->SetInheritedOpacity(0.5);
2179  ASSERT_EQ(solid_color->GetColor().alpha, 0.25);
2180 
2181  // Color source contents can accept opacity if their geometry
2182  // doesn't overlap.
2183  auto tiled_texture = std::make_shared<TiledTextureContents>();
2184  tiled_texture->SetGeometry(
2185  Geometry::MakeRect(Rect::MakeLTRB(100, 100, 200, 200)));
2186  tiled_texture->SetOpacityFactor(0.5);
2187 
2188  ASSERT_TRUE(tiled_texture->CanInheritOpacity(entity));
2189 
2190  tiled_texture->SetInheritedOpacity(0.5);
2191  ASSERT_EQ(tiled_texture->GetOpacityFactor(), 0.25);
2192  tiled_texture->SetInheritedOpacity(0.5);
2193  ASSERT_EQ(tiled_texture->GetOpacityFactor(), 0.25);
2194 
2195  // Text contents can accept opacity if the text frames do not
2196  // overlap
2197  SkFont font;
2198  font.setSize(30);
2199  auto blob = SkTextBlob::MakeFromString("A", font);
2200  auto frame = MakeTextFrameFromTextBlobSkia(blob);
2201  auto lazy_glyph_atlas =
2202  std::make_shared<LazyGlyphAtlas>(TypographerContextSkia::Make());
2203  lazy_glyph_atlas->AddTextFrame(*frame, 1.0f);
2204 
2205  auto text_contents = std::make_shared<TextContents>();
2206  text_contents->SetTextFrame(frame);
2207  text_contents->SetColor(Color::Blue().WithAlpha(0.5));
2208 
2209  ASSERT_TRUE(text_contents->CanInheritOpacity(entity));
2210 
2211  text_contents->SetInheritedOpacity(0.5);
2212  ASSERT_EQ(text_contents->GetColor().alpha, 0.25);
2213  text_contents->SetInheritedOpacity(0.5);
2214  ASSERT_EQ(text_contents->GetColor().alpha, 0.25);
2215 
2216  // Clips and restores trivially accept opacity.
2217  ASSERT_TRUE(ClipContents().CanInheritOpacity(entity));
2218  ASSERT_TRUE(ClipRestoreContents().CanInheritOpacity(entity));
2219 
2220  // Runtime effect contents can't accept opacity.
2221  auto runtime_effect = std::make_shared<RuntimeEffectContents>();
2222  ASSERT_FALSE(runtime_effect->CanInheritOpacity(entity));
2223 }
2224 
2225 TEST_P(EntityTest, ColorFilterWithForegroundColorAdvancedBlend) {
2226  auto image = CreateTextureForFixture("boston.jpg");
2227  auto filter = ColorFilterContents::MakeBlend(
2229 
2230  auto callback = [&](ContentContext& context, RenderPass& pass) -> bool {
2231  Entity entity;
2232  entity.SetTransformation(Matrix::MakeScale(GetContentScale()) *
2233  Matrix::MakeTranslation({500, 300}) *
2234  Matrix::MakeScale(Vector2{0.5, 0.5}));
2235  entity.SetContents(filter);
2236  return entity.Render(context, pass);
2237  };
2238  ASSERT_TRUE(OpenPlaygroundHere(callback));
2239 }
2240 
2241 TEST_P(EntityTest, ColorFilterWithForegroundColorClearBlend) {
2242  auto image = CreateTextureForFixture("boston.jpg");
2243  auto filter = ColorFilterContents::MakeBlend(
2245 
2246  auto callback = [&](ContentContext& context, RenderPass& pass) -> bool {
2247  Entity entity;
2248  entity.SetTransformation(Matrix::MakeScale(GetContentScale()) *
2249  Matrix::MakeTranslation({500, 300}) *
2250  Matrix::MakeScale(Vector2{0.5, 0.5}));
2251  entity.SetContents(filter);
2252  return entity.Render(context, pass);
2253  };
2254  ASSERT_TRUE(OpenPlaygroundHere(callback));
2255 }
2256 
2257 TEST_P(EntityTest, ColorFilterWithForegroundColorSrcBlend) {
2258  auto image = CreateTextureForFixture("boston.jpg");
2259  auto filter = ColorFilterContents::MakeBlend(
2261 
2262  auto callback = [&](ContentContext& context, RenderPass& pass) -> bool {
2263  Entity entity;
2264  entity.SetTransformation(Matrix::MakeScale(GetContentScale()) *
2265  Matrix::MakeTranslation({500, 300}) *
2266  Matrix::MakeScale(Vector2{0.5, 0.5}));
2267  entity.SetContents(filter);
2268  return entity.Render(context, pass);
2269  };
2270  ASSERT_TRUE(OpenPlaygroundHere(callback));
2271 }
2272 
2273 TEST_P(EntityTest, ColorFilterWithForegroundColorDstBlend) {
2274  auto image = CreateTextureForFixture("boston.jpg");
2275  auto filter = ColorFilterContents::MakeBlend(
2277 
2278  auto callback = [&](ContentContext& context, RenderPass& pass) -> bool {
2279  Entity entity;
2280  entity.SetTransformation(Matrix::MakeScale(GetContentScale()) *
2281  Matrix::MakeTranslation({500, 300}) *
2282  Matrix::MakeScale(Vector2{0.5, 0.5}));
2283  entity.SetContents(filter);
2284  return entity.Render(context, pass);
2285  };
2286  ASSERT_TRUE(OpenPlaygroundHere(callback));
2287 }
2288 
2289 TEST_P(EntityTest, ColorFilterWithForegroundColorSrcInBlend) {
2290  auto image = CreateTextureForFixture("boston.jpg");
2291  auto filter = ColorFilterContents::MakeBlend(
2293 
2294  auto callback = [&](ContentContext& context, RenderPass& pass) -> bool {
2295  Entity entity;
2296  entity.SetTransformation(Matrix::MakeScale(GetContentScale()) *
2297  Matrix::MakeTranslation({500, 300}) *
2298  Matrix::MakeScale(Vector2{0.5, 0.5}));
2299  entity.SetContents(filter);
2300  return entity.Render(context, pass);
2301  };
2302  ASSERT_TRUE(OpenPlaygroundHere(callback));
2303 }
2304 
2305 TEST_P(EntityTest, CoverageForStrokePathWithNegativeValuesInTransform) {
2306  auto arrow_head = PathBuilder{}
2307  .MoveTo({50, 120})
2308  .LineTo({120, 190})
2309  .LineTo({190, 120})
2310  .TakePath();
2311  auto geometry = Geometry::MakeStrokePath(arrow_head, 15.0, 4.0, Cap::kRound,
2312  Join::kRound);
2313 
2314  auto transform = Matrix::MakeTranslation({300, 300}) *
2316  EXPECT_LT(transform.e[0][0], 0.f);
2317  auto coverage = geometry->GetCoverage(transform);
2318  ASSERT_RECT_NEAR(coverage.value(), Rect::MakeXYWH(102.5, 342.5, 85, 155));
2319 }
2320 
2321 TEST_P(EntityTest, SolidColorContentsIsOpaque) {
2322  SolidColorContents contents;
2323  contents.SetColor(Color::CornflowerBlue());
2324  ASSERT_TRUE(contents.IsOpaque());
2325  contents.SetColor(Color::CornflowerBlue().WithAlpha(0.5));
2326  ASSERT_FALSE(contents.IsOpaque());
2327 }
2328 
2329 TEST_P(EntityTest, ConicalGradientContentsIsOpaque) {
2330  ConicalGradientContents contents;
2331  contents.SetColors({Color::CornflowerBlue()});
2332  ASSERT_FALSE(contents.IsOpaque());
2333  contents.SetColors({Color::CornflowerBlue().WithAlpha(0.5)});
2334  ASSERT_FALSE(contents.IsOpaque());
2335 }
2336 
2337 TEST_P(EntityTest, LinearGradientContentsIsOpaque) {
2338  LinearGradientContents contents;
2339  contents.SetColors({Color::CornflowerBlue()});
2340  ASSERT_TRUE(contents.IsOpaque());
2341  contents.SetColors({Color::CornflowerBlue().WithAlpha(0.5)});
2342  ASSERT_FALSE(contents.IsOpaque());
2343  contents.SetColors({Color::CornflowerBlue()});
2345  ASSERT_FALSE(contents.IsOpaque());
2346 }
2347 
2348 TEST_P(EntityTest, RadialGradientContentsIsOpaque) {
2349  RadialGradientContents contents;
2350  contents.SetColors({Color::CornflowerBlue()});
2351  ASSERT_TRUE(contents.IsOpaque());
2352  contents.SetColors({Color::CornflowerBlue().WithAlpha(0.5)});
2353  ASSERT_FALSE(contents.IsOpaque());
2354  contents.SetColors({Color::CornflowerBlue()});
2356  ASSERT_FALSE(contents.IsOpaque());
2357 }
2358 
2359 TEST_P(EntityTest, SweepGradientContentsIsOpaque) {
2360  RadialGradientContents contents;
2361  contents.SetColors({Color::CornflowerBlue()});
2362  ASSERT_TRUE(contents.IsOpaque());
2363  contents.SetColors({Color::CornflowerBlue().WithAlpha(0.5)});
2364  ASSERT_FALSE(contents.IsOpaque());
2365  contents.SetColors({Color::CornflowerBlue()});
2367  ASSERT_FALSE(contents.IsOpaque());
2368 }
2369 
2370 TEST_P(EntityTest, TiledTextureContentsIsOpaque) {
2371  auto bay_bridge = CreateTextureForFixture("bay_bridge.jpg");
2372  TiledTextureContents contents;
2373  contents.SetTexture(bay_bridge);
2374  // This is a placeholder test. Images currently never decompress as opaque
2375  // (whether in Flutter or the playground), and so this should currently always
2376  // return false in practice.
2377  ASSERT_FALSE(contents.IsOpaque());
2378 }
2379 
2381  {
2382  // Sanity check simple rectangle.
2383  auto [pts, indices] =
2385  .AddRect(Rect::MakeLTRB(0, 0, 10, 10))
2386  .TakePath()
2387  .CreatePolyline(1.0));
2388 
2389  std::vector<Point> expected = {
2390  {0, 0}, {10, 0}, {10, 10}, {0, 10}, //
2391  };
2392  std::vector<uint16_t> expected_indices = {0, 1, 2, 0, 2, 3};
2393  ASSERT_EQ(pts, expected);
2394  ASSERT_EQ(indices, expected_indices);
2395  }
2396 
2397  {
2398  auto [pts, indices] =
2400  .AddRect(Rect::MakeLTRB(0, 0, 10, 10))
2401  .AddRect(Rect::MakeLTRB(20, 20, 30, 30))
2402  .TakePath()
2403  .CreatePolyline(1.0));
2404 
2405  std::vector<Point> expected = {
2406  {0, 0}, {10, 0}, {10, 10}, {0, 10}, //
2407  {20, 20}, {30, 20}, {30, 30}, {20, 30} //
2408  };
2409  std::vector<uint16_t> expected_indices = {0, 1, 2, 0, 2, 3,
2410  0, 6, 7, 0, 7, 8};
2411  ASSERT_EQ(pts, expected);
2412  ASSERT_EQ(indices, expected_indices);
2413  }
2414 }
2415 
2416 TEST_P(EntityTest, PointFieldGeometryDivisions) {
2417  // Square always gives 4 divisions.
2418  ASSERT_EQ(PointFieldGeometry::ComputeCircleDivisions(24.0, false), 4u);
2419  ASSERT_EQ(PointFieldGeometry::ComputeCircleDivisions(2.0, false), 4u);
2420  ASSERT_EQ(PointFieldGeometry::ComputeCircleDivisions(200.0, false), 4u);
2421 
2422  ASSERT_EQ(PointFieldGeometry::ComputeCircleDivisions(0.5, true), 4u);
2423  ASSERT_EQ(PointFieldGeometry::ComputeCircleDivisions(1.5, true), 8u);
2424  ASSERT_EQ(PointFieldGeometry::ComputeCircleDivisions(5.5, true), 24u);
2425  ASSERT_EQ(PointFieldGeometry::ComputeCircleDivisions(12.5, true), 34u);
2426  ASSERT_EQ(PointFieldGeometry::ComputeCircleDivisions(22.3, true), 22u);
2427  ASSERT_EQ(PointFieldGeometry::ComputeCircleDivisions(40.5, true), 40u);
2428  ASSERT_EQ(PointFieldGeometry::ComputeCircleDivisions(100.0, true), 100u);
2429  // Caps at 140.
2430  ASSERT_EQ(PointFieldGeometry::ComputeCircleDivisions(1000.0, true), 140u);
2431  ASSERT_EQ(PointFieldGeometry::ComputeCircleDivisions(20000.0, true), 140u);
2432 }
2433 
2434 TEST_P(EntityTest, PointFieldGeometryCoverage) {
2435  std::vector<Point> points = {{10, 20}, {100, 200}};
2436  auto geometry = Geometry::MakePointField(points, 5.0, false);
2437  ASSERT_EQ(*geometry->GetCoverage(Matrix()), Rect::MakeLTRB(5, 15, 105, 205));
2438  ASSERT_EQ(*geometry->GetCoverage(Matrix::MakeTranslation({30, 0, 0})),
2439  Rect::MakeLTRB(35, 15, 135, 205));
2440 }
2441 
2442 TEST_P(EntityTest, ColorFilterContentsWithLargeGeometry) {
2443  Entity entity;
2444  entity.SetTransformation(Matrix::MakeScale(GetContentScale()));
2445  auto src_contents = std::make_shared<SolidColorContents>();
2446  src_contents->SetGeometry(
2447  Geometry::MakeRect(Rect::MakeLTRB(-300, -500, 30000, 50000)));
2448  src_contents->SetColor(Color::Red());
2449 
2450  auto dst_contents = std::make_shared<SolidColorContents>();
2451  dst_contents->SetGeometry(
2452  Geometry::MakeRect(Rect::MakeLTRB(300, 500, 20000, 30000)));
2453  dst_contents->SetColor(Color::Blue());
2454 
2455  auto contents = ColorFilterContents::MakeBlend(
2456  BlendMode::kSourceOver, {FilterInput::Make(dst_contents, false),
2457  FilterInput::Make(src_contents, false)});
2458  entity.SetContents(std::move(contents));
2459  ASSERT_TRUE(OpenPlaygroundHere(entity));
2460 }
2461 
2462 TEST_P(EntityTest, TextContentsCeilsGlyphScaleToDecimal) {
2463  ASSERT_EQ(TextFrame::RoundScaledFontSize(0.4321111f, 12), 0.43f);
2464  ASSERT_EQ(TextFrame::RoundScaledFontSize(0.5321111f, 12), 0.53f);
2465  ASSERT_EQ(TextFrame::RoundScaledFontSize(2.1f, 12), 2.1f);
2466  ASSERT_EQ(TextFrame::RoundScaledFontSize(0.0f, 12), 0.0f);
2467 }
2468 
2470  public:
2471  explicit TestRenderTargetAllocator(std::shared_ptr<Allocator> allocator)
2472  : RenderTargetAllocator(std::move(allocator)) {}
2473 
2474  ~TestRenderTargetAllocator() = default;
2475 
2476  std::shared_ptr<Texture> CreateTexture(
2477  const TextureDescriptor& desc) override {
2478  allocated_.push_back(desc);
2480  }
2481 
2482  void Start() override { RenderTargetAllocator::Start(); }
2483 
2484  void End() override { RenderTargetAllocator::End(); }
2485 
2486  std::vector<TextureDescriptor> GetDescriptors() const { return allocated_; }
2487 
2488  private:
2489  std::vector<TextureDescriptor> allocated_;
2490 };
2491 
2492 TEST_P(EntityTest, AdvancedBlendCoverageHintIsNotResetByEntityPass) {
2493  if (GetContext()->GetCapabilities()->SupportsFramebufferFetch()) {
2494  GTEST_SKIP() << "Backends that support framebuffer fetch dont use coverage "
2495  "for advanced blends.";
2496  }
2497 
2498  auto contents = std::make_shared<SolidColorContents>();
2499  contents->SetGeometry(Geometry::MakeRect({100, 100, 100, 100}));
2500  contents->SetColor(Color::Red());
2501 
2502  Entity entity;
2503  entity.SetTransformation(Matrix::MakeScale(Vector3(2, 2, 1)));
2505  entity.SetContents(contents);
2506 
2507  auto coverage = entity.GetCoverage();
2508  EXPECT_TRUE(coverage.has_value());
2509 
2510  auto pass = std::make_unique<EntityPass>();
2511  auto test_allocator = std::make_shared<TestRenderTargetAllocator>(
2512  GetContext()->GetResourceAllocator());
2513  auto stencil_config = RenderTarget::AttachmentConfig{
2515  .load_action = LoadAction::kClear,
2516  .store_action = StoreAction::kDontCare,
2517  .clear_color = Color::BlackTransparent()};
2519  *GetContext(), *test_allocator, ISize::MakeWH(1000, 1000), "Offscreen",
2521  auto content_context = ContentContext(
2522  GetContext(), TypographerContextSkia::Make(), test_allocator);
2523  pass->AddEntity(entity);
2524 
2525  EXPECT_TRUE(pass->Render(content_context, rt));
2526 
2527  if (test_allocator->GetDescriptors().size() == 6u) {
2528  EXPECT_EQ(test_allocator->GetDescriptors()[0].size, ISize(1000, 1000));
2529  EXPECT_EQ(test_allocator->GetDescriptors()[1].size, ISize(1000, 1000));
2530 
2531  EXPECT_EQ(test_allocator->GetDescriptors()[2].size, ISize(200, 200));
2532  EXPECT_EQ(test_allocator->GetDescriptors()[3].size, ISize(200, 200));
2533  EXPECT_EQ(test_allocator->GetDescriptors()[4].size, ISize(200, 200));
2534  EXPECT_EQ(test_allocator->GetDescriptors()[5].size, ISize(200, 200));
2535  } else if (test_allocator->GetDescriptors().size() == 9u) {
2536  // Onscreen render target.
2537  EXPECT_EQ(test_allocator->GetDescriptors()[0].size, ISize(1000, 1000));
2538  EXPECT_EQ(test_allocator->GetDescriptors()[1].size, ISize(1000, 1000));
2539  EXPECT_EQ(test_allocator->GetDescriptors()[2].size, ISize(1000, 1000));
2540  EXPECT_EQ(test_allocator->GetDescriptors()[3].size, ISize(1000, 1000));
2541  EXPECT_EQ(test_allocator->GetDescriptors()[4].size, ISize(1000, 1000));
2542 
2543  EXPECT_EQ(test_allocator->GetDescriptors()[5].size, ISize(200, 200));
2544  EXPECT_EQ(test_allocator->GetDescriptors()[5].size, ISize(200, 200));
2545  EXPECT_EQ(test_allocator->GetDescriptors()[6].size, ISize(200, 200));
2546  EXPECT_EQ(test_allocator->GetDescriptors()[7].size, ISize(200, 200));
2547  } else {
2548  EXPECT_TRUE(false);
2549  }
2550 }
2551 
2552 } // namespace testing
2553 } // namespace impeller
2554 
2555 // NOLINTEND(bugprone-unchecked-optional-access)
text_contents.h
impeller::Matrix::MakeSkew
static constexpr Matrix MakeSkew(Scalar sx, Scalar sy)
Definition: matrix.h:116
impeller::BlendMode
BlendMode
Definition: color.h:57
impeller::SolidColorContents::Make
static std::unique_ptr< SolidColorContents > Make(const Path &path, Color color)
Definition: solid_color_contents.cc:89
impeller::Color::Blue
static constexpr Color Blue()
Definition: color.h:266
impeller::Entity::ClipOperation::kIntersect
@ kIntersect
impeller::TiledTextureContents::IsOpaque
bool IsOpaque() const override
Whether this Contents only emits opaque source colors from the fragment stage. This value does not ac...
Definition: tiled_texture_contents.cc:102
impeller::Entity::TileMode::kClamp
@ kClamp
impeller::OptionsFromPass
ContentContextOptions OptionsFromPass(const RenderPass &pass)
Definition: contents.cc:20
impeller::Entity::SetTransformation
void SetTransformation(const Matrix &transformation)
Definition: entity.cc:53
impeller::Command
An object used to specify work to the GPU along with references to resources the GPU will used when d...
Definition: command.h:99
impeller::Color::DeepPink
static constexpr Color DeepPink()
Definition: color.h:424
DEBUG_COMMAND_INFO
#define DEBUG_COMMAND_INFO(obj, arg)
Definition: command.h:31
impeller::Matrix::MakeRotationX
static Matrix MakeRotationX(Radians r)
Definition: matrix.h:181
impeller::testing::TestPassDelegate
Definition: entity_unittests.cc:75
impeller::Cap::kRound
@ kRound
impeller::Entity::kLastPipelineBlendMode
static constexpr BlendMode kLastPipelineBlendMode
Definition: entity.h:23
impeller::SolidColorContents::IsOpaque
bool IsOpaque() const override
Whether this Contents only emits opaque source colors from the fragment stage. This value does not ac...
Definition: solid_color_contents.cc:31
impeller::Cap::kSquare
@ kSquare
contents.h
impeller::FilterContents::MorphType::kErode
@ kErode
entity_pass_delegate.h
impeller::TPoint::y
Type y
Definition: point.h:24
impeller::Entity::ClipOperation::kDifference
@ kDifference
impeller::Scalar
float Scalar
Definition: scalar.h:15
impeller::EntityPass::GetSubpassCoverage
std::optional< Rect > GetSubpassCoverage(const EntityPass &subpass, std::optional< Rect > coverage_limit) const
Get the coverage of an unfiltered subpass.
Definition: entity_pass.cc:195
impeller::Entity::SetBlendMode
void SetBlendMode(BlendMode blend_mode)
Definition: entity.cc:101
impeller::RenderTarget::kDefaultColorAttachmentConfig
static constexpr AttachmentConfig kDefaultColorAttachmentConfig
Definition: render_target.h:65
texture_contents.h
impeller::Color::Red
static constexpr Color Red()
Definition: color.h:262
geometry_asserts.h
stroke_path_geometry.h
entity.h
impeller::RenderTargetAllocator::Start
virtual void Start()
Mark the beginning of a frame workload.
Definition: render_target.cc:21
impeller::TRect< Scalar >::MakeXYWH
constexpr static TRect MakeXYWH(Type x, Type y, Type width, Type height)
Definition: rect.h:47
impeller::FilterContents::BlurStyle
BlurStyle
Definition: filter_contents.h:21
solid_color_contents.h
impeller::Color
Definition: color.h:122
impeller::FilterInput::Make
static FilterInput::Ref Make(Variant input, bool msaa_enabled=true)
Definition: filter_input.cc:19
impeller::ConicalGradientContents::SetColors
void SetColors(std::vector< Color > colors)
Definition: conical_gradient_contents.cc:36
point_field_geometry.h
impeller::TextureDescriptor::format
PixelFormat format
Definition: texture_descriptor.h:42
impeller::testing::TestRenderTargetAllocator::TestRenderTargetAllocator
TestRenderTargetAllocator(std::shared_ptr< Allocator > allocator)
Definition: entity_unittests.cc:2471
impeller::Entity::TileMode::kDecal
@ kDecal
impeller::PlaygroundBackend::kMetal
@ kMetal
impeller::EntityPass::AddSubpass
EntityPass * AddSubpass(std::unique_ptr< EntityPass > pass)
Appends a given pass as a subpass.
Definition: entity_pass.cc:226
texture_descriptor.h
impeller::testing::TestPassDelegate::TestPassDelegate
TestPassDelegate(bool collapse=false)
Definition: entity_unittests.cc:77
impeller::TRect::TransformBounds
constexpr TRect TransformBounds(const Matrix &transform) const
Creates a new bounding box that contains this transformed rectangle.
Definition: rect.h:204
impeller::PathBuilder
Definition: path_builder.h:13
impeller::BlendMode::kSourceIn
@ kSourceIn
tiled_texture_contents.h
impeller::Vector2
Point Vector2
Definition: point.h:310
impeller::Matrix::MakeRotationY
static Matrix MakeRotationY(Radians r)
Definition: matrix.h:194
impeller::Geometry::MakeRect
static std::unique_ptr< Geometry > MakeRect(Rect rect)
Definition: geometry.cc:142
impeller::kPi
constexpr float kPi
Definition: constants.h:25
impeller::Color::alpha
Scalar alpha
Definition: color.h:141
impeller::StoreAction::kDontCare
@ kDontCare
impeller::Color::MintCream
static constexpr Color MintCream()
Definition: color.h:648
playground.h
IMPELLER_PLAYGROUND_POINT
#define IMPELLER_PLAYGROUND_POINT(default_position, radius, color)
Definition: widgets.h:14
impeller::Color::CornflowerBlue
static constexpr Color CornflowerBlue()
Definition: color.h:332
impeller::TessellateConvex
std::pair< std::vector< Point >, std::vector< uint16_t > > TessellateConvex(Path::Polyline polyline)
Given a convex polyline, create a triangle fan structure.
Definition: geometry.cc:19
impeller::FilterContents::MakeDirectionalGaussianBlur
static std::shared_ptr< FilterContents > MakeDirectionalGaussianBlur(FilterInput::Ref input, Sigma sigma, Vector2 direction, BlurStyle blur_style=BlurStyle::kNormal, Entity::TileMode tile_mode=Entity::TileMode::kDecal, bool is_second_pass=false, Sigma secondary_sigma={})
Definition: filter_contents.cc:33
impeller::VertexBufferBuilder::AddVertices
VertexBufferBuilder & AddVertices(std::initializer_list< VertexType_ > vertices)
Definition: vertex_buffer_builder.h:64
impeller::PathBuilder::AddRoundedRect
PathBuilder & AddRoundedRect(Rect rect, RoundingRadii radii)
Definition: path_builder.cc:207
impeller::Color::Yellow
static constexpr Color Yellow()
Definition: color.h:832
impeller::FilterContents::BlurStyle::kNormal
@ kNormal
Blurred inside and outside.
impeller::PathBuilder::SetConvexity
PathBuilder & SetConvexity(Convexity value)
Definition: path_builder.cc:111
impeller::StorageMode::kHostVisible
@ kHostVisible
impeller::EntityPass::GetElementsCoverage
std::optional< Rect > GetElementsCoverage(std::optional< Rect > coverage_limit) const
Definition: entity_pass.cc:104
sweep_gradient_contents.h
impeller::BlendMode::kDestinationIn
@ kDestinationIn
impeller::Cap::kButt
@ kButt
impeller::TextFrame::RoundScaledFontSize
static Scalar RoundScaledFontSize(Scalar scale, Scalar point_size)
Definition: text_frame.cc:66
impeller::Entity::TileMode::kRepeat
@ kRepeat
impeller::Matrix::MakeTranslation
static constexpr Matrix MakeTranslation(const Vector3 &t)
Definition: matrix.h:94
impeller::EntityPassDelegate
Definition: entity_pass_delegate.h:19
impeller::RadialGradientContents::IsOpaque
bool IsOpaque() const override
Whether this Contents only emits opaque source colors from the fragment stage. This value does not ac...
Definition: radial_gradient_contents.cc:52
impeller::Vector3::x
Scalar x
Definition: vector.h:20
impeller::MoveTo
void MoveTo(PathBuilder *builder, Scalar x, Scalar y)
Definition: tessellator.cc:18
impeller::PathBuilder::AddRect
PathBuilder & AddRect(Rect rect)
Definition: path_builder.cc:181
impeller::Join::kMiter
@ kMiter
runtime_effect_contents.h
impeller::kPiOver2
constexpr float kPiOver2
Definition: constants.h:31
impeller::BlendMode::kXor
@ kXor
typographer_context_skia.h
impeller::RenderTarget::AttachmentConfig
Definition: render_target.h:50
ASSERT_RECT_NEAR
#define ASSERT_RECT_NEAR(a, b)
Definition: geometry_asserts.h:154
impeller::testing::TestRenderTargetAllocator
Definition: entity_unittests.cc:2469
impeller::Entity::GetTransformation
const Matrix & GetTransformation() const
Definition: entity.cc:49
impeller::Entity::TileMode::kMirror
@ kMirror
tessellator.h
impeller::Entity::SetContents
void SetContents(std::shared_ptr< Contents > contents)
Definition: entity.cc:81
vertices_contents.h
path_builder.h
impeller::LinearGradientContents::SetColors
void SetColors(std::vector< Color > colors)
Definition: linear_gradient_contents.cc:27
impeller::testing::INSTANTIATE_PLAYGROUND_SUITE
INSTANTIATE_PLAYGROUND_SUITE(AiksTest)
impeller::StrokePathGeometry
A geometry that is created from a stroked path object.
Definition: stroke_path_geometry.h:12
impeller::BlendMode::kColorBurn
@ kColorBurn
impeller::LinearGradientContents::SetTileMode
void SetTileMode(Entity::TileMode tile_mode)
Definition: linear_gradient_contents.cc:43
impeller::Entity
Definition: entity.h:21
command.h
impeller::ConicalGradientContents
Definition: conical_gradient_contents.h:21
text_frame_skia.h
impeller::testing::TEST_P
TEST_P(AiksTest, RotateColorFilteredPath)
Definition: aiks_unittests.cc:56
impeller::TSize
Definition: size.h:18
impeller::LoadAction::kClear
@ kClear
impeller::Point
TPoint< Scalar > Point
Definition: point.h:306
impeller::FilterContents::MakeGaussianBlur
static std::shared_ptr< FilterContents > MakeGaussianBlur(const FilterInput::Ref &input, Sigma sigma_x, Sigma sigma_y, BlurStyle blur_style=BlurStyle::kNormal, Entity::TileMode tile_mode=Entity::TileMode::kDecal)
Definition: filter_contents.cc:52
filter_contents.h
render_pass.h
runtime_stage.h
impeller::BlendMode::kSourceOver
@ kSourceOver
impeller::testing::RGBToYUV
static Vector3 RGBToYUV(Vector3 rgb, YUVColorSpace yuv_color_space)
Definition: entity_unittests.cc:2012
impeller::Entity::Render
bool Render(const ContentContext &renderer, RenderPass &parent_pass) const
Definition: entity.cc:159
impeller::testing::TestPassDelegate::WithImageFilter
std::shared_ptr< FilterContents > WithImageFilter(const FilterInput::Variant &input, const Matrix &effect_transform) const override
Definition: entity_unittests.cc:98
sigma.h
impeller::BlendMode::kPlus
@ kPlus
impeller::Radius
For convolution filters, the "radius" is the size of the convolution kernel to use on the local space...
Definition: sigma.h:47
impeller::FilterContents::BlurStyle::kSolid
@ kSolid
Solid inside, blurred outside.
impeller::Path
Paths are lightweight objects that describe a collection of linear, quadratic, or cubic segments....
Definition: path.h:54
impeller::BlendMode::kDestination
@ kDestination
impeller::Geometry::MakeStrokePath
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:125
widgets.h
conical_gradient_contents.h
impeller::EntityPass
Definition: entity_pass.h:26
impeller::VertexBufferBuilder
Definition: vertex_buffer_builder.h:23
impeller::YUVColorSpace::kBT601FullRange
@ kBT601FullRange
impeller::testing::TestPassDelegate::~TestPassDelegate
~TestPassDelegate() override=default
impeller::StorageMode::kDevicePrivate
@ kDevicePrivate
impeller::Color::WithAlpha
constexpr Color WithAlpha(Scalar new_alpha) const
Definition: color.h:268
impeller::SolidColorContents
Definition: solid_color_contents.h:24
impeller::testing::TestRenderTargetAllocator::~TestRenderTargetAllocator
~TestRenderTargetAllocator()=default
impeller::ColorFilterContents::MakeSrgbToLinearFilter
static std::shared_ptr< ColorFilterContents > MakeSrgbToLinearFilter(FilterInput::Ref input)
Definition: color_filter_contents.cc:75
impeller::ColorMatrix::array
Scalar array[20]
Definition: color.h:116
geometry.h
impeller::BlendMode::kDestinationATop
@ kDestinationATop
impeller::PathBuilder::LineTo
PathBuilder & LineTo(Point point, bool relative=false)
Insert a line from the current position to point.
Definition: path_builder.cc:46
impeller::ContentContext::GetSolidFillPipeline
std::shared_ptr< Pipeline< PipelineDescriptor > > GetSolidFillPipeline(ContentContextOptions opts) const
Definition: content_context.h:417
impeller::PathBuilder::AddCubicCurve
PathBuilder & AddCubicCurve(Point p1, Point cp1, Point cp2, Point p2)
Move to point p1, then insert a cubic curve from p1 to p2 with control points cp1 and cp2.
Definition: path_builder.cc:172
impeller::Color::White
static constexpr Color White()
Definition: color.h:254
impeller::Sigma
In filters that use Gaussian distributions, "sigma" is a size of one standard deviation in terms of t...
Definition: sigma.h:31
impeller::Vector3::z
Scalar z
Definition: vector.h:22
impeller::Radians
Definition: scalar.h:35
impeller::RenderTarget::AttachmentConfig::storage_mode
StorageMode storage_mode
Definition: render_target.h:51
impeller::RenderTargetAllocator::CreateTexture
virtual std::shared_ptr< Texture > CreateTexture(const TextureDescriptor &desc)
Create a new render target texture, or recycle a previously allocated render target texture.
Definition: render_target.cc:25
impeller::PathBuilder::AddLine
PathBuilder & AddLine(const Point &p1, const Point &p2)
Move to point p1, then insert a line from p1 to p2.
Definition: path_builder.cc:433
clip_contents.h
impeller::PixelFormat::kR8G8UNormInt
@ kR8G8UNormInt
impeller::FilterInput::Variant
std::variant< std::shared_ptr< FilterContents >, std::shared_ptr< Contents >, std::shared_ptr< Texture >, Rect > Variant
Definition: filter_input.h:36
impeller::testing::CreateTestYUVTextures
static std::vector< std::shared_ptr< Texture > > CreateTestYUVTextures(Context *context, YUVColorSpace yuv_color_space)
Definition: entity_unittests.cc:2029
entity_pass.h
impeller::Color::Green
static constexpr Color Green()
Definition: color.h:264
impeller::PathBuilder::TakePath
Path TakePath(FillType fill=FillType::kNonZero)
Definition: path_builder.cc:21
filter_input.h
impeller::Rect
TRect< Scalar > Rect
Definition: rect.h:306
impeller::Join::kRound
@ kRound
impeller::FilterContents::MakeBorderMaskBlur
static std::shared_ptr< FilterContents > MakeBorderMaskBlur(FilterInput::Ref input, Sigma sigma_x, Sigma sigma_y, BlurStyle blur_style=BlurStyle::kNormal)
Definition: filter_contents.cc:66
impeller::LinearGradientContents::IsOpaque
bool IsOpaque() const override
Whether this Contents only emits opaque source colors from the fragment stage. This value does not ac...
Definition: linear_gradient_contents.cc:47
impeller::BlendMode::kDestinationOut
@ kDestinationOut
impeller::Vector3::y
Scalar y
Definition: vector.h:21
impeller::FilterContents::BlurStyle::kInner
@ kInner
Blurred inside, nothing outside.
impeller::ClipRestoreContents
Definition: clip_contents.h:57
impeller::RenderTargetAllocator
a wrapper around the impeller [Allocator] instance that can be used to provide caching of allocated r...
Definition: render_target.h:22
color_filter_contents.h
impeller::YUVColorSpace::kBT601LimitedRange
@ kBT601LimitedRange
impeller::Entity::TileMode
TileMode
Definition: entity.h:40
impeller::BlendMode::kSoftLight
@ kSoftLight
impeller::testing::TestRenderTargetAllocator::End
void End() override
Mark the end of a frame workload.
Definition: entity_unittests.cc:2484
impeller::ColorFilterContents::MakeLinearToSrgbFilter
static std::shared_ptr< ColorFilterContents > MakeLinearToSrgbFilter(FilterInput::Ref input)
Definition: color_filter_contents.cc:68
impeller::Entity::GetBlendMode
BlendMode GetBlendMode() const
Definition: entity.cc:105
impeller::Geometry::MakeCover
static std::unique_ptr< Geometry > MakeCover()
Definition: geometry.cc:138
impeller::testing::CreatePassWithRectPath
auto CreatePassWithRectPath(Rect rect, std::optional< Rect > bounds_hint, bool collapse=false)
Definition: entity_unittests.cc:109
impeller::MakeTextFrameFromTextBlobSkia
std::shared_ptr< TextFrame > MakeTextFrameFromTextBlobSkia(const sk_sp< SkTextBlob > &blob)
Definition: text_frame_skia.cc:41
impeller::TPoint::x
Type x
Definition: point.h:23
impeller::FilterContents::MakeYUVToRGBFilter
static std::shared_ptr< FilterContents > MakeYUVToRGBFilter(std::shared_ptr< Texture > y_texture, std::shared_ptr< Texture > uv_texture, YUVColorSpace yuv_color_space)
Definition: filter_contents.cc:123
impeller::VertexBufferBuilder::CreateVertexBuffer
VertexBuffer CreateVertexBuffer(HostBuffer &host_buffer) const
Definition: vertex_buffer_builder.h:78
impeller::Matrix::IsIdentity
constexpr bool IsIdentity() const
Definition: matrix.h:345
impeller::Close
void Close(PathBuilder *builder)
Definition: tessellator.cc:36
impeller::testing::TestRenderTargetAllocator::Start
void Start() override
Mark the beginning of a frame workload.
Definition: entity_unittests.cc:2482
impeller::RenderPass
Render passes encode render commands directed as one specific render target into an underlying comman...
Definition: render_pass.h:27
impeller::TextureDescriptor::size
ISize size
Definition: texture_descriptor.h:43
impeller::RenderTarget::CreateOffscreen
static RenderTarget CreateOffscreen(const Context &context, RenderTargetAllocator &allocator, ISize size, const std::string &label="Offscreen", AttachmentConfig color_attachment_config=kDefaultColorAttachmentConfig, std::optional< AttachmentConfig > stencil_attachment_config=kDefaultStencilAttachmentConfig)
Definition: render_target.cc:223
atlas_contents.h
impeller::FilterContents::MorphType
MorphType
Definition: filter_contents.h:32
impeller::Join::kBevel
@ kBevel
impeller::EntityPlayground
Definition: entity_playground.h:17
impeller::FilterContents::BlurStyle::kOuter
@ kOuter
Nothing inside, blurred outside.
impeller::Entity::GetCoverage
std::optional< Rect > GetCoverage() const
Definition: entity.cc:57
impeller::PlaygroundBackend::kOpenGLES
@ kOpenGLES
impeller::Matrix::MakeRotationZ
static Matrix MakeRotationZ(Radians r)
Definition: matrix.h:208
impeller::FilterContents::MakeMorphology
static std::shared_ptr< FilterContents > MakeMorphology(FilterInput::Ref input, Radius radius_x, Radius radius_y, MorphType morph_type)
Definition: filter_contents.cc:91
impeller::PathBuilder::Close
PathBuilder & Close()
Definition: path_builder.cc:39
impeller::Geometry::MakePointField
static std::unique_ptr< Geometry > MakePointField(std::vector< Point > points, Scalar radius, bool round)
Definition: geometry.cc:119
impeller::LineTo
void LineTo(PathBuilder *builder, Scalar x, Scalar y)
Definition: tessellator.cc:22
vector.h
impeller::Join
Join
Definition: path.h:23
impeller::TiledTextureContents
Definition: tiled_texture_contents.h:21
impeller::Context
To do anything rendering related with Impeller, you need a context.
Definition: context.h:47
impeller::TRect< Scalar >::MakeSize
constexpr static TRect MakeSize(const TSize< U > &size)
Definition: rect.h:52
impeller::TPoint::GetLength
constexpr Type GetLength() const
Definition: point.h:199
std
Definition: comparable.h:98
impeller::FilterContents::MorphType::kDilate
@ kDilate
impeller::BlendMode::kDestinationOver
@ kDestinationOver
impeller::RadialGradientContents::SetTileMode
void SetTileMode(Entity::TileMode tile_mode)
Definition: radial_gradient_contents.cc:28
impeller::TPoint< Scalar >
impeller::RenderTargetAllocator::End
virtual void End()
Mark the end of a frame workload.
Definition: render_target.cc:23
impeller::PathBuilder::MoveTo
PathBuilder & MoveTo(Point point, bool relative=false)
Definition: path_builder.cc:32
impeller::Color::BlackTransparent
static constexpr Color BlackTransparent()
Definition: color.h:260
impeller::SolidColorContents::SetColor
void SetColor(Color color)
Definition: solid_color_contents.cc:19
impeller::Geometry::MakeFillPath
static std::unique_ptr< Geometry > MakeFillPath(const Path &path, std::optional< Rect > inner_rect=std::nullopt)
Definition: geometry.cc:113
impeller::Color::Black
static constexpr Color Black()
Definition: color.h:256
impeller::BlendMode::kClear
@ kClear
impeller::PointFieldGeometry::ComputeCircleDivisions
static size_t ComputeCircleDivisions(Scalar scaled_radius, bool round)
Compute the number of vertices to divide each circle into.
Definition: point_field_geometry.cc:244
impeller::ClipContents
Definition: clip_contents.h:18
impeller::Matrix::MakeOrthographic
static constexpr Matrix MakeOrthographic(TSize< T > size)
Definition: matrix.h:448
impeller::YUVColorSpace
YUVColorSpace
Definition: color.h:53
impeller::ColorFilterContents::MakeColorMatrix
static std::shared_ptr< ColorFilterContents > MakeColorMatrix(FilterInput::Ref input, const ColorMatrix &color_matrix)
Definition: color_filter_contents.cc:58
color.h
impeller::LinearGradientContents
Definition: linear_gradient_contents.h:22
impeller::Color::WhiteTransparent
static constexpr Color WhiteTransparent()
Definition: color.h:258
impeller::testing::TestRenderTargetAllocator::GetDescriptors
std::vector< TextureDescriptor > GetDescriptors() const
Definition: entity_unittests.cc:2486
impeller::PrimitiveType::kTriangle
@ kTriangle
impeller::TextureDescriptor::storage_mode
StorageMode storage_mode
Definition: texture_descriptor.h:40
radial_gradient_contents.h
impeller::testing::TestPassDelegate::CanElide
bool CanElide() override
Definition: entity_unittests.cc:83
blend_filter_contents.h
impeller::PixelFormat::kR8UNormInt
@ kR8UNormInt
impeller::TRect< Scalar >::MakeLTRB
constexpr static TRect MakeLTRB(Type left, Type top, Type right, Type bottom)
Definition: rect.h:40
impeller::TextureDescriptor
A lightweight object that describes the attributes of a texture that can then used an allocator to cr...
Definition: texture_descriptor.h:39
impeller::Command::BindVertices
bool BindVertices(const VertexBuffer &buffer)
Specify the vertex and index buffer to use for this command.
Definition: command.cc:15
impeller::BlendMode::kSourceATop
@ kSourceATop
impeller::Command::pipeline
std::shared_ptr< Pipeline< PipelineDescriptor > > pipeline
Definition: command.h:103
impeller::BlendMode::kModulate
@ kModulate
impeller::Convexity::kConvex
@ kConvex
impeller::TPoint::Normalize
constexpr TPoint Normalize() const
Definition: point.h:201
impeller::Contents::StencilCoverage::Type::kAppend
@ kAppend
impeller::Contents::IsOpaque
virtual bool IsOpaque() const
Whether this Contents only emits opaque source colors from the fragment stage. This value does not ac...
Definition: contents.cc:48
impeller::Color::Coral
static constexpr Color Coral()
Definition: color.h:328
impeller::TSize< int64_t >::MakeWH
static constexpr TSize MakeWH(Type width, Type height)
Definition: size.h:33
impeller::RenderPipelineT::VertexShader
VertexShader_ VertexShader
Definition: pipeline.h:90
impeller::ColorMatrix
Definition: color.h:115
impeller::ColorFilterContents::MakeBlend
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.
Definition: color_filter_contents.cc:17
impeller::TextureContents::MakeRect
static std::shared_ptr< TextureContents > MakeRect(Rect destination)
A common case factory that marks the texture contents as having a destination rectangle....
Definition: texture_contents.cc:28
impeller::RadialGradientContents
Definition: radial_gradient_contents.h:21
IMPELLER_PLAYGROUND_LINE
#define IMPELLER_PLAYGROUND_LINE(default_position_a, default_position_b, radius, color_a, color_b)
Definition: widgets.h:55
impeller::BlendMode::kScreen
@ kScreen
impeller::testing::TestPassDelegate::CanCollapseIntoParentPass
bool CanCollapseIntoParentPass(EntityPass *entity_pass) override
Whether or not this entity pass can be collapsed into the parent. If true, this method may modify the...
Definition: entity_unittests.cc:86
impeller::RadialGradientContents::SetColors
void SetColors(std::vector< Color > colors)
Definition: radial_gradient_contents.cc:36
impeller
Definition: aiks_context.cc:10
impeller::Matrix::MakeScale
static constexpr Matrix MakeScale(const Vector3 &s)
Definition: matrix.h:103
impeller::testing::TestPassDelegate::CreateContentsForSubpassTarget
std::shared_ptr< Contents > CreateContentsForSubpassTarget(std::shared_ptr< Texture > target, const Matrix &transform) override
Definition: entity_unittests.cc:91
impeller::Context::GetResourceAllocator
virtual std::shared_ptr< Allocator > GetResourceAllocator() const =0
Returns the allocator used to create textures and buffers on the device.
solid_rrect_blur_contents.h
impeller::ContentContext
Definition: content_context.h:344
impeller::BlendMode::kSourceOut
@ kSourceOut
impeller::Color::Premultiply
constexpr Color Premultiply() const
Definition: color.h:212
impeller::TypographerContextSkia::Make
static std::shared_ptr< TypographerContext > Make()
Definition: typographer_context_skia.cc:30
impeller::TRect< Scalar >
impeller::BlendMode::kSource
@ kSource
impeller::Matrix
A 4x4 matrix using column-major storage.
Definition: matrix.h:36
impeller::Vector3
Definition: vector.h:17
impeller::Entity::ShouldRender
bool ShouldRender(const std::optional< Rect > &stencil_coverage) const
Definition: entity.cc:73
impeller::Cap
Cap
Definition: path.h:17
impeller::EntityPass::AddEntity
void AddEntity(Entity entity)
Definition: entity_pass.cc:77
entity_playground.h
vertex_buffer_builder.h
linear_gradient_contents.h
impeller::TiledTextureContents::SetTexture
void SetTexture(std::shared_ptr< Texture > texture)
Definition: tiled_texture_contents.cc:44
impeller::testing::TestRenderTargetAllocator::CreateTexture
std::shared_ptr< Texture > CreateTexture(const TextureDescriptor &desc) override
Create a new render target texture, or recycle a previously allocated render target texture.
Definition: entity_unittests.cc:2476