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