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