Flutter Engine
The Flutter Engine
Loading...
Searching...
No Matches
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"
58#include "third_party/imgui/imgui.h"
60
61// TODO(zanderso): https://github.com/flutter/flutter/issues/127701
62// NOLINTBEGIN(bugprone-unchecked-optional-access)
63
64namespace impeller {
65namespace testing {
66
67using EntityTest = EntityPlayground;
69
70TEST_P(EntityTest, CanCreateEntity) {
71 Entity entity;
72 ASSERT_TRUE(entity.GetTransform().IsIdentity());
73}
74
76 public:
77 explicit TestPassDelegate(bool collapse = false) : collapse_(collapse) {}
78
79 // |EntityPassDelegate|
80 ~TestPassDelegate() override = default;
81
82 // |EntityPassDelgate|
83 bool CanElide() override { return false; }
84
85 // |EntityPassDelgate|
86 bool CanCollapseIntoParentPass(EntityPass* entity_pass) override {
87 return collapse_;
88 }
89
90 // |EntityPassDelgate|
91 std::shared_ptr<Contents> CreateContentsForSubpassTarget(
92 std::shared_ptr<Texture> target,
93 const Matrix& transform) override {
94 return nullptr;
95 }
96
97 // |EntityPassDelegate|
98 std::shared_ptr<FilterContents> WithImageFilter(
99 const FilterInput::Variant& input,
100 const Matrix& effect_transform) const override {
101 return nullptr;
102 }
103
104 private:
105 const std::optional<Rect> coverage_;
106 const bool collapse_;
107};
108
110 Rect rect,
111 std::optional<Rect> bounds_hint,
113 bool collapse = false) {
114 auto subpass = std::make_unique<EntityPass>();
115 Entity entity;
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
124TEST_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
151TEST_P(EntityTest, EntityPassTrustsSnugSubpassBoundsLimit) {
152 EntityPass pass;
153
154 auto subpass0 = //
156 Rect::MakeLTRB(5, 5, 95, 95),
158 auto subpass1 = //
159 CreatePassWithRectPath(Rect::MakeLTRB(500, 500, 1000, 1000),
160 Rect::MakeLTRB(495, 495, 1005, 1005),
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
187TEST_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),
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
209TEST_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
239TEST_P(EntityTest, FilterCoverageRespectsCropRect) {
240 auto image = CreateTextureForFixture("boston.jpg");
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
264TEST_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
276TEST_P(EntityTest, CanDrawRRect) {
277 auto contents = std::make_shared<SolidColorContents>();
278 auto path = PathBuilder{}
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
292TEST_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
300TEST_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
319TEST_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
340TEST_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
383TEST_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(),
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(),
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(),
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(),
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(),
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(),
516 }
517
518 return true;
519 };
520 ASSERT_TRUE(OpenPlaygroundHere(callback));
521}
522
523TEST_P(EntityTest, CubicCurveTest) {
524 // Compare with https://fiddle.skia.org/c/b3625f26122c9de7afe7794fcf25ead3
525 Path path =
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()));
542 ASSERT_TRUE(OpenPlaygroundHere(std::move(entity)));
543}
544
545TEST_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())
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);
586 return entity.Render(context, pass);
587 };
588 ASSERT_TRUE(OpenPlaygroundHere(callback));
589}
590
591TEST_P(EntityTest, CubicCurveAndOverlapTest) {
592 // Compare with https://fiddle.skia.org/c/7a05a3e186c65a8dfb732f68020aae06
593 Path path =
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()));
816 ASSERT_TRUE(OpenPlaygroundHere(std::move(entity)));
817}
818
819TEST_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
841TEST_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
861TEST_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.
874 switch (b) {
876 blend_mode_names.push_back("Clear");
877 blend_mode_values.push_back(BlendMode::kClear);
879 blend_mode_names.push_back("Source");
880 blend_mode_values.push_back(BlendMode::kSource);
882 blend_mode_names.push_back("Destination");
883 blend_mode_values.push_back(BlendMode::kDestination);
885 blend_mode_names.push_back("SourceOver");
886 blend_mode_values.push_back(BlendMode::kSourceOver);
888 blend_mode_names.push_back("DestinationOver");
889 blend_mode_values.push_back(BlendMode::kDestinationOver);
891 blend_mode_names.push_back("SourceIn");
892 blend_mode_values.push_back(BlendMode::kSourceIn);
894 blend_mode_names.push_back("DestinationIn");
895 blend_mode_values.push_back(BlendMode::kDestinationIn);
897 blend_mode_names.push_back("SourceOut");
898 blend_mode_values.push_back(BlendMode::kSourceOut);
900 blend_mode_names.push_back("DestinationOut");
901 blend_mode_values.push_back(BlendMode::kDestinationOut);
903 blend_mode_names.push_back("SourceATop");
904 blend_mode_values.push_back(BlendMode::kSourceATop);
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);
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 {
925
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),
980 result = result && draw_rect(Rect::MakeLTRB(a.x, a.y, b.x, b.y), color1,
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
989TEST_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}));
1018 return entity.Render(context, pass);
1019 };
1020 ASSERT_TRUE(OpenPlaygroundHere(callback));
1021}
1022
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(
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
1051TEST_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[] = {
1065 const Entity::TileMode tile_modes[] = {
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:
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()) *
1182 Matrix::MakeRotationZ(Radians(rotation)) *
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;
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()) {
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
1224TEST_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[] = {
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()) *
1290 Matrix::MakeRotationZ(Radians(rotation)) *
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;
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;
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
1324TEST_P(EntityTest, SetBlendMode) {
1325 Entity entity;
1326 ASSERT_EQ(entity.GetBlendMode(), BlendMode::kSourceOver);
1328 ASSERT_EQ(entity.GetBlendMode(), BlendMode::kClear);
1329}
1330
1331TEST_P(EntityTest, ContentsGetBoundsForEmptyPathReturnsNullopt) {
1332 Entity entity;
1333 entity.SetContents(std::make_shared<SolidColorContents>());
1334 ASSERT_FALSE(entity.GetCoverage().has_value());
1335}
1336
1337TEST_P(EntityTest, SolidStrokeCoverageIsCorrect) {
1338 {
1339 auto geometry = Geometry::MakeStrokePath(
1340 PathBuilder{}.AddLine({0, 0}, {10, 10}).TakePath(), 4.0, 4.0,
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,
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,
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
1390TEST_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
1417TEST_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
1449TEST_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
1484TEST_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
1520TEST_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(),
1551
1552 contents->SetCullRect(Rect::MakeLTRB(0, 0, 10, 10));
1553
1554 ASSERT_EQ(contents->GetCoverage(e).value(),
1556}
1557
1558TEST_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
1593TEST_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
1613TEST_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;
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
1656TEST_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
1688TEST_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
1702TEST_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
1726TEST_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));
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));
1783 }
1784}
1785
1786TEST_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
1839TEST_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 =
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
1868TEST_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.
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()) *
1913 Matrix::MakeRotationZ(Radians(rotation)) *
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
1926TEST_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 =
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
1947TEST_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 =
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}));
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
1978TEST_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 =
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
1999TEST_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 =
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}));
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
2030static Vector3 RGBToYUV(Vector3 rgb, YUVColorSpace yuv_color_space) {
2031 Vector3 yuv;
2032 switch (yuv_color_space) {
2034 yuv.x = rgb.x * 0.299 + rgb.y * 0.587 + rgb.z * 0.114;
2035 yuv.y = rgb.x * -0.169 + rgb.y * -0.331 + rgb.z * 0.5 + 0.5;
2036 yuv.z = rgb.x * 0.5 + rgb.y * -0.419 + rgb.z * -0.081 + 0.5;
2037 break;
2039 yuv.x = rgb.x * 0.257 + rgb.y * 0.516 + rgb.z * 0.100 + 0.063;
2040 yuv.y = rgb.x * -0.145 + rgb.y * -0.291 + rgb.z * 0.439 + 0.5;
2041 yuv.z = rgb.x * 0.429 + rgb.y * -0.368 + rgb.z * -0.071 + 0.5;
2042 break;
2043 }
2044 return yuv;
2045}
2046
2047static std::vector<std::shared_ptr<Texture>> CreateTestYUVTextures(
2048 Context* context,
2049 YUVColorSpace yuv_color_space) {
2050 Vector3 red = {244.0 / 255.0, 67.0 / 255.0, 54.0 / 255.0};
2051 Vector3 green = {76.0 / 255.0, 175.0 / 255.0, 80.0 / 255.0};
2052 Vector3 blue = {33.0 / 255.0, 150.0 / 255.0, 243.0 / 255.0};
2053 Vector3 white = {1.0, 1.0, 1.0};
2054 Vector3 red_yuv = RGBToYUV(red, yuv_color_space);
2055 Vector3 green_yuv = RGBToYUV(green, yuv_color_space);
2056 Vector3 blue_yuv = RGBToYUV(blue, yuv_color_space);
2057 Vector3 white_yuv = RGBToYUV(white, yuv_color_space);
2058 std::vector<Vector3> yuvs{red_yuv, green_yuv, blue_yuv, white_yuv};
2059 std::vector<uint8_t> y_data;
2060 std::vector<uint8_t> uv_data;
2061 for (int i = 0; i < 4; i++) {
2062 auto yuv = yuvs[i];
2063 uint8_t y = std::round(yuv.x * 255.0);
2064 uint8_t u = std::round(yuv.y * 255.0);
2065 uint8_t v = std::round(yuv.z * 255.0);
2066 for (int j = 0; j < 16; j++) {
2067 y_data.push_back(y);
2068 }
2069 for (int j = 0; j < 8; j++) {
2070 uv_data.push_back(j % 2 == 0 ? u : v);
2071 }
2072 }
2073 impeller::TextureDescriptor y_texture_descriptor;
2075 y_texture_descriptor.format = PixelFormat::kR8UNormInt;
2076 y_texture_descriptor.size = {8, 8};
2077 auto y_texture =
2078 context->GetResourceAllocator()->CreateTexture(y_texture_descriptor);
2079 auto y_mapping = std::make_shared<fml::DataMapping>(y_data);
2080 if (!y_texture->SetContents(y_mapping)) {
2081 FML_DLOG(ERROR) << "Could not copy contents into Y texture.";
2082 }
2083
2084 impeller::TextureDescriptor uv_texture_descriptor;
2085 uv_texture_descriptor.storage_mode = impeller::StorageMode::kHostVisible;
2086 uv_texture_descriptor.format = PixelFormat::kR8G8UNormInt;
2087 uv_texture_descriptor.size = {4, 4};
2088 auto uv_texture =
2089 context->GetResourceAllocator()->CreateTexture(uv_texture_descriptor);
2090 auto uv_mapping = std::make_shared<fml::DataMapping>(uv_data);
2091 if (!uv_texture->SetContents(uv_mapping)) {
2092 FML_DLOG(ERROR) << "Could not copy contents into UV texture.";
2093 }
2094
2095 return {y_texture, uv_texture};
2096}
2097
2098TEST_P(EntityTest, YUVToRGBFilter) {
2099 if (GetParam() == PlaygroundBackend::kOpenGLES) {
2100 // TODO(114588) : Support YUV to RGB filter on OpenGLES backend.
2101 GTEST_SKIP_("YUV to RGB filter is not supported on OpenGLES backend yet.");
2102 }
2103
2104 auto callback = [&](ContentContext& context, RenderPass& pass) -> bool {
2105 YUVColorSpace yuv_color_space_array[2]{YUVColorSpace::kBT601FullRange,
2107 for (int i = 0; i < 2; i++) {
2108 auto yuv_color_space = yuv_color_space_array[i];
2109 auto textures =
2110 CreateTestYUVTextures(GetContext().get(), yuv_color_space);
2111 auto filter_contents = FilterContents::MakeYUVToRGBFilter(
2112 textures[0], textures[1], yuv_color_space);
2113 Entity filter_entity;
2114 filter_entity.SetContents(filter_contents);
2115 auto snapshot = filter_contents->RenderToSnapshot(context, filter_entity);
2116
2117 Entity entity;
2118 auto contents = TextureContents::MakeRect(Rect::MakeLTRB(0, 0, 256, 256));
2119 contents->SetTexture(snapshot->texture);
2120 contents->SetSourceRect(Rect::MakeSize(snapshot->texture->GetSize()));
2121 entity.SetContents(contents);
2122 entity.SetTransform(
2123 Matrix::MakeTranslation({static_cast<Scalar>(100 + 400 * i), 300}));
2124 entity.Render(context, pass);
2125 }
2126 return true;
2127 };
2128 ASSERT_TRUE(OpenPlaygroundHere(callback));
2129}
2130
2131TEST_P(EntityTest, RuntimeEffect) {
2132 auto runtime_stages =
2133 OpenAssetAsRuntimeStage("runtime_stage_example.frag.iplr");
2134 auto runtime_stage =
2135 runtime_stages[PlaygroundBackendToRuntimeStageBackend(GetBackend())];
2136 ASSERT_TRUE(runtime_stage);
2137 ASSERT_TRUE(runtime_stage->IsDirty());
2138
2139 bool expect_dirty = true;
2140 Pipeline<PipelineDescriptor>* first_pipeline;
2141 auto callback = [&](ContentContext& context, RenderPass& pass) -> bool {
2142 EXPECT_EQ(runtime_stage->IsDirty(), expect_dirty);
2143
2144 auto contents = std::make_shared<RuntimeEffectContents>();
2145 contents->SetGeometry(Geometry::MakeCover());
2146 contents->SetRuntimeStage(runtime_stage);
2147
2148 struct FragUniforms {
2149 Vector2 iResolution;
2150 Scalar iTime;
2151 } frag_uniforms = {
2152 .iResolution = Vector2(GetWindowSize().width, GetWindowSize().height),
2153 .iTime = static_cast<Scalar>(GetSecondsElapsed()),
2154 };
2155 auto uniform_data = std::make_shared<std::vector<uint8_t>>();
2156 uniform_data->resize(sizeof(FragUniforms));
2157 memcpy(uniform_data->data(), &frag_uniforms, sizeof(FragUniforms));
2158 contents->SetUniformData(uniform_data);
2159
2160 Entity entity;
2161 entity.SetContents(contents);
2162 bool result = contents->Render(context, entity, pass);
2163
2164 if (expect_dirty) {
2165 EXPECT_NE(first_pipeline, pass.GetCommands().back().pipeline.get());
2166 first_pipeline = pass.GetCommands().back().pipeline.get();
2167 } else {
2168 EXPECT_EQ(pass.GetCommands().back().pipeline.get(), first_pipeline);
2169 }
2170
2171 expect_dirty = false;
2172 return result;
2173 };
2174
2175 // Simulate some renders and hot reloading of the shader.
2176 auto content_context = GetContentContext();
2177 {
2179 content_context->GetRenderTargetCache()->CreateOffscreen(
2180 *content_context->GetContext(), {1, 1}, 1u);
2181
2182 testing::MockRenderPass mock_pass(GetContext(), target);
2183 callback(*content_context, mock_pass);
2184 callback(*content_context, mock_pass);
2185
2186 // Dirty the runtime stage.
2187 runtime_stages = OpenAssetAsRuntimeStage("runtime_stage_example.frag.iplr");
2188 runtime_stage =
2189 runtime_stages[PlaygroundBackendToRuntimeStageBackend(GetBackend())];
2190
2191 ASSERT_TRUE(runtime_stage->IsDirty());
2192 expect_dirty = true;
2193
2194 callback(*content_context, mock_pass);
2195 }
2196}
2197
2198TEST_P(EntityTest, RuntimeEffectCanSuccessfullyRender) {
2199 auto runtime_stages =
2200 OpenAssetAsRuntimeStage("runtime_stage_example.frag.iplr");
2201 auto runtime_stage =
2202 runtime_stages[PlaygroundBackendToRuntimeStageBackend(GetBackend())];
2203 ASSERT_TRUE(runtime_stage);
2204 ASSERT_TRUE(runtime_stage->IsDirty());
2205
2206 auto contents = std::make_shared<RuntimeEffectContents>();
2207 contents->SetGeometry(Geometry::MakeCover());
2208
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
2226 // Create a render target with a depth-stencil, similar to how EntityPass
2227 // does.
2229 GetContentContext()->GetRenderTargetCache()->CreateOffscreenMSAA(
2230 *GetContext(), {GetWindowSize().width, GetWindowSize().height}, 1,
2231 "RuntimeEffect Texture");
2232 testing::MockRenderPass pass(GetContext(), target);
2233
2234 ASSERT_TRUE(contents->Render(*GetContentContext(), entity, pass));
2235 ASSERT_EQ(pass.GetCommands().size(), 1u);
2236 const auto& command = pass.GetCommands()[0];
2237 ASSERT_TRUE(command.pipeline->GetDescriptor()
2238 .GetDepthStencilAttachmentDescriptor()
2239 .has_value());
2240 ASSERT_TRUE(command.pipeline->GetDescriptor()
2241 .GetFrontStencilAttachmentDescriptor()
2242 .has_value());
2243}
2244
2245TEST_P(EntityTest, RuntimeEffectCanPrecache) {
2246 auto runtime_stages =
2247 OpenAssetAsRuntimeStage("runtime_stage_example.frag.iplr");
2248 auto runtime_stage =
2249 runtime_stages[PlaygroundBackendToRuntimeStageBackend(GetBackend())];
2250 ASSERT_TRUE(runtime_stage);
2251 ASSERT_TRUE(runtime_stage->IsDirty());
2252
2253 auto contents = std::make_shared<RuntimeEffectContents>();
2254 contents->SetRuntimeStage(runtime_stage);
2255
2256 EXPECT_TRUE(contents->BootstrapShader(*GetContentContext()));
2257}
2258
2259TEST_P(EntityTest, RuntimeEffectSetsRightSizeWhenUniformIsStruct) {
2260 if (GetBackend() != PlaygroundBackend::kVulkan) {
2261 GTEST_SKIP() << "Test only applies to Vulkan";
2262 }
2263
2264 auto runtime_stages =
2265 OpenAssetAsRuntimeStage("runtime_stage_example.frag.iplr");
2266 auto runtime_stage =
2267 runtime_stages[PlaygroundBackendToRuntimeStageBackend(GetBackend())];
2268 ASSERT_TRUE(runtime_stage);
2269 ASSERT_TRUE(runtime_stage->IsDirty());
2270
2271 auto contents = std::make_shared<RuntimeEffectContents>();
2272 contents->SetGeometry(Geometry::MakeCover());
2273 contents->SetRuntimeStage(runtime_stage);
2274
2275 struct FragUniforms {
2276 Vector2 iResolution;
2277 Scalar iTime;
2278 } frag_uniforms = {
2279 .iResolution = Vector2(GetWindowSize().width, GetWindowSize().height),
2280 .iTime = static_cast<Scalar>(GetSecondsElapsed()),
2281 };
2282 auto uniform_data = std::make_shared<std::vector<uint8_t>>();
2283 uniform_data->resize(sizeof(FragUniforms));
2284 memcpy(uniform_data->data(), &frag_uniforms, sizeof(FragUniforms));
2285 contents->SetUniformData(uniform_data);
2286
2287 Entity entity;
2288 entity.SetContents(contents);
2289
2290 auto context = GetContentContext();
2291 RenderTarget target = context->GetRenderTargetCache()->CreateOffscreen(
2292 *context->GetContext(), {1, 1}, 1u);
2293
2294 testing::MockRenderPass pass(GetContext(), target);
2295 ASSERT_TRUE(contents->Render(*context, entity, pass));
2296 ASSERT_EQ(pass.GetCommands().size(), 1u);
2297 const auto& command = pass.GetCommands()[0];
2298 ASSERT_EQ(command.fragment_bindings.buffers.size(), 1u);
2299 // 16 bytes:
2300 // 8 bytes for iResolution
2301 // 4 bytes for iTime
2302 // 4 bytes padding
2303 EXPECT_EQ(command.fragment_bindings.buffers[0].view.resource.range.length,
2304 16u);
2305}
2306
2307TEST_P(EntityTest, InheritOpacityTest) {
2308 Entity entity;
2309
2310 // Texture contents can always accept opacity.
2311 auto texture_contents = std::make_shared<TextureContents>();
2312 texture_contents->SetOpacity(0.5);
2313 ASSERT_TRUE(texture_contents->CanInheritOpacity(entity));
2314
2315 texture_contents->SetInheritedOpacity(0.5);
2316 ASSERT_EQ(texture_contents->GetOpacity(), 0.25);
2317 texture_contents->SetInheritedOpacity(0.5);
2318 ASSERT_EQ(texture_contents->GetOpacity(), 0.25);
2319
2320 // Solid color contents can accept opacity if their geometry
2321 // doesn't overlap.
2322 auto solid_color = std::make_shared<SolidColorContents>();
2323 solid_color->SetGeometry(
2324 Geometry::MakeRect(Rect::MakeLTRB(100, 100, 200, 200)));
2325 solid_color->SetColor(Color::Blue().WithAlpha(0.5));
2326
2327 ASSERT_TRUE(solid_color->CanInheritOpacity(entity));
2328
2329 solid_color->SetInheritedOpacity(0.5);
2330 ASSERT_EQ(solid_color->GetColor().alpha, 0.25);
2331 solid_color->SetInheritedOpacity(0.5);
2332 ASSERT_EQ(solid_color->GetColor().alpha, 0.25);
2333
2334 // Color source contents can accept opacity if their geometry
2335 // doesn't overlap.
2336 auto tiled_texture = std::make_shared<TiledTextureContents>();
2337 tiled_texture->SetGeometry(
2338 Geometry::MakeRect(Rect::MakeLTRB(100, 100, 200, 200)));
2339 tiled_texture->SetOpacityFactor(0.5);
2340
2341 ASSERT_TRUE(tiled_texture->CanInheritOpacity(entity));
2342
2343 tiled_texture->SetInheritedOpacity(0.5);
2344 ASSERT_EQ(tiled_texture->GetOpacityFactor(), 0.25);
2345 tiled_texture->SetInheritedOpacity(0.5);
2346 ASSERT_EQ(tiled_texture->GetOpacityFactor(), 0.25);
2347
2348 // Text contents can accept opacity if the text frames do not
2349 // overlap
2351 auto blob = SkTextBlob::MakeFromString("A", font);
2353 auto lazy_glyph_atlas =
2354 std::make_shared<LazyGlyphAtlas>(TypographerContextSkia::Make());
2355 lazy_glyph_atlas->AddTextFrame(*frame, 1.0f);
2356
2357 auto text_contents = std::make_shared<TextContents>();
2358 text_contents->SetTextFrame(frame);
2359 text_contents->SetColor(Color::Blue().WithAlpha(0.5));
2360
2361 ASSERT_TRUE(text_contents->CanInheritOpacity(entity));
2362
2363 text_contents->SetInheritedOpacity(0.5);
2364 ASSERT_EQ(text_contents->GetColor().alpha, 0.25);
2365 text_contents->SetInheritedOpacity(0.5);
2366 ASSERT_EQ(text_contents->GetColor().alpha, 0.25);
2367
2368 // Clips and restores trivially accept opacity.
2369 ASSERT_TRUE(ClipContents().CanInheritOpacity(entity));
2370 ASSERT_TRUE(ClipRestoreContents().CanInheritOpacity(entity));
2371
2372 // Runtime effect contents can't accept opacity.
2373 auto runtime_effect = std::make_shared<RuntimeEffectContents>();
2374 ASSERT_FALSE(runtime_effect->CanInheritOpacity(entity));
2375}
2376
2377TEST_P(EntityTest, ColorFilterWithForegroundColorAdvancedBlend) {
2378 auto image = CreateTextureForFixture("boston.jpg");
2379 auto filter = ColorFilterContents::MakeBlend(
2381
2382 auto callback = [&](ContentContext& context, RenderPass& pass) -> bool {
2383 Entity entity;
2384 entity.SetTransform(Matrix::MakeScale(GetContentScale()) *
2385 Matrix::MakeTranslation({500, 300}) *
2386 Matrix::MakeScale(Vector2{0.5, 0.5}));
2387 entity.SetContents(filter);
2388 return entity.Render(context, pass);
2389 };
2390 ASSERT_TRUE(OpenPlaygroundHere(callback));
2391}
2392
2393TEST_P(EntityTest, ColorFilterWithForegroundColorClearBlend) {
2394 auto image = CreateTextureForFixture("boston.jpg");
2395 auto filter = ColorFilterContents::MakeBlend(
2397
2398 auto callback = [&](ContentContext& context, RenderPass& pass) -> bool {
2399 Entity entity;
2400 entity.SetTransform(Matrix::MakeScale(GetContentScale()) *
2401 Matrix::MakeTranslation({500, 300}) *
2402 Matrix::MakeScale(Vector2{0.5, 0.5}));
2403 entity.SetContents(filter);
2404 return entity.Render(context, pass);
2405 };
2406 ASSERT_TRUE(OpenPlaygroundHere(callback));
2407}
2408
2409TEST_P(EntityTest, ColorFilterWithForegroundColorSrcBlend) {
2410 auto image = CreateTextureForFixture("boston.jpg");
2411 auto filter = ColorFilterContents::MakeBlend(
2413
2414 auto callback = [&](ContentContext& context, RenderPass& pass) -> bool {
2415 Entity entity;
2416 entity.SetTransform(Matrix::MakeScale(GetContentScale()) *
2417 Matrix::MakeTranslation({500, 300}) *
2418 Matrix::MakeScale(Vector2{0.5, 0.5}));
2419 entity.SetContents(filter);
2420 return entity.Render(context, pass);
2421 };
2422 ASSERT_TRUE(OpenPlaygroundHere(callback));
2423}
2424
2425TEST_P(EntityTest, ColorFilterWithForegroundColorDstBlend) {
2426 auto image = CreateTextureForFixture("boston.jpg");
2427 auto filter = ColorFilterContents::MakeBlend(
2429
2430 auto callback = [&](ContentContext& context, RenderPass& pass) -> bool {
2431 Entity entity;
2432 entity.SetTransform(Matrix::MakeScale(GetContentScale()) *
2433 Matrix::MakeTranslation({500, 300}) *
2434 Matrix::MakeScale(Vector2{0.5, 0.5}));
2435 entity.SetContents(filter);
2436 return entity.Render(context, pass);
2437 };
2438 ASSERT_TRUE(OpenPlaygroundHere(callback));
2439}
2440
2441TEST_P(EntityTest, ColorFilterWithForegroundColorSrcInBlend) {
2442 auto image = CreateTextureForFixture("boston.jpg");
2443 auto filter = ColorFilterContents::MakeBlend(
2445
2446 auto callback = [&](ContentContext& context, RenderPass& pass) -> bool {
2447 Entity entity;
2448 entity.SetTransform(Matrix::MakeScale(GetContentScale()) *
2449 Matrix::MakeTranslation({500, 300}) *
2450 Matrix::MakeScale(Vector2{0.5, 0.5}));
2451 entity.SetContents(filter);
2452 return entity.Render(context, pass);
2453 };
2454 ASSERT_TRUE(OpenPlaygroundHere(callback));
2455}
2456
2457TEST_P(EntityTest, CoverageForStrokePathWithNegativeValuesInTransform) {
2458 auto arrow_head = PathBuilder{}
2459 .MoveTo({50, 120})
2460 .LineTo({120, 190})
2461 .LineTo({190, 120})
2462 .TakePath();
2463 auto geometry = Geometry::MakeStrokePath(arrow_head, 15.0, 4.0, Cap::kRound,
2464 Join::kRound);
2465
2466 auto transform = Matrix::MakeTranslation({300, 300}) *
2468 // Note that e[0][0] used to be tested here, but it was -epsilon solely
2469 // due to floating point inaccuracy in the transcendental trig functions.
2470 // e[1][0] is the intended negative value that we care about (-1.0) as it
2471 // comes from the rotation of pi/2.
2472 EXPECT_LT(transform.e[1][0], 0.0f);
2473 auto coverage = geometry->GetCoverage(transform);
2474 ASSERT_RECT_NEAR(coverage.value(), Rect::MakeXYWH(102.5, 342.5, 85, 155));
2475}
2476
2477TEST_P(EntityTest, SolidColorContentsIsOpaque) {
2478 SolidColorContents contents;
2479 contents.SetColor(Color::CornflowerBlue());
2480 ASSERT_TRUE(contents.IsOpaque());
2481 contents.SetColor(Color::CornflowerBlue().WithAlpha(0.5));
2482 ASSERT_FALSE(contents.IsOpaque());
2483}
2484
2485TEST_P(EntityTest, ConicalGradientContentsIsOpaque) {
2486 ConicalGradientContents contents;
2487 contents.SetColors({Color::CornflowerBlue()});
2488 ASSERT_FALSE(contents.IsOpaque());
2489 contents.SetColors({Color::CornflowerBlue().WithAlpha(0.5)});
2490 ASSERT_FALSE(contents.IsOpaque());
2491}
2492
2493TEST_P(EntityTest, LinearGradientContentsIsOpaque) {
2494 LinearGradientContents contents;
2495 contents.SetColors({Color::CornflowerBlue()});
2496 ASSERT_TRUE(contents.IsOpaque());
2497 contents.SetColors({Color::CornflowerBlue().WithAlpha(0.5)});
2498 ASSERT_FALSE(contents.IsOpaque());
2499 contents.SetColors({Color::CornflowerBlue()});
2501 ASSERT_FALSE(contents.IsOpaque());
2502}
2503
2504TEST_P(EntityTest, RadialGradientContentsIsOpaque) {
2505 RadialGradientContents contents;
2506 contents.SetColors({Color::CornflowerBlue()});
2507 ASSERT_TRUE(contents.IsOpaque());
2508 contents.SetColors({Color::CornflowerBlue().WithAlpha(0.5)});
2509 ASSERT_FALSE(contents.IsOpaque());
2510 contents.SetColors({Color::CornflowerBlue()});
2512 ASSERT_FALSE(contents.IsOpaque());
2513}
2514
2515TEST_P(EntityTest, SweepGradientContentsIsOpaque) {
2516 RadialGradientContents contents;
2517 contents.SetColors({Color::CornflowerBlue()});
2518 ASSERT_TRUE(contents.IsOpaque());
2519 contents.SetColors({Color::CornflowerBlue().WithAlpha(0.5)});
2520 ASSERT_FALSE(contents.IsOpaque());
2521 contents.SetColors({Color::CornflowerBlue()});
2523 ASSERT_FALSE(contents.IsOpaque());
2524}
2525
2526TEST_P(EntityTest, TiledTextureContentsIsOpaque) {
2527 auto bay_bridge = CreateTextureForFixture("bay_bridge.jpg");
2528 TiledTextureContents contents;
2529 contents.SetTexture(bay_bridge);
2530 // This is a placeholder test. Images currently never decompress as opaque
2531 // (whether in Flutter or the playground), and so this should currently always
2532 // return false in practice.
2533 ASSERT_FALSE(contents.IsOpaque());
2534}
2535
2536TEST_P(EntityTest, PointFieldGeometryCoverage) {
2537 std::vector<Point> points = {{10, 20}, {100, 200}};
2538 auto geometry = Geometry::MakePointField(points, 5.0, false);
2539 ASSERT_EQ(*geometry->GetCoverage(Matrix()), Rect::MakeLTRB(5, 15, 105, 205));
2540 ASSERT_EQ(*geometry->GetCoverage(Matrix::MakeTranslation({30, 0, 0})),
2541 Rect::MakeLTRB(35, 15, 135, 205));
2542}
2543
2544TEST_P(EntityTest, ColorFilterContentsWithLargeGeometry) {
2545 Entity entity;
2546 entity.SetTransform(Matrix::MakeScale(GetContentScale()));
2547 auto src_contents = std::make_shared<SolidColorContents>();
2548 src_contents->SetGeometry(
2549 Geometry::MakeRect(Rect::MakeLTRB(-300, -500, 30000, 50000)));
2550 src_contents->SetColor(Color::Red());
2551
2552 auto dst_contents = std::make_shared<SolidColorContents>();
2553 dst_contents->SetGeometry(
2554 Geometry::MakeRect(Rect::MakeLTRB(300, 500, 20000, 30000)));
2555 dst_contents->SetColor(Color::Blue());
2556
2557 auto contents = ColorFilterContents::MakeBlend(
2558 BlendMode::kSourceOver, {FilterInput::Make(dst_contents, false),
2559 FilterInput::Make(src_contents, false)});
2560 entity.SetContents(std::move(contents));
2561 ASSERT_TRUE(OpenPlaygroundHere(std::move(entity)));
2562}
2563
2564TEST_P(EntityTest, TextContentsCeilsGlyphScaleToDecimal) {
2565 ASSERT_EQ(TextFrame::RoundScaledFontSize(0.4321111f, 12), 0.43f);
2566 ASSERT_EQ(TextFrame::RoundScaledFontSize(0.5321111f, 12), 0.53f);
2567 ASSERT_EQ(TextFrame::RoundScaledFontSize(2.1f, 12), 2.1f);
2568 ASSERT_EQ(TextFrame::RoundScaledFontSize(0.0f, 12), 0.0f);
2569}
2570
2571TEST_P(EntityTest, AdvancedBlendCoverageHintIsNotResetByEntityPass) {
2572 if (GetContext()->GetCapabilities()->SupportsFramebufferFetch()) {
2573 GTEST_SKIP() << "Backends that support framebuffer fetch dont use coverage "
2574 "for advanced blends.";
2575 }
2576
2577 auto contents = std::make_shared<SolidColorContents>();
2578 contents->SetGeometry(Geometry::MakeRect(Rect::MakeXYWH(100, 100, 100, 100)));
2579 contents->SetColor(Color::Red());
2580
2581 Entity entity;
2582 entity.SetTransform(Matrix::MakeScale(Vector3(2, 2, 1)));
2584 entity.SetContents(contents);
2585
2586 auto coverage = entity.GetCoverage();
2587 EXPECT_TRUE(coverage.has_value());
2588
2589 auto pass = std::make_unique<EntityPass>();
2590 std::shared_ptr<RenderTargetCache> render_target_allocator =
2591 std::make_shared<RenderTargetCache>(GetContext()->GetResourceAllocator());
2592 auto stencil_config = RenderTarget::AttachmentConfig{
2594 .load_action = LoadAction::kClear,
2595 .store_action = StoreAction::kDontCare,
2596 .clear_color = Color::BlackTransparent()};
2597 auto rt = render_target_allocator->CreateOffscreen(
2598 *GetContext(), ISize::MakeWH(1000, 1000),
2599 /*mip_count=*/1, "Offscreen", RenderTarget::kDefaultColorAttachmentConfig,
2600 stencil_config);
2601 auto content_context = ContentContext(
2602 GetContext(), TypographerContextSkia::Make(), render_target_allocator);
2603 pass->AddEntity(std::move(entity));
2604
2605 EXPECT_TRUE(pass->Render(content_context, rt));
2606
2607 auto contains_size = [&render_target_allocator](ISize size) -> bool {
2608 return std::find_if(render_target_allocator->GetRenderTargetDataBegin(),
2609 render_target_allocator->GetRenderTargetDataEnd(),
2610 [&size](const auto& data) {
2611 return data.config.size == size;
2612 }) != render_target_allocator->GetRenderTargetDataEnd();
2613 };
2614
2615 EXPECT_TRUE(contains_size(ISize(1000, 1000)))
2616 << "The root texture wasn't allocated";
2617 EXPECT_TRUE(contains_size(ISize(200, 200)))
2618 << "The ColorBurned texture wasn't allocated (100x100 scales up 2x)";
2619}
2620
2621TEST_P(EntityTest, SpecializationConstantsAreAppliedToVariants) {
2622 auto content_context = GetContentContext();
2623
2624 auto default_gyph = content_context->GetGlyphAtlasPipeline({
2625 .color_attachment_pixel_format = PixelFormat::kR8G8B8A8UNormInt,
2626 .has_depth_stencil_attachments = false,
2627 });
2628 auto alt_gyph = content_context->GetGlyphAtlasPipeline(
2629 {.color_attachment_pixel_format = PixelFormat::kR8G8B8A8UNormInt,
2630 .has_depth_stencil_attachments = true});
2631
2632 EXPECT_NE(default_gyph, alt_gyph);
2633 EXPECT_EQ(default_gyph->GetDescriptor().GetSpecializationConstants(),
2634 alt_gyph->GetDescriptor().GetSpecializationConstants());
2635
2636 auto use_a8 = GetContext()->GetCapabilities()->GetDefaultGlyphAtlasFormat() ==
2638
2639 std::vector<Scalar> expected_constants = {static_cast<Scalar>(use_a8)};
2640 EXPECT_EQ(default_gyph->GetDescriptor().GetSpecializationConstants(),
2641 expected_constants);
2642}
2643
2644TEST_P(EntityTest, DecalSpecializationAppliedToMorphologyFilter) {
2645 auto content_context = GetContentContext();
2646 auto default_color_burn = content_context->GetMorphologyFilterPipeline({
2647 .color_attachment_pixel_format = PixelFormat::kR8G8B8A8UNormInt,
2648 });
2649
2650 auto decal_supported = static_cast<Scalar>(
2651 GetContext()->GetCapabilities()->SupportsDecalSamplerAddressMode());
2652 std::vector<Scalar> expected_constants = {decal_supported};
2653 ASSERT_EQ(default_color_burn->GetDescriptor().GetSpecializationConstants(),
2654 expected_constants);
2655}
2656
2657// This doesn't really tell you if the hashes will have frequent
2658// collisions, but since this type is only used to hash a bounded
2659// set of options, we can just compare benchmarks.
2660TEST_P(EntityTest, ContentContextOptionsHasReasonableHashFunctions) {
2662 auto hash_a = ContentContextOptions::Hash{}(opts);
2663
2665 auto hash_b = ContentContextOptions::Hash{}(opts);
2666
2667 opts.has_depth_stencil_attachments = false;
2668 auto hash_c = ContentContextOptions::Hash{}(opts);
2669
2671 auto hash_d = ContentContextOptions::Hash{}(opts);
2672
2673 EXPECT_NE(hash_a, hash_b);
2674 EXPECT_NE(hash_b, hash_c);
2675 EXPECT_NE(hash_c, hash_d);
2676}
2677
2678#ifdef FML_OS_LINUX
2679TEST_P(EntityTest, FramebufferFetchVulkanBindingOffsetIsTheSame) {
2680 // Using framebuffer fetch on Vulkan requires that we maintain a subpass input
2681 // binding that we don't have a good route for configuring with the current
2682 // metadata approach. This test verifies that the binding value doesn't change
2683 // from the expected constant.
2684 // See also:
2685 // * impeller/renderer/backend/vulkan/binding_helpers_vk.cc
2686 // * impeller/entity/shaders/blending/framebuffer_blend.frag
2687 // This test only works on Linux because macOS hosts incorrectly populate the
2688 // Vulkan descriptor sets based on the MSL compiler settings.
2689
2690 bool expected_layout = false;
2692 FragmentShader::kDescriptorSetLayouts) {
2693 if (layout.binding == 64 &&
2694 layout.descriptor_type == DescriptorType::kInputAttachment) {
2695 expected_layout = true;
2696 }
2697 }
2698 EXPECT_TRUE(expected_layout);
2699}
2700#endif
2701
2702TEST_P(EntityTest, FillPathGeometryGetPositionBufferReturnsExpectedMode) {
2704 testing::MockRenderPass mock_pass(GetContext(), target);
2705
2706 auto get_result = [this, &mock_pass](const Path& path) {
2707 auto geometry = Geometry::MakeFillPath(
2708 path, /* inner rect */ Rect::MakeLTRB(0, 0, 100, 100));
2709 return geometry->GetPositionBuffer(*GetContentContext(), {}, mock_pass);
2710 };
2711
2712 // Convex path
2713 {
2715 get_result(PathBuilder{}
2716 .AddRect(Rect::MakeLTRB(0, 0, 100, 100))
2717 .SetConvexity(Convexity::kConvex)
2718 .TakePath());
2719 EXPECT_EQ(result.mode, GeometryResult::Mode::kNormal);
2720 }
2721
2722 // Concave path
2723 {
2724 Path path = PathBuilder{}
2725 .MoveTo({0, 0})
2726 .LineTo({100, 0})
2727 .LineTo({100, 100})
2728 .LineTo({50, 50})
2729 .Close()
2730 .TakePath();
2731 GeometryResult result = get_result(path);
2732 EXPECT_EQ(result.mode, GeometryResult::Mode::kNonZero);
2733 }
2734}
2735
2736TEST_P(EntityTest, FailOnValidationError) {
2737 if (GetParam() != PlaygroundBackend::kVulkan) {
2738 GTEST_SKIP() << "Validation is only fatal on Vulkan backend.";
2739 }
2740 EXPECT_DEATH(
2741 // The easiest way to trigger a validation error is to try to compile
2742 // a shader with an unsupported pixel format.
2743 GetContentContext()->GetBlendColorBurnPipeline({
2744 .color_attachment_pixel_format = PixelFormat::kUnknown,
2745 .has_depth_stencil_attachments = false,
2746 }),
2747 "");
2748}
2749
2750TEST_P(EntityTest, CanComputeGeometryForEmptyPathsWithoutCrashing) {
2751 PathBuilder builder = {};
2752 builder.AddRect(Rect::MakeLTRB(0, 0, 0, 0));
2753 Path path = builder.TakePath();
2754
2755 EXPECT_TRUE(path.GetBoundingBox()->IsEmpty());
2756
2757 auto geom = Geometry::MakeFillPath(path);
2758
2759 Entity entity;
2761 GetContentContext()->GetRenderTargetCache()->CreateOffscreen(
2762 *GetContext(), {1, 1}, 1u);
2763 testing::MockRenderPass render_pass(GetContext(), target);
2764 auto position_result =
2765 geom->GetPositionBuffer(*GetContentContext(), entity, render_pass);
2766
2767 EXPECT_EQ(position_result.vertex_buffer.vertex_count, 0u);
2768
2769 EXPECT_EQ(geom->GetResultMode(), GeometryResult::Mode::kNormal);
2770}
2771
2772TEST_P(EntityTest, CanRenderEmptyPathsWithoutCrashing) {
2773 PathBuilder builder = {};
2774 builder.AddRect(Rect::MakeLTRB(0, 0, 0, 0));
2775 Path path = builder.TakePath();
2776
2777 EXPECT_TRUE(path.GetBoundingBox()->IsEmpty());
2778
2779 auto contents = std::make_shared<SolidColorContents>();
2780 contents->SetGeometry(Geometry::MakeFillPath(path));
2781 contents->SetColor(Color::Red());
2782
2783 Entity entity;
2784 entity.SetTransform(Matrix::MakeScale(GetContentScale()));
2785 entity.SetContents(contents);
2786
2787 ASSERT_TRUE(OpenPlaygroundHere(std::move(entity)));
2788}
2789
2790} // namespace testing
2791} // namespace impeller
2792
2793// NOLINTEND(bugprone-unchecked-optional-access)
const char * options
static const int points[]
SkColor4f color
static SkPath clip(const SkPath &path, const SkHalfPlane &plane)
Definition SkPath.cpp:3824
static sk_sp< SkTextBlob > MakeFromString(const char *string, const SkFont &font, SkTextEncoding encoding=SkTextEncoding::kUTF8)
Definition SkTextBlob.h:115
static std::shared_ptr< ColorFilterContents > MakeColorMatrix(FilterInput::Ref input, const ColorMatrix &color_matrix)
static std::shared_ptr< ColorFilterContents > MakeSrgbToLinearFilter(FilterInput::Ref input)
static std::shared_ptr< ColorFilterContents > MakeLinearToSrgbFilter(FilterInput::Ref input)
static std::shared_ptr< ColorFilterContents > MakeBlend(BlendMode blend_mode, FilterInput::Vector inputs, std::optional< Color > foreground_color=std::nullopt)
the [inputs] are expected to be in the order of dst, src.
void SetColors(std::vector< Color > colors)
HostBuffer & GetTransientsBuffer() const
Retrieve the currnent host buffer for transient storage.
std::shared_ptr< Pipeline< PipelineDescriptor > > GetSolidFillPipeline(ContentContextOptions opts) const
virtual bool IsOpaque() const
Whether this Contents only emits opaque source colors from the fragment stage. This value does not ac...
Definition contents.cc:53
To do anything rendering related with Impeller, you need a context.
Definition context.h:46
virtual std::shared_ptr< Allocator > GetResourceAllocator() const =0
Returns the allocator used to create textures and buffers on the device.
void AddEntity(Entity entity)
Add an entity to the current entity pass.
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...
EntityPass * AddSubpass(std::unique_ptr< EntityPass > pass)
Appends a given pass as a subpass.
std::optional< Rect > GetElementsCoverage(std::optional< Rect > coverage_limit) const
void SetTransform(const Matrix &transform)
Set the global transform matrix for this Entity.
Definition entity.cc:62
std::optional< Rect > GetCoverage() const
Definition entity.cc:66
BlendMode GetBlendMode() const
Definition entity.cc:119
void SetContents(std::shared_ptr< Contents > contents)
Definition entity.cc:90
void SetBlendMode(BlendMode blend_mode)
Definition entity.cc:115
bool Render(const ContentContext &renderer, RenderPass &parent_pass) const
Definition entity.cc:173
const Matrix & GetTransform() const
Get the global transform matrix for this Entity.
Definition entity.cc:46
static constexpr BlendMode kLastPipelineBlendMode
Definition entity.h:23
bool ShouldRender(const std::optional< Rect > &clip_coverage) const
Definition entity.cc:82
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)
@ kNormal
Blurred inside and outside.
@ kOuter
Nothing inside, blurred outside.
@ kInner
Blurred inside, nothing outside.
@ kSolid
Solid inside, blurred outside.
static std::shared_ptr< FilterContents > MakeMorphology(FilterInput::Ref input, Radius radius_x, Radius radius_y, MorphType morph_type)
static std::shared_ptr< FilterContents > MakeBorderMaskBlur(FilterInput::Ref input, Sigma sigma_x, Sigma sigma_y, BlurStyle blur_style=BlurStyle::kNormal)
static std::shared_ptr< FilterContents > MakeYUVToRGBFilter(std::shared_ptr< Texture > y_texture, std::shared_ptr< Texture > uv_texture, YUVColorSpace yuv_color_space)
std::variant< std::shared_ptr< FilterContents >, std::shared_ptr< Contents >, std::shared_ptr< Texture >, Rect > Variant
static FilterInput::Ref Make(Variant input, bool msaa_enabled=true)
static std::shared_ptr< Geometry > MakeCover()
Definition geometry.cc:85
static std::shared_ptr< Geometry > MakeRect(const Rect &rect)
Definition geometry.cc:89
static std::shared_ptr< Geometry > MakePointField(std::vector< Point > points, Scalar radius, bool round)
Definition geometry.cc:66
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:72
static std::shared_ptr< Geometry > MakeFillPath(const Path &path, std::optional< Rect > inner_rect=std::nullopt)
Definition geometry.cc:60
BufferView EmplaceUniform(const UniformType &uniform)
Emplace uniform data onto the host buffer. Ensure that backend specific uniform alignment requirement...
Definition host_buffer.h:50
bool IsOpaque() const override
Whether this Contents only emits opaque source colors from the fragment stage. This value does not ac...
void SetTileMode(Entity::TileMode tile_mode)
void SetColors(std::vector< Color > colors)
PathBuilder & AddRect(Rect rect)
Path TakePath(FillType fill=FillType::kNonZero)
PathBuilder & LineTo(Point point, bool relative=false)
Insert a line from the current position to point.
PathBuilder & MoveTo(Point point, bool relative=false)
PathBuilder & Close()
PathBuilder & AddLine(const Point &p1, const Point &p2)
Move to point p1, then insert a line from p1 to p2.
PathBuilder & AddRoundedRect(Rect rect, RoundingRadii radii)
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.
PathBuilder & SetConvexity(Convexity value)
Paths are lightweight objects that describe a collection of linear, quadratic, or cubic segments....
Definition path.h:51
Describes the fixed function and programmable aspects of rendering and compute operations performed b...
Definition pipeline.h:49
void SetTileMode(Entity::TileMode tile_mode)
bool IsOpaque() const override
Whether this Contents only emits opaque source colors from the fragment stage. This value does not ac...
void SetColors(std::vector< Color > colors)
Render passes encode render commands directed as one specific render target into an underlying comman...
Definition render_pass.h:33
virtual const std::vector< Command > & GetCommands() const
Accessor for the current Commands.
static constexpr AttachmentConfig kDefaultColorAttachmentConfig
bool IsOpaque() const override
Whether this Contents only emits opaque source colors from the fragment stage. This value does not ac...
static std::unique_ptr< SolidColorContents > Make(const Path &path, Color color)
A geometry that is created from a stroked path object.
static Scalar RoundScaledFontSize(Scalar scale, Scalar point_size)
Definition text_frame.cc:66
static std::shared_ptr< TextureContents > MakeRect(Rect destination)
A common case factory that marks the texture contents as having a destination rectangle....
bool IsOpaque() const override
Whether this Contents only emits opaque source colors from the fragment stage. This value does not ac...
void SetTexture(std::shared_ptr< Texture > texture)
static std::shared_ptr< TypographerContext > Make()
VertexBuffer CreateVertexBuffer(HostBuffer &host_buffer) const
VertexBufferBuilder & AddVertices(std::initializer_list< VertexType_ > vertices)
~TestPassDelegate() override=default
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...
std::shared_ptr< FilterContents > WithImageFilter(const FilterInput::Variant &input, const Matrix &effect_transform) const override
std::shared_ptr< Contents > CreateContentsForSubpassTarget(std::shared_ptr< Texture > target, const Matrix &transform) override
std::vector< std::shared_ptr< FakeTexture > > textures
VULKAN_HPP_DEFAULT_DISPATCH_LOADER_DYNAMIC_STORAGE auto & d
Definition main.cc:19
sk_sp< SkImage > image
Definition examples.cpp:29
double frame
Definition examples.cpp:31
static bool b
struct MyStruct a[10]
FlKeyEvent uint64_t FlKeyResponderAsyncCallback callback
GAsyncResult * result
uint32_t * target
#define FML_DLOG(severity)
Definition logging.h:102
#define FML_CHECK(condition)
Definition logging.h:85
#define ASSERT_RECT_NEAR(a, b)
#define EXPECT_RECT_NEAR(a, b)
static void draw_rect(SkCanvas *canvas, SkImage *, const SkRect &r, sk_sp< SkImageFilter > imf)
FlTexture * texture
double y
SkFont CreateTestFontOfSize(SkScalar scalar)
auto CreatePassWithRectPath(Rect rect, std::optional< Rect > bounds_hint, ContentBoundsPromise bounds_promise=ContentBoundsPromise::kUnknown, bool collapse=false)
static std::vector< std::shared_ptr< Texture > > CreateTestYUVTextures(Context *context, YUVColorSpace yuv_color_space)
TEST_P(AiksTest, CanRenderAdvancedBlendColorFilterWithSaveLayer)
static Vector3 RGBToYUV(Vector3 rgb, YUVColorSpace yuv_color_space)
YUVColorSpace
Definition color.h:55
@ kPoint
Draws a point at each input vertex.
Point Vector2
Definition point.h:320
constexpr float kPi
Definition constants.h:26
float Scalar
Definition scalar.h:18
Point DrawPlaygroundPoint(PlaygroundPoint &point)
Definition widgets.cc:9
std::tuple< Point, Point > DrawPlaygroundLine(PlaygroundPoint &point_a, PlaygroundPoint &point_b)
Definition widgets.cc:50
constexpr RuntimeStageBackend PlaygroundBackendToRuntimeStageBackend(PlaygroundBackend backend)
Definition playground.h:35
SolidFillVertexShader VS
TPoint< Scalar > Point
Definition point.h:316
constexpr float kPiOver2
Definition constants.h:32
TSize< int64_t > ISize
Definition size.h:138
BlendMode
Definition color.h:59
ContentBoundsPromise
Definition entity_pass.h:28
@ kUnknown
The caller makes no claims related to the size of the bounds.
@ kContainsContents
The caller claims the bounds are a reasonably tight estimate of the coverage of the contents and shou...
void MoveTo(PathBuilder *builder, Scalar x, Scalar y)
std::shared_ptr< TextFrame > MakeTextFrameFromTextBlobSkia(const sk_sp< SkTextBlob > &blob)
void LineTo(PathBuilder *builder, Scalar x, Scalar y)
ContentContextOptions OptionsFromPass(const RenderPass &pass)
Definition contents.cc:20
void Close(PathBuilder *builder)
static SkColor4f transform(SkColor4f c, SkColorSpace *src, SkColorSpace *dst)
Definition p3.cpp:47
#define INSTANTIATE_PLAYGROUND_SUITE(playground)
int32_t height
int32_t width
const Scalar scale
Point offset
Scalar array[20]
Definition color.h:118
static constexpr Color BlackTransparent()
Definition color.h:262
static constexpr Color MintCream()
Definition color.h:650
Scalar alpha
Definition color.h:143
static constexpr Color DeepPink()
Definition color.h:426
static constexpr Color Black()
Definition color.h:258
static constexpr Color CornflowerBlue()
Definition color.h:334
static constexpr Color White()
Definition color.h:256
constexpr Color WithAlpha(Scalar new_alpha) const
Definition color.h:270
static constexpr Color WhiteTransparent()
Definition color.h:260
static constexpr Color Coral()
Definition color.h:330
static constexpr Color Red()
Definition color.h:264
constexpr Color Premultiply() const
Definition color.h:214
static constexpr Color Yellow()
Definition color.h:834
static constexpr Color Blue()
Definition color.h:268
static constexpr Color Green()
Definition color.h:266
@ kNormal
The geometry has no overlapping triangles.
A 4x4 matrix using column-major storage.
Definition matrix.h:37
static constexpr Matrix MakeTranslation(const Vector3 &t)
Definition matrix.h:95
constexpr bool IsIdentity() const
Definition matrix.h:377
static Matrix MakeRotationY(Radians r)
Definition matrix.h:198
static constexpr Matrix MakeSkew(Scalar sx, Scalar sy)
Definition matrix.h:117
static Matrix MakeRotationZ(Radians r)
Definition matrix.h:213
static constexpr Matrix MakeScale(const Vector3 &s)
Definition matrix.h:104
static Matrix MakeRotationX(Radians r)
Definition matrix.h:183
For convolution filters, the "radius" is the size of the convolution kernel to use on the local space...
Definition sigma.h:48
In filters that use Gaussian distributions, "sigma" is a size of one standard deviation in terms of t...
Definition sigma.h:32
constexpr TRect TransformBounds(const Matrix &transform) const
Creates a new bounding box that contains this transformed rectangle.
Definition rect.h:440
static constexpr TRect MakeXYWH(Type x, Type y, Type width, Type height)
Definition rect.h:136
static constexpr TRect MakeSize(const TSize< U > &size)
Definition rect.h:146
static constexpr TRect MakeLTRB(Type left, Type top, Type right, Type bottom)
Definition rect.h:129
static constexpr TSize MakeWH(Type width, Type height)
Definition size.h:34
A lightweight object that describes the attributes of a texture that can then used an allocator to cr...
#define ERROR(message)
#define EXPECT_TRUE(handle)
Definition unit_test.h:685