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