| Flutter Impeller |

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