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