| Flutter Impeller |

entity_unittests.cc
Go to the documentation of this file.
1 // Copyright 2013 The Flutter Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include <algorithm>
6 #include <cstring>
7 #include <memory>
8 #include <optional>
9 #include <utility>
10 #include <vector>
11
12 #include "flutter/display_list/testing/dl_test_snippets.h"
13 #include "fml/logging.h"
14 #include "gtest/gtest.h"
15 #include "impeller/core/device_buffer.h"
16 #include "impeller/core/formats.h"
17 #include "impeller/core/texture_descriptor.h"
18 #include "impeller/entity/contents/atlas_contents.h"
19 #include "impeller/entity/contents/clip_contents.h"
20 #include "impeller/entity/contents/conical_gradient_contents.h"
21 #include "impeller/entity/contents/content_context.h"
22 #include "impeller/entity/contents/contents.h"
23 #include "impeller/entity/contents/filters/color_filter_contents.h"
24 #include "impeller/entity/contents/filters/filter_contents.h"
25 #include "impeller/entity/contents/filters/gaussian_blur_filter_contents.h"
26 #include "impeller/entity/contents/filters/inputs/filter_input.h"
27 #include "impeller/entity/contents/linear_gradient_contents.h"
28 #include "impeller/entity/contents/radial_gradient_contents.h"
29 #include "impeller/entity/contents/runtime_effect_contents.h"
30 #include "impeller/entity/contents/solid_color_contents.h"
31 #include "impeller/entity/contents/solid_rrect_blur_contents.h"
32 #include "impeller/entity/contents/text_contents.h"
33 #include "impeller/entity/contents/texture_contents.h"
34 #include "impeller/entity/contents/tiled_texture_contents.h"
35 #include "impeller/entity/entity.h"
36 #include "impeller/entity/entity_pass.h"
37 #include "impeller/entity/entity_pass_delegate.h"
38 #include "impeller/entity/entity_playground.h"
39 #include "impeller/entity/geometry/geometry.h"
40 #include "impeller/entity/geometry/point_field_geometry.h"
41 #include "impeller/entity/geometry/stroke_path_geometry.h"
42 #include "impeller/entity/render_target_cache.h"
43 #include "impeller/geometry/color.h"
44 #include "impeller/geometry/geometry_asserts.h"
45 #include "impeller/geometry/path_builder.h"
46 #include "impeller/geometry/point.h"
47 #include "impeller/geometry/sigma.h"
48 #include "impeller/geometry/vector.h"
49 #include "impeller/playground/playground.h"
50 #include "impeller/playground/widgets.h"
51 #include "impeller/renderer/command.h"
52 #include "impeller/renderer/pipeline_descriptor.h"
53 #include "impeller/renderer/render_pass.h"
54 #include "impeller/renderer/render_target.h"
55 #include "impeller/renderer/testing/mocks.h"
56 #include "impeller/renderer/vertex_buffer_builder.h"
57 #include "impeller/typographer/backends/skia/text_frame_skia.h"
58 #include "impeller/typographer/backends/skia/typographer_context_skia.h"
59 #include "third_party/imgui/imgui.h"
60 #include "third_party/skia/include/core/SkTextBlob.h"
61
62 // TODO(zanderso): https://github.com/flutter/flutter/issues/127701
63 // NOLINTBEGIN(bugprone-unchecked-optional-access)
64
65 namespace impeller {
66 namespace testing {
67
68 using EntityTest = EntityPlayground;
69 INSTANTIATE_PLAYGROUND_SUITE(EntityTest);
70
71 TEST_P(EntityTest, CanCreateEntity) {
72 Entity entity;
73 ASSERT_TRUE(entity.GetTransform().IsIdentity());
74 }
75
76 class TestPassDelegate final : public EntityPassDelegate {
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
110 auto CreatePassWithRectPath(
111 Rect rect,
112 std::optional<Rect> bounds_hint,
113 ContentBoundsPromise bounds_promise = ContentBoundsPromise::kUnknown,
114 bool collapse = false) {
115 auto subpass = std::make_unique<EntityPass>();
116 Entity entity;
117 entity.SetContents(SolidColorContents::Make(
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
125 TEST_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
152 TEST_P(EntityTest, EntityPassTrustsSnugSubpassBoundsLimit) {
153 EntityPass pass;
154
155 auto subpass0 = //
156 CreatePassWithRectPath(Rect::MakeLTRB(10, 10, 90, 90),
157 Rect::MakeLTRB(5, 5, 95, 95),
158 ContentBoundsPromise::kContainsContents);
159 auto subpass1 = //
160 CreatePassWithRectPath(Rect::MakeLTRB(500, 500, 1000, 1000),
161 Rect::MakeLTRB(495, 495, 1005, 1005),
162 ContentBoundsPromise::kContainsContents);
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
188 TEST_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),
195 ContentBoundsPromise::kUnknown, true);
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
210 TEST_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
240 TEST_P(EntityTest, FilterCoverageRespectsCropRect) {
241 auto image = CreateTextureForFixture("boston.jpg");
242 auto filter = ColorFilterContents::MakeBlend(BlendMode::kSoftLight,
243 FilterInput::Make({image}));
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
265 TEST_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
277 TEST_P(EntityTest, CanDrawRRect) {
278 auto contents = std::make_shared<SolidColorContents>();
279 auto path = PathBuilder{}
280 .SetConvexity(Convexity::kConvex)
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
293 TEST_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
301 TEST_P(EntityTest, ThreeStrokesInOnePath) {
302 Path path = PathBuilder{}
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
320 TEST_P(EntityTest, StrokeWithTextureContents) {
321 auto bridge = CreateTextureForFixture("bay_bridge.jpg");
322 Path path = PathBuilder{}
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
341 TEST_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);
360 Path path = PathBuilder{}
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
384 TEST_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(),
445 Cap::kButt, Join::kBevel);
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(),
458 Cap::kSquare, Join::kBevel);
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(),
471 Cap::kRound, Join::kBevel);
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(),
488 Cap::kButt, Join::kBevel);
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(),
502 Cap::kButt, Join::kMiter);
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(),
516 Cap::kButt, Join::kRound);
517 }
518
519 return true;
520 };
521 ASSERT_TRUE(OpenPlaygroundHere(callback));
522 }
523
524 TEST_P(EntityTest, CubicCurveTest) {
525 // Compare with https://fiddle.skia.org/c/b3625f26122c9de7afe7794fcf25ead3
526 Path path =
527 PathBuilder{}
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()));
542 entity.SetContents(SolidColorContents::Make(path, Color::Red()));
543 ASSERT_TRUE(OpenPlaygroundHere(std::move(entity)));
544 }
545
546 TEST_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())
577 .MakeTranslation(
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);
586 entity.SetContents(SolidColorContents::Make(path, Color::Red()));
587 return entity.Render(context, pass);
588 };
589 ASSERT_TRUE(OpenPlaygroundHere(callback));
590 }
591
592 TEST_P(EntityTest, CubicCurveAndOverlapTest) {
593 // Compare with https://fiddle.skia.org/c/7a05a3e186c65a8dfb732f68020aae06
594 Path path =
595 PathBuilder{}
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()));
816 entity.SetContents(SolidColorContents::Make(path, Color::Red()));
817 ASSERT_TRUE(OpenPlaygroundHere(std::move(entity)));
818 }
819
820 TEST_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
842 TEST_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
862 TEST_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.
874 static_assert(Entity::kLastPipelineBlendMode == BlendMode::kModulate);
875 switch (b) {
876 case BlendMode::kClear:
877 blend_mode_names.push_back("Clear");
878 blend_mode_values.push_back(BlendMode::kClear);
879 case BlendMode::kSource:
880 blend_mode_names.push_back("Source");
881 blend_mode_values.push_back(BlendMode::kSource);
882 case BlendMode::kDestination:
883 blend_mode_names.push_back("Destination");
884 blend_mode_values.push_back(BlendMode::kDestination);
885 case BlendMode::kSourceOver:
886 blend_mode_names.push_back("SourceOver");
887 blend_mode_values.push_back(BlendMode::kSourceOver);
888 case BlendMode::kDestinationOver:
889 blend_mode_names.push_back("DestinationOver");
890 blend_mode_values.push_back(BlendMode::kDestinationOver);
891 case BlendMode::kSourceIn:
892 blend_mode_names.push_back("SourceIn");
893 blend_mode_values.push_back(BlendMode::kSourceIn);
894 case BlendMode::kDestinationIn:
895 blend_mode_names.push_back("DestinationIn");
896 blend_mode_values.push_back(BlendMode::kDestinationIn);
897 case BlendMode::kSourceOut:
898 blend_mode_names.push_back("SourceOut");
899 blend_mode_values.push_back(BlendMode::kSourceOut);
900 case BlendMode::kDestinationOut:
901 blend_mode_names.push_back("DestinationOut");
902 blend_mode_values.push_back(BlendMode::kDestinationOut);
903 case BlendMode::kSourceATop:
904 blend_mode_names.push_back("SourceATop");
905 blend_mode_values.push_back(BlendMode::kSourceATop);
906 case BlendMode::kDestinationATop:
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);
915 case BlendMode::kModulate:
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 {
925 using VS = SolidFillPipeline::VertexShader;
926 using FS = SolidFillPipeline::FragmentShader;
927
928 VertexBufferBuilder<VS::PerVertexData> vtx_builder;
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),
983 Color(), BlendMode::kClear);
984 result = result && draw_rect(Rect::MakeLTRB(a.x, a.y, b.x, b.y), color1,
985 BlendMode::kSourceOver);
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
993 TEST_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}));
1021 entity.SetContents(SolidColorContents::Make(path, Color::Red()));
1022 return entity.Render(context, pass);
1023 };
1024 ASSERT_TRUE(OpenPlaygroundHere(callback));
1025 }
1026
1027 TEST_P(EntityTest, Filters) {
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(
1042 BlendMode::kScreen,
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
1055 TEST_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[] = {
1067 FilterContents::BlurStyle::kNormal, FilterContents::BlurStyle::kSolid,
1068 FilterContents::BlurStyle::kOuter, FilterContents::BlurStyle::kInner};
1069 const Entity::TileMode tile_modes[] = {
1070 Entity::TileMode::kClamp, Entity::TileMode::kRepeat,
1071 Entity::TileMode::kMirror, Entity::TileMode::kDecal};
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:
1173 blur = FilterContents::MakeGaussianBlur(
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()) *
1185 Matrix::MakeTranslation(Vector3(offset[0], offset[1])) *
1186 Matrix::MakeRotationZ(Radians(rotation)) *
1187 Matrix::MakeScale(Vector2(scale[0], scale[1])) *
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;
1202 cover_entity.SetContents(SolidColorContents::Make(
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()) {
1213 bounds_entity.SetContents(SolidColorContents::Make(
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
1228 TEST_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[] = {
1235 FilterContents::MorphType::kDilate, FilterContents::MorphType::kErode};
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()) *
1293 Matrix::MakeTranslation(Vector3(offset[0], offset[1])) *
1294 Matrix::MakeRotationZ(Radians(rotation)) *
1295 Matrix::MakeScale(Vector2(scale[0], scale[1])) *
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;
1308 cover_entity.SetContents(SolidColorContents::Make(
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;
1316 bounds_entity.SetContents(SolidColorContents::Make(
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
1328 TEST_P(EntityTest, SetBlendMode) {
1329 Entity entity;
1330 ASSERT_EQ(entity.GetBlendMode(), BlendMode::kSourceOver);
1331 entity.SetBlendMode(BlendMode::kClear);
1332 ASSERT_EQ(entity.GetBlendMode(), BlendMode::kClear);
1333 }
1334
1335 TEST_P(EntityTest, ContentsGetBoundsForEmptyPathReturnsNullopt) {
1336 Entity entity;
1337 entity.SetContents(std::make_shared<SolidColorContents>());
1338 ASSERT_FALSE(entity.GetCoverage().has_value());
1339 }
1340
1341 TEST_P(EntityTest, SolidStrokeCoverageIsCorrect) {
1342 {
1343 auto geometry = Geometry::MakeStrokePath(
1344 PathBuilder{}.AddLine({0, 0}, {10, 10}).TakePath(), 4.0, 4.0,
1345 Cap::kButt, Join::kBevel);
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,
1362 Cap::kSquare, Join::kBevel);
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,
1380 Cap::kSquare, Join::kMiter);
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
1394 TEST_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
1421 TEST_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;
1443 entity.SetTransform(Matrix::MakeTranslation(Vector2(4, 5)));
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
1464 TEST_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
1496 TEST_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
1510 TEST_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
1534 TEST_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));
1576 ASSERT_EQ(result.type, Contents::ClipCoverage::Type::kAppend);
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));
1590 ASSERT_EQ(result.type, Contents::ClipCoverage::Type::kAppend);
1591 }
1592 }
1593
1594 TEST_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
1647 TEST_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.
1655 ColorMatrix matrix = {
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 =
1663 ColorFilterContents::MakeColorMatrix(FilterInput::Make(fill), matrix);
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
1676 TEST_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.
1713 auto filter = ColorFilterContents::MakeColorMatrix(
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()) *
1720 Matrix::MakeTranslation(Vector3(offset[0], offset[1])) *
1721 Matrix::MakeRotationZ(Radians(rotation)) *
1722 Matrix::MakeScale(Vector2(scale[0], scale[1])) *
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
1734 TEST_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 =
1742 ColorFilterContents::MakeLinearToSrgbFilter(FilterInput::Make(fill));
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
1755 TEST_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 =
1761 ColorFilterContents::MakeLinearToSrgbFilter(FilterInput::Make(image));
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}));
1769 auto unfiltered = FilterContents::MakeGaussianBlur(FilterInput::Make(image),
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
1786 TEST_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 =
1794 ColorFilterContents::MakeSrgbToLinearFilter(FilterInput::Make(fill));
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
1807 TEST_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 =
1813 ColorFilterContents::MakeSrgbToLinearFilter(FilterInput::Make(image));
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}));
1821 auto unfiltered = FilterContents::MakeGaussianBlur(FilterInput::Make(image),
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
1838 static Vector3 RGBToYUV(Vector3 rgb, YUVColorSpace yuv_color_space) {
1839 Vector3 yuv;
1840 switch (yuv_color_space) {
1841 case YUVColorSpace::kBT601FullRange:
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;
1846 case YUVColorSpace::kBT601LimitedRange:
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
1855 static 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;
1885 y_texture_descriptor.storage_mode = impeller::StorageMode::kHostVisible;
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
1916 TEST_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,
1924 YUVColorSpace::kBT601LimitedRange};
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
1949 TEST_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 {
1996 RenderTarget target =
1997 content_context->GetRenderTargetCache()->CreateOffscreen(
1998 *content_context->GetContext(), {1, 1}, 1u);
1999
2000 testing::MockRenderPass mock_pass(GetContext(), target);
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
2016 TEST_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.
2046 RenderTarget target =
2047 GetContentContext()->GetRenderTargetCache()->CreateOffscreenMSAA(
2048 *GetContext(), {GetWindowSize().width, GetWindowSize().height}, 1,
2049 "RuntimeEffect Texture");
2050 testing::MockRenderPass pass(GetContext(), target);
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
2063 TEST_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
2077 TEST_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
2112 testing::MockRenderPass pass(GetContext(), target);
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
2125 TEST_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
2175 TEST_P(EntityTest, ColorFilterWithForegroundColorAdvancedBlend) {
2176 auto image = CreateTextureForFixture("boston.jpg");
2177 auto filter = ColorFilterContents::MakeBlend(
2178 BlendMode::kColorBurn, FilterInput::Make({image}), Color::Red());
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
2191 TEST_P(EntityTest, ColorFilterWithForegroundColorClearBlend) {
2192 auto image = CreateTextureForFixture("boston.jpg");
2193 auto filter = ColorFilterContents::MakeBlend(
2194 BlendMode::kClear, FilterInput::Make({image}), Color::Red());
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
2207 TEST_P(EntityTest, ColorFilterWithForegroundColorSrcBlend) {
2208 auto image = CreateTextureForFixture("boston.jpg");
2209 auto filter = ColorFilterContents::MakeBlend(
2210 BlendMode::kSource, FilterInput::Make({image}), Color::Red());
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
2223 TEST_P(EntityTest, ColorFilterWithForegroundColorDstBlend) {
2224 auto image = CreateTextureForFixture("boston.jpg");
2225 auto filter = ColorFilterContents::MakeBlend(
2226 BlendMode::kDestination, FilterInput::Make({image}), Color::Red());
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
2239 TEST_P(EntityTest, ColorFilterWithForegroundColorSrcInBlend) {
2240 auto image = CreateTextureForFixture("boston.jpg");
2241 auto filter = ColorFilterContents::MakeBlend(
2242 BlendMode::kSourceIn, FilterInput::Make({image}), Color::Red());
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
2255 TEST_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}) *
2265 Matrix::MakeRotationZ(Radians(kPiOver2));
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
2275 TEST_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
2283 TEST_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
2291 TEST_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()});
2298 contents.SetTileMode(Entity::TileMode::kDecal);
2299 ASSERT_FALSE(contents.IsOpaque());
2300 }
2301
2302 TEST_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()});
2309 contents.SetTileMode(Entity::TileMode::kDecal);
2310 ASSERT_FALSE(contents.IsOpaque());
2311 }
2312
2313 TEST_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()});
2320 contents.SetTileMode(Entity::TileMode::kDecal);
2321 ASSERT_FALSE(contents.IsOpaque());
2322 }
2323
2324 TEST_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
2334 TEST_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
2342 TEST_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
2362 TEST_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
2370 TEST_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)));
2382 entity.SetBlendMode(BlendMode::kColorBurn);
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{
2392 .storage_mode = StorageMode::kDevicePrivate,
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
2420 TEST_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() ==
2436 PixelFormat::kA8UNormInt;
2437
2438 std::vector<Scalar> expected_constants = {static_cast<Scalar>(use_a8)};
2439 EXPECT_EQ(default_gyph->GetDescriptor().GetSpecializationConstants(),
2440 expected_constants);
2441 }
2442
2443 TEST_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.
2459 TEST_P(EntityTest, ContentContextOptionsHasReasonableHashFunctions) {
2460 ContentContextOptions opts;
2461 auto hash_a = ContentContextOptions::Hash{}(opts);
2462
2463 opts.blend_mode = BlendMode::kColorBurn;
2464 auto hash_b = ContentContextOptions::Hash{}(opts);
2465
2466 opts.has_depth_stencil_attachments = false;
2467 auto hash_c = ContentContextOptions::Hash{}(opts);
2468
2469 opts.primitive_type = PrimitiveType::kPoint;
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
2478 TEST_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;
2490 for (const DescriptorSetLayout& layout : FramebufferBlendColorBurnPipeline::
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
2501 TEST_P(EntityTest, FillPathGeometryGetPositionBufferReturnsExpectedMode) {
2502 RenderTarget target;
2503 testing::MockRenderPass mock_pass(GetContext(), target);
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 {
2513 GeometryResult result =
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 {
2523 Path path = PathBuilder{}
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
2535 TEST_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
2549 TEST_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;
2559 RenderTarget target =
2560 GetContentContext()->GetRenderTargetCache()->CreateOffscreen(
2561 *GetContext(), {1, 1}, 1u);
2562 testing::MockRenderPass render_pass(GetContext(), target);
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
2571 TEST_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)
text_contents.h
impeller::DeviceBuffer::AsBufferView
static BufferView AsBufferView(std::shared_ptr< DeviceBuffer > buffer)
Create a buffer view of this entire buffer.
Definition: device_buffer.cc:18
impeller::Matrix::MakeSkew
static constexpr Matrix MakeSkew(Scalar sx, Scalar sy)
Definition: matrix.h:117
impeller::Pipeline< PipelineDescriptor >
impeller::ISize
ISize64 ISize
Definition: size.h:140
impeller::SolidColorContents::Make
static std::unique_ptr< SolidColorContents > Make(const Path &path, Color color)
Definition: solid_color_contents.cc:73
impeller::RenderPipelineHandle::FragmentShader
FragmentShader_ FragmentShader
Definition: pipeline.h:107
impeller::GeometryResult::Mode::kNormal
@ kNormal
The geometry has no overlapping triangles.
impeller::Color::Blue
static constexpr Color Blue()
Definition: color.h:278
impeller::Entity::ClipOperation::kIntersect
@ kIntersect
impeller::TiledTextureContents::IsOpaque
bool IsOpaque() const override
Whether this Contents only emits opaque source colors from the fragment stage. This value does not ac...
Definition: tiled_texture_contents.cc:99
impeller::Entity::TileMode::kClamp
@ kClamp
impeller::OptionsFromPass
ContentContextOptions OptionsFromPass(const RenderPass &pass)
Definition: contents.cc:19
impeller::Color::DeepPink
static constexpr Color DeepPink()
Definition: color.h:436
impeller::Matrix::MakeRotationX
static Matrix MakeRotationX(Radians r)
Definition: matrix.h:183
impeller::testing::TestPassDelegate
Definition: entity_unittests.cc:76
impeller::Cap::kRound
@ kRound
impeller::PlaygroundBackend::kVulkan
@ kVulkan
impeller::Entity::kLastPipelineBlendMode
static constexpr BlendMode kLastPipelineBlendMode
Definition: entity.h:22
impeller::SolidColorContents::IsOpaque
bool IsOpaque() const override
Whether this Contents only emits opaque source colors from the fragment stage. This value does not ac...
Definition: solid_color_contents.cc:31
impeller::DescriptorSetLayout
Definition: shader_types.h:162
impeller::Cap::kSquare
@ kSquare
impeller::BlendMode::kDestinationATop
@ kDestinationATop
contents.h
point.h
impeller::FilterContents::MorphType::kErode
@ kErode
entity_pass_delegate.h
impeller::Geometry::MakePointField
static std::shared_ptr< Geometry > MakePointField(std::vector< Point > points, Scalar radius, bool round)
Definition: geometry.cc:66
impeller::TPoint::y
Type y
Definition: point.h:31
impeller::Entity::ClipOperation::kDifference
@ kDifference
impeller::Scalar
float Scalar
Definition: scalar.h:18
impeller::EntityPass::GetSubpassCoverage
std::optional< Rect > GetSubpassCoverage(const EntityPass &subpass, std::optional< Rect > coverage_limit) const
Computes the coverage of a given subpass. This is used to determine the texture size of a given subpa...
Definition: entity_pass.cc:231
impeller::Entity::SetBlendMode
void SetBlendMode(BlendMode blend_mode)
Definition: entity.cc:115
impeller::RenderTarget::kDefaultColorAttachmentConfig
static constexpr AttachmentConfig kDefaultColorAttachmentConfig
Definition: render_target.h:55
texture_contents.h
impeller::Color::Red
static constexpr Color Red()
Definition: color.h:274
impeller::Entity::GetTransform
const Matrix & GetTransform() const
Get the global transform matrix for this Entity.
Definition: entity.cc:46
geometry_asserts.h
impeller::PixelFormat::kA8UNormInt
@ kA8UNormInt
impeller::Geometry::MakeStrokePath
static std::shared_ptr< Geometry > MakeStrokePath(const Path &path, Scalar stroke_width=0.0, Scalar miter_limit=4.0, Cap stroke_cap=Cap::kButt, Join stroke_join=Join::kMiter)
Definition: geometry.cc:72
stroke_path_geometry.h
entity.h
impeller::TRect< Scalar >::MakeXYWH
constexpr static TRect MakeXYWH(Type x, Type y, Type width, Type height)
Definition: rect.h:136
impeller::PixelFormat::kR8UNormInt
@ kR8UNormInt
impeller::BlendMode
BlendMode
Definition: color.h:59
impeller::FilterContents::BlurStyle
BlurStyle
Definition: filter_contents.h:26
solid_color_contents.h
impeller::Color
Definition: color.h:124
device_buffer.h
impeller::FilterInput::Make
static FilterInput::Ref Make(Variant input, bool msaa_enabled=true)
Definition: filter_input.cc:19
impeller::ConicalGradientContents::SetColors
void SetColors(std::vector< Color > colors)
Definition: conical_gradient_contents.cc:30
data
std::shared_ptr< const fml::Mapping > data
Definition: texture_gles.cc:63
point_field_geometry.h
impeller::TextureDescriptor::format
PixelFormat format
Definition: texture_descriptor.h:41
impeller::Entity::TileMode::kDecal
@ kDecal
impeller::BlendMode::kSource
@ kSource
impeller::PixelFormat::kR8G8B8A8UNormInt
@ kR8G8B8A8UNormInt
impeller::EntityPass::AddSubpass
EntityPass * AddSubpass(std::unique_ptr< EntityPass > pass)
Appends a given pass as a subpass.
Definition: entity_pass.cc:267
impeller::BlendMode::kDestination
@ kDestination
texture_descriptor.h
impeller::testing::TestPassDelegate::TestPassDelegate
TestPassDelegate(bool collapse=false)
Definition: entity_unittests.cc:78
formats.h
impeller::PathBuilder
Definition: path_builder.h:14
impeller::ContentContextOptions::Hash
Definition: content_context.h:318
tiled_texture_contents.h
impeller::Vector2
Point Vector2
Definition: point.h:326
impeller::Matrix::MakeRotationY
static Matrix MakeRotationY(Radians r)
Definition: matrix.h:198
impeller::Path::GetBoundingBox
std::optional< Rect > GetBoundingBox() const
Definition: path.cc:405
impeller::kPi
constexpr float kPi
Definition: constants.h:26
impeller::Color::alpha
Scalar alpha
Definition: color.h:143
impeller::ContentContextOptions::has_depth_stencil_attachments
bool has_depth_stencil_attachments
Definition: content_context.h:313
impeller::BlendMode::kDestinationOver
@ kDestinationOver
impeller::BlendMode::kPlus
@ kPlus
impeller::StoreAction::kDontCare
@ kDontCare
impeller::Color::MintCream
static constexpr Color MintCream()
Definition: color.h:660
playground.h
impeller::ContentContextOptions::blend_mode
BlendMode blend_mode
Definition: content_context.h:308
impeller::Color::CornflowerBlue
static constexpr Color CornflowerBlue()
Definition: color.h:344
impeller::ContentBoundsPromise
ContentBoundsPromise
Definition: entity_pass.h:28
impeller::ContentBoundsPromise::kUnknown
@ kUnknown
The caller makes no claims related to the size of the bounds.
impeller::VertexBufferBuilder::AddVertices
VertexBufferBuilder & AddVertices(std::initializer_list< VertexType_ > vertices)
Definition: vertex_buffer_builder.h:67
padding
Vector2 padding
The halo padding in source space.
Definition: gaussian_blur_filter_contents.cc:91
impeller::PathBuilder::AddRoundedRect
PathBuilder & AddRoundedRect(Rect rect, RoundingRadii radii)
Definition: path_builder.cc:150
gaussian_blur_filter_contents.h
impeller::FilterContents::BlurStyle::kNormal
@ kNormal
Blurred inside and outside.
impeller::PathBuilder::SetConvexity
PathBuilder & SetConvexity(Convexity value)
Definition: path_builder.cc:85
impeller::StorageMode::kHostVisible
@ kHostVisible
impeller::EntityPass::GetElementsCoverage
std::optional< Rect > GetElementsCoverage(std::optional< Rect > coverage_limit) const
Definition: entity_pass.cc:154
impeller::Cap::kButt
@ kButt
impeller::RenderPipelineHandle
Definition: pipeline.h:99
impeller::TextFrame::RoundScaledFontSize
static Scalar RoundScaledFontSize(Scalar scale, Scalar point_size)
Definition: text_frame.cc:40
impeller::Entity::TileMode::kRepeat
@ kRepeat
impeller::Matrix::MakeTranslation
static constexpr Matrix MakeTranslation(const Vector3 &t)
Definition: matrix.h:95
impeller::EntityPassDelegate
Definition: entity_pass_delegate.h:19
impeller::RadialGradientContents::IsOpaque
bool IsOpaque() const override
Whether this Contents only emits opaque source colors from the fragment stage. This value does not ac...
Definition: radial_gradient_contents.cc:46
impeller::PlaygroundBackendToRuntimeStageBackend
constexpr RuntimeStageBackend PlaygroundBackendToRuntimeStageBackend(PlaygroundBackend backend)
Definition: playground.h:33
impeller::Vector3::x
Scalar x
Definition: vector.h:23
offset
SeparatedVector2 offset
Definition: stroke_path_geometry.cc:311
impeller::MoveTo
void MoveTo(PathBuilder *builder, Scalar x, Scalar y)
Definition: tessellator.cc:20
EXPECT_RECT_NEAR
#define EXPECT_RECT_NEAR(a, b)
Definition: geometry_asserts.h:203
impeller::BlendMode::kModulate
@ kModulate
impeller::PathBuilder::AddRect
PathBuilder & AddRect(Rect rect)
Definition: path_builder.cc:117
impeller::Join::kMiter
@ kMiter
runtime_effect_contents.h
impeller::kPiOver2
constexpr float kPiOver2
Definition: constants.h:32
impeller::BlendMode::kSourceOut
@ kSourceOut
typographer_context_skia.h
impeller::testing::TEST_P
TEST_P(AiksTest, CanRenderAdvancedBlendColorFilterWithSaveLayer)
Definition: aiks_blend_unittests.cc:21
impeller::RenderTarget::AttachmentConfig
Definition: render_target.h:40
ASSERT_RECT_NEAR
#define ASSERT_RECT_NEAR(a, b)
Definition: geometry_asserts.h:191
impeller::Entity::TileMode::kMirror
@ kMirror
impeller::Entity::SetContents
void SetContents(std::shared_ptr< Contents > contents)
Definition: entity.cc:90
path_builder.h
impeller::LinearGradientContents::SetColors
void SetColors(std::vector< Color > colors)
Definition: linear_gradient_contents.cc:27
impeller::VS
SolidFillVertexShader VS
Definition: stroke_path_geometry.cc:16
impeller::testing::INSTANTIATE_PLAYGROUND_SUITE
INSTANTIATE_PLAYGROUND_SUITE(AiksTest)
impeller::StrokePathGeometry
A geometry that is created from a stroked path object.
Definition: stroke_path_geometry.h:13
impeller::PrimitiveType::kTriangle
@ kTriangle
impeller::LinearGradientContents::SetTileMode
void SetTileMode(Entity::TileMode tile_mode)
Definition: linear_gradient_contents.cc:43
impeller::Entity
Definition: entity.h:20
command.h
impeller::ConicalGradientContents
Definition: conical_gradient_contents.h:17
text_frame_skia.h
impeller::Geometry::MakeFillPath
static std::shared_ptr< Geometry > MakeFillPath(const Path &path, std::optional< Rect > inner_rect=std::nullopt)
Definition: geometry.cc:60
impeller::TSize< Scalar >
impeller::LoadAction::kClear
@ kClear
impeller::Entity::ShouldRender
bool ShouldRender(const std::optional< Rect > &clip_coverage) const
Definition: entity.cc:82
impeller::BlendMode::kSoftLight
@ kSoftLight
impeller::Point
TPoint< Scalar > Point
Definition: point.h:322
filter_contents.h
render_pass.h
impeller::BlendMode::kColorBurn
@ kColorBurn
impeller::testing::RGBToYUV
static Vector3 RGBToYUV(Vector3 rgb, YUVColorSpace yuv_color_space)
Definition: entity_unittests.cc:1838
impeller::Entity::Render
bool Render(const ContentContext &renderer, RenderPass &parent_pass) const
Definition: entity.cc:173
impeller::testing::TestPassDelegate::WithImageFilter
std::shared_ptr< FilterContents > WithImageFilter(const FilterInput::Variant &input, const Matrix &effect_transform) const override
Definition: entity_unittests.cc:99
sigma.h
impeller::Radius
For convolution filters, the "radius" is the size of the convolution kernel to use on the local space...
Definition: sigma.h:48
impeller::FilterContents::BlurStyle::kSolid
@ kSolid
Solid inside, blurred outside.
impeller::Path
Paths are lightweight objects that describe a collection of linear, quadratic, or cubic segments....
Definition: path.h:52
widgets.h
impeller::PrimitiveType::kPoint
@ kPoint
Draws a point at each input vertex.
transform
Matrix transform
Definition: gaussian_blur_filter_contents.cc:231
impeller::PixelFormat::kR8G8UNormInt
@ kR8G8UNormInt
conical_gradient_contents.h
impeller::BlendMode::kClear
@ kClear
impeller::EntityPass
Definition: entity_pass.h:43
impeller::VertexBufferBuilder
Definition: vertex_buffer_builder.h:21
impeller::YUVColorSpace::kBT601FullRange
@ kBT601FullRange
impeller::testing::TestPassDelegate::~TestPassDelegate
~TestPassDelegate() override=default
impeller::StorageMode::kDevicePrivate
@ kDevicePrivate
impeller::Color::WithAlpha
constexpr Color WithAlpha(Scalar new_alpha) const
Definition: color.h:280
impeller::SolidColorContents
Definition: solid_color_contents.h:17
impeller::testing::EntityTest
EntityPlayground EntityTest
Definition: clip_contents_unittests.cc:21
impeller::ColorFilterContents::MakeSrgbToLinearFilter
static std::shared_ptr< ColorFilterContents > MakeSrgbToLinearFilter(FilterInput::Ref input)
Definition: color_filter_contents.cc:75
impeller::ColorMatrix::array
Scalar array[20]
Definition: color.h:118
geometry.h
impeller::RenderPipelineHandle::VertexShader
VertexShader_ VertexShader
Definition: pipeline.h:106
impeller::testing::CreatePassWithRectPath
auto CreatePassWithRectPath(Rect rect, std::optional< Rect > bounds_hint, ContentBoundsPromise bounds_promise=ContentBoundsPromise::kUnknown, bool collapse=false)
Definition: entity_unittests.cc:110
impeller::PathBuilder::LineTo
PathBuilder & LineTo(Point point, bool relative=false)
Insert a line from the current position to point.
Definition: path_builder.cc:52
impeller::ContentContext::GetSolidFillPipeline
std::shared_ptr< Pipeline< PipelineDescriptor > > GetSolidFillPipeline(ContentContextOptions opts) const
Definition: content_context.h:437
impeller::GeometryResult
Definition: geometry.h:19
impeller::PathBuilder::AddCubicCurve
PathBuilder & AddCubicCurve(Point p1, Point cp1, Point cp2, Point p2)
Move to point p1, then insert a cubic curve from p1 to p2 with control points cp1 and cp2.
Definition: path_builder.cc:108
impeller::Color::White
static constexpr Color White()
Definition: color.h:266
impeller::Sigma
In filters that use Gaussian distributions, "sigma" is a size of one standard deviation in terms of t...
Definition: sigma.h:32
impeller::Vector3::z
Scalar z
Definition: vector.h:25
impeller::Radians
Definition: scalar.h:38
impeller::RenderTarget::AttachmentConfig::storage_mode
StorageMode storage_mode
Definition: render_target.h:41
impeller::PathBuilder::AddLine
PathBuilder & AddLine(const Point &p1, const Point &p2)
Move to point p1, then insert a line from p1 to p2.
Definition: path_builder.cc:424
clip_contents.h
impeller::FilterInput::Variant
std::variant< std::shared_ptr< FilterContents >, std::shared_ptr< Contents >, std::shared_ptr< Texture >, Rect > Variant
Definition: filter_input.h:37
impeller::RenderTarget
Definition: render_target.h:38
impeller::testing::CreateTestYUVTextures
static std::vector< std::shared_ptr< Texture > > CreateTestYUVTextures(Context *context, YUVColorSpace yuv_color_space)
Definition: entity_unittests.cc:1855
entity_pass.h
impeller::Color::Green
static constexpr Color Green()
Definition: color.h:276
impeller::PathBuilder::TakePath
Path TakePath(FillType fill=FillType::kNonZero)
Definition: path_builder.cc:22
filter_input.h
impeller::Context::CreateCommandBuffer
virtual std::shared_ptr< CommandBuffer > CreateCommandBuffer() const =0
Create a new command buffer. Command buffers can be used to encode graphics, blit,...
impeller::Context::GetCommandQueue
virtual std::shared_ptr< CommandQueue > GetCommandQueue() const =0
Return the graphics queue for submitting command buffers.
impeller::Join::kRound
@ kRound
impeller::FilterContents::MakeBorderMaskBlur
static std::shared_ptr< FilterContents > MakeBorderMaskBlur(FilterInput::Ref input, Sigma sigma_x, Sigma sigma_y, BlurStyle blur_style=BlurStyle::kNormal)
Definition: filter_contents.cc:49
impeller::LinearGradientContents::IsOpaque
bool IsOpaque() const override
Whether this Contents only emits opaque source colors from the fragment stage. This value does not ac...
Definition: linear_gradient_contents.cc:47
impeller::Vector3::y
Scalar y
Definition: vector.h:24
impeller::FilterContents::BlurStyle::kInner
@ kInner
Blurred inside, nothing outside.
impeller::ClipRestoreContents
Definition: clip_contents.h:59
color_filter_contents.h
impeller::YUVColorSpace::kBT601LimitedRange
@ kBT601LimitedRange
impeller::Entity::TileMode
TileMode
Definition: entity.h:42
impeller::ColorFilterContents::MakeLinearToSrgbFilter
static std::shared_ptr< ColorFilterContents > MakeLinearToSrgbFilter(FilterInput::Ref input)
Definition: color_filter_contents.cc:68
impeller::Entity::GetBlendMode
BlendMode GetBlendMode() const
Definition: entity.cc:119
impeller::TPoint::x
Type x
Definition: point.h:30
impeller::FilterContents::MakeYUVToRGBFilter
static std::shared_ptr< FilterContents > MakeYUVToRGBFilter(std::shared_ptr< Texture > y_texture, std::shared_ptr< Texture > uv_texture, YUVColorSpace yuv_color_space)
Definition: filter_contents.cc:106
impeller::ContentContextOptions::primitive_type
PrimitiveType primitive_type
Definition: content_context.h:311
impeller::VertexBufferBuilder::CreateVertexBuffer
VertexBuffer CreateVertexBuffer(HostBuffer &host_buffer) const
Definition: vertex_buffer_builder.h:81
impeller::PixelFormat::kUnknown
@ kUnknown
impeller::Matrix::IsIdentity
constexpr bool IsIdentity() const
Definition: matrix.h:379
impeller::Close
void Close(PathBuilder *builder)
Definition: tessellator.cc:38
impeller::RenderPass
Render passes encode render commands directed as one specific render target into an underlying comman...
Definition: render_pass.h:33
impeller::Geometry::MakeRect
static std::shared_ptr< Geometry > MakeRect(const Rect &rect)
Definition: geometry.cc:89
impeller::TextureDescriptor::size
ISize size
Definition: texture_descriptor.h:42
atlas_contents.h
impeller::FilterContents::MorphType
MorphType
Definition: filter_contents.h:37
impeller::BlendMode::kDestinationIn
@ kDestinationIn
impeller::Join::kBevel
@ kBevel
content_context.h
impeller::BlendMode::kDestinationOut
@ kDestinationOut
impeller::EntityPlayground
Definition: entity_playground.h:17
impeller::FilterContents::BlurStyle::kOuter
@ kOuter
Nothing inside, blurred outside.
impeller::Entity::GetCoverage
std::optional< Rect > GetCoverage() const
Definition: entity.cc:66
impeller::Entity::SetTransform
void SetTransform(const Matrix &transform)
Set the global transform matrix for this Entity.
Definition: entity.cc:62
impeller::PlaygroundBackend::kOpenGLES
@ kOpenGLES
impeller::Matrix::MakeRotationZ
static Matrix MakeRotationZ(Radians r)
Definition: matrix.h:213
impeller::FilterContents::MakeMorphology
static std::shared_ptr< FilterContents > MakeMorphology(FilterInput::Ref input, Radius radius_x, Radius radius_y, MorphType morph_type)
Definition: filter_contents.cc:74
impeller::PathBuilder::Close
PathBuilder & Close()
Definition: path_builder.cc:40
impeller::LineTo
void LineTo(PathBuilder *builder, Scalar x, Scalar y)
Definition: tessellator.cc:24
vector.h
impeller::Join
Join
Definition: path.h:24
impeller::DrawPlaygroundPoint
Point DrawPlaygroundPoint(PlaygroundPoint &point)
Definition: widgets.cc:9
impeller::TiledTextureContents
Definition: tiled_texture_contents.h:21
impeller::PlaygroundPoint
Definition: widgets.h:17
impeller::Context
To do anything rendering related with Impeller, you need a context.
Definition: context.h:45
impeller::TRect< Scalar >::MakeSize
constexpr static TRect MakeSize(const TSize< U > &size)
Definition: rect.h:146
impeller::FilterContents::MorphType::kDilate
@ kDilate
impeller::RadialGradientContents::SetTileMode
void SetTileMode(Entity::TileMode tile_mode)
Definition: radial_gradient_contents.cc:26
impeller::TPoint< Scalar >
impeller::saturated::b
SI b
Definition: saturated_math.h:87
impeller::PathBuilder::MoveTo
PathBuilder & MoveTo(Point point, bool relative=false)
Definition: path_builder.cc:33
impeller::Color::BlackTransparent
static constexpr Color BlackTransparent()
Definition: color.h:272
impeller::SolidColorContents::SetColor
void SetColor(Color color)
Definition: solid_color_contents.cc:19
impeller::Color::Black
static constexpr Color Black()
Definition: color.h:268
impeller::HostBuffer::EmplaceUniform
BufferView EmplaceUniform(const UniformType &uniform)
Emplace uniform data onto the host buffer. Ensure that backend specific uniform alignment requirement...
Definition: host_buffer.h:50
impeller::BlendMode::kSourceIn
@ kSourceIn
scale
const Scalar scale
Definition: stroke_path_geometry.cc:308
blur_radius
Vector2 blur_radius
Blur radius in source pixels based on scaled_sigma.
Definition: gaussian_blur_filter_contents.cc:89
impeller::BlendMode::kScreen
@ kScreen
impeller::ClipContents
Definition: clip_contents.h:18
impeller::YUVColorSpace
YUVColorSpace
Definition: color.h:55
impeller::ColorFilterContents::MakeColorMatrix
static std::shared_ptr< ColorFilterContents > MakeColorMatrix(FilterInput::Ref input, const ColorMatrix &color_matrix)
Definition: color_filter_contents.cc:58
impeller::Contents::ClipCoverage::Type::kAppend
@ kAppend
color.h
impeller::LinearGradientContents
Definition: linear_gradient_contents.h:17
impeller::Color::WhiteTransparent
static constexpr Color WhiteTransparent()
Definition: color.h:270
color
DlColor color
Definition: dl_golden_blur_unittests.cc:23
impeller::TextureDescriptor::storage_mode
StorageMode storage_mode
Definition: texture_descriptor.h:39
radial_gradient_contents.h
impeller::testing::TestPassDelegate::CanElide
bool CanElide() override
Definition: entity_unittests.cc:84
impeller::DescriptorType::kInputAttachment
@ kInputAttachment
impeller::TRect< Scalar >::MakeLTRB
constexpr static TRect MakeLTRB(Type left, Type top, Type right, Type bottom)
Definition: rect.h:129
pipeline_descriptor.h
impeller::TextureDescriptor
A lightweight object that describes the attributes of a texture that can then used an allocator to cr...
Definition: texture_descriptor.h:38
render_target.h
impeller::BlendMode::kXor
@ kXor
impeller::Convexity::kConvex
@ kConvex
render_target_cache.h
impeller::Contents::IsOpaque
virtual bool IsOpaque() const
Whether this Contents only emits opaque source colors from the fragment stage. This value does not ac...
Definition: contents.cc:52
impeller::Color::Coral
static constexpr Color Coral()
Definition: color.h:340
impeller::TSize::MakeWH
static constexpr TSize MakeWH(Type width, Type height)
Definition: size.h:34
impeller::ColorMatrix
Definition: color.h:117
impeller::ColorFilterContents::MakeBlend
static std::shared_ptr< ColorFilterContents > MakeBlend(BlendMode blend_mode, FilterInput::Vector inputs, std::optional< Color > foreground_color=std::nullopt)
the [inputs] are expected to be in the order of dst, src.
Definition: color_filter_contents.cc:17
impeller::ContentContextOptions
Definition: content_context.h:254
impeller::TextureContents::MakeRect
static std::shared_ptr< TextureContents > MakeRect(Rect destination)
A common case factory that marks the texture contents as having a destination rectangle....
Definition: texture_contents.cc:27
impeller::ContentBoundsPromise::kContainsContents
@ kContainsContents
The caller claims the bounds are a reasonably tight estimate of the coverage of the contents and shou...
impeller::RadialGradientContents
Definition: radial_gradient_contents.h:17
impeller::testing::TestPassDelegate::CanCollapseIntoParentPass
bool CanCollapseIntoParentPass(EntityPass *entity_pass) override
Whether or not this entity pass can be collapsed into the parent. If true, this method may modify the...
Definition: entity_unittests.cc:87
impeller::RadialGradientContents::SetColors
void SetColors(std::vector< Color > colors)
Definition: radial_gradient_contents.cc:30
impeller
Definition: aiks_blend_unittests.cc:18
impeller::Matrix::MakeScale
static constexpr Matrix MakeScale(const Vector3 &s)
Definition: matrix.h:104
impeller::testing::TestPassDelegate::CreateContentsForSubpassTarget
std::shared_ptr< Contents > CreateContentsForSubpassTarget(std::shared_ptr< Texture > target, const Matrix &transform) override
Definition: entity_unittests.cc:92
impeller::BlendMode::kSourceATop
@ kSourceATop
impeller::Context::GetResourceAllocator
virtual std::shared_ptr< Allocator > GetResourceAllocator() const =0
Returns the allocator used to create textures and buffers on the device.
solid_rrect_blur_contents.h
impeller::ContentContext
Definition: content_context.h:366
impeller::Color::Premultiply
constexpr Color Premultiply() const
Definition: color.h:214
impeller::TypographerContextSkia::Make
static std::shared_ptr< TypographerContext > Make()
Definition: typographer_context_skia.cc:78
impeller::TRect
Definition: rect.h:122
impeller::GeometryResult::mode
Mode mode
Definition: geometry.h:38
impeller::Matrix
A 4x4 matrix using column-major storage.
Definition: matrix.h:37
impeller::Vector3
Definition: vector.h:20
impeller::BlendMode::kSourceOver
@ kSourceOver
impeller::ContentContext::GetTransientsBuffer
HostBuffer & GetTransientsBuffer() const
Retrieve the currnent host buffer for transient storage.
Definition: content_context.h:752
impeller::Cap
Cap
Definition: path.h:18
impeller::GeometryResult::Mode::kNonZero
@ kNonZero
impeller::Geometry::MakeCover
static std::shared_ptr< Geometry > MakeCover()
Definition: geometry.cc:85
impeller::DrawPlaygroundLine
std::tuple< Point, Point > DrawPlaygroundLine(PlaygroundPoint &point_a, PlaygroundPoint &point_b)
Definition: widgets.cc:50
impeller::EntityPass::AddEntity
void AddEntity(Entity entity)
Add an entity to the current entity pass.
Definition: entity_pass.cc:100
entity_playground.h
vertex_buffer_builder.h
linear_gradient_contents.h
impeller::TiledTextureContents::SetTexture
void SetTexture(std::shared_ptr< Texture > texture)
Definition: tiled_texture_contents.cc:40
impeller::FilterContents::MakeGaussianBlur
static std::shared_ptr< FilterContents > MakeGaussianBlur(const FilterInput::Ref &input, Sigma sigma_x, Sigma sigma_y, Entity::TileMode tile_mode=Entity::TileMode::kDecal, BlurStyle mask_blur_style=BlurStyle::kNormal, const std::shared_ptr< Geometry > &mask_geometry=nullptr)
Definition: filter_contents.cc:36