Flutter Engine
 
Loading...
Searching...
No Matches
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
14#include "fml/logging.h"
15#include "gtest/gtest.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
62namespace impeller {
63namespace testing {
64
65using EntityTest = EntityPlayground;
67
69 return Rect::MakeSize(size).Shift(center - size / 2);
70}
71
72TEST_P(EntityTest, CanCreateEntity) {
73 Entity entity;
74 ASSERT_TRUE(entity.GetTransform().IsIdentity());
75}
76
77TEST_P(EntityTest, FilterCoverageRespectsCropRect) {
78 auto image = CreateTextureForFixture("boston.jpg");
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
102TEST_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
110TEST_P(EntityTest, ThreeStrokesInOnePath) {
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
132TEST_P(EntityTest, StrokeWithTextureContents) {
133 auto bridge = CreateTextureForFixture("bay_bridge.jpg");
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
155TEST_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);
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
200TEST_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 =
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(),
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(),
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(),
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(),
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(),
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(),
363 }
364
365 return true;
366 };
367 ASSERT_TRUE(OpenPlaygroundHere(callback));
368}
369
370TEST_P(EntityTest, CubicCurveTest) {
371 // Compare with https://fiddle.skia.org/c/b3625f26122c9de7afe7794fcf25ead3
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
399TEST_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())
431 Vector3(Point(pass.GetRenderTargetSize().width / 2.0,
432 pass.GetRenderTargetSize().height / 2.0)));
433 Matrix result_transform = current_transform * rotation_matrix;
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
452TEST_P(EntityTest, CubicCurveAndOverlapTest) {
453 // Compare with https://fiddle.skia.org/c/7a05a3e186c65a8dfb732f68020aae06
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
687TEST_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
719TEST_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
747TEST_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.
760 switch (b) {
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);
771 blend_mode_names.push_back("SourceOver");
772 blend_mode_values.push_back(BlendMode::kSrcOver);
774 blend_mode_names.push_back("DestinationOver");
775 blend_mode_values.push_back(BlendMode::kDstOver);
777 blend_mode_names.push_back("SourceIn");
778 blend_mode_values.push_back(BlendMode::kSrcIn);
780 blend_mode_names.push_back("DestinationIn");
781 blend_mode_values.push_back(BlendMode::kDstIn);
783 blend_mode_names.push_back("SourceOut");
784 blend_mode_values.push_back(BlendMode::kSrcOut);
786 blend_mode_names.push_back("DestinationOut");
787 blend_mode_values.push_back(BlendMode::kDstOut);
789 blend_mode_names.push_back("SourceATop");
790 blend_mode_values.push_back(BlendMode::kSrcATop);
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);
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
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),
870 result = result && draw_rect(Rect::MakeLTRB(a.x, a.y, b.x, b.y), color1,
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
879TEST_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()));
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
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(
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
948TEST_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[] = {
962 const Entity::TileMode tile_modes[] = {
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 =
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:
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 =
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 =
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
1130TEST_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[] = {
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 =
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
1236TEST_P(EntityTest, SetBlendMode) {
1237 Entity entity;
1238 ASSERT_EQ(entity.GetBlendMode(), BlendMode::kSrcOver);
1240 ASSERT_EQ(entity.GetBlendMode(), BlendMode::kClear);
1241}
1242
1243TEST_P(EntityTest, ContentsGetBoundsForEmptyPathReturnsNullopt) {
1244 Entity entity;
1245 entity.SetContents(std::make_shared<SolidColorContents>());
1246 ASSERT_FALSE(entity.GetCoverage().has_value());
1247}
1248
1249TEST_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
1320TEST_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;
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
1348TEST_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);
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;
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
1393TEST_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
1447TEST_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 =
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
1477TEST_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.
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
1535TEST_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 =
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
1557TEST_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 =
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}));
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
1588TEST_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 =
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
1610TEST_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 =
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}));
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
1641static Vector3 RGBToYUV(Vector3 rgb, YUVColorSpace yuv_color_space) {
1642 Vector3 yuv;
1643 switch (yuv_color_space) {
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;
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
1658static 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;
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
1719TEST_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,
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
1754TEST_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 {
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
1822TEST_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.
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
1869TEST_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
1883TEST_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
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
1925TEST_P(EntityTest, ColorFilterWithForegroundColorAdvancedBlend) {
1926 auto image = CreateTextureForFixture("boston.jpg");
1927 auto filter = ColorFilterContents::MakeBlend(
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
1941TEST_P(EntityTest, ColorFilterWithForegroundColorClearBlend) {
1942 auto image = CreateTextureForFixture("boston.jpg");
1943 auto filter = ColorFilterContents::MakeBlend(
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
1957TEST_P(EntityTest, ColorFilterWithForegroundColorSrcBlend) {
1958 auto image = CreateTextureForFixture("boston.jpg");
1959 auto filter = ColorFilterContents::MakeBlend(
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
1973TEST_P(EntityTest, ColorFilterWithForegroundColorDstBlend) {
1974 auto image = CreateTextureForFixture("boston.jpg");
1975 auto filter = ColorFilterContents::MakeBlend(
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
1989TEST_P(EntityTest, ColorFilterWithForegroundColorSrcInBlend) {
1990 auto image = CreateTextureForFixture("boston.jpg");
1991 auto filter = ColorFilterContents::MakeBlend(
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
2005TEST_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}) *
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
2030TEST_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
2050TEST_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.
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
2071TEST_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()});
2083 EXPECT_FALSE(contents.IsOpaque(matrix));
2084
2085 // Create stroked path that required alpha coverage.
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
2095TEST_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()});
2107 EXPECT_FALSE(contents.IsOpaque(matrix));
2108
2109 // Create stroked path that required alpha coverage.
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
2119TEST_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()});
2131 EXPECT_FALSE(contents.IsOpaque(matrix));
2132
2133 // Create stroked path that required alpha coverage.
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
2143TEST_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
2154TEST_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
2162TEST_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
2182TEST_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
2190TEST_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() ==
2207
2208 std::vector<Scalar> expected_constants = {static_cast<Scalar>(use_a8)};
2209 EXPECT_EQ(default_gyph->GetDescriptor().GetSpecializationConstants(),
2210 expected_constants);
2211}
2212
2213TEST_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.
2229TEST_P(EntityTest, ContentContextOptionsHasReasonableHashFunctions) {
2231 auto hash_a = opts.ToKey();
2232
2234 auto hash_b = opts.ToKey();
2235
2236 opts.has_depth_stencil_attachments = false;
2237 auto hash_c = opts.ToKey();
2238
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
2248TEST_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;
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
2273TEST_P(EntityTest, FillPathGeometryGetPositionBufferReturnsExpectedMode) {
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 {
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
2304TEST_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
2318TEST_P(EntityTest, CanComputeGeometryForEmptyPathsWithoutCrashing) {
2320
2321 EXPECT_TRUE(path.GetBounds().IsEmpty());
2322
2323 auto geom = Geometry::MakeFillPath(path);
2324
2325 Entity entity;
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
2338TEST_P(EntityTest, CanRenderEmptyPathsWithoutCrashing) {
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
2355TEST_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
2388TEST_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
2474 RectMakeCenterSize({center[0], center[1]}, {size[0], size[1]}), radii);
2475
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 {
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
2500TEST_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.
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*>(
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
2538TEST_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.
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*>(
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
2576TEST_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
2605
2606TEST_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*>(
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
2667 public:
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
2695 public:
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
2712 public:
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) {
2719 allocator, context->GetIdleWaiter(),
2720 context->GetCapabilities()->GetMinimumUniformAlignment()));
2722 allocator, context->GetIdleWaiter(),
2723 context->GetCapabilities()->GetMinimumUniformAlignment()));
2724 }
2725};
2726
2727TEST_P(EntityTest, RoundSuperellipseGetPositionBufferFlushes) {
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 =
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)
BufferView buffer_view
DlPathBuilder & LineTo(DlPoint p2)
Draw a line from the current point to the indicated point p2.
DlPathBuilder & MoveTo(DlPoint p2)
Start a new contour that will originate at the indicated point p2.
const DlPath TakePath()
Returns the path constructed by this path builder and resets its internal state to the default state ...
DlPathBuilder & Close()
The path is closed back to the location of the most recent MoveTo call. Contours that are filled are ...
static DlPath MakeLine(const DlPoint a, const DlPoint b)
Definition dl_path.cc:89
static DlPath MakeRect(const DlRect &rect)
Definition dl_path.cc:39
static DlPath MakeRoundSuperellipse(const DlRoundSuperellipse &rse)
Definition dl_path.cc:85
double x() const
Definition geometry.h:22
double y() const
Definition geometry.h:23
An object that allocates device memory.
Definition allocator.h:24
static std::shared_ptr< ColorFilterContents > MakeColorMatrix(FilterInput::Ref input, const ColorMatrix &color_matrix)
static std::shared_ptr< ColorFilterContents > MakeSrgbToLinearFilter(FilterInput::Ref input)
static std::shared_ptr< ColorFilterContents > MakeLinearToSrgbFilter(FilterInput::Ref input)
static std::shared_ptr< ColorFilterContents > MakeBlend(BlendMode blend_mode, FilterInput::Vector inputs, std::optional< Color > foreground_color=std::nullopt)
the [inputs] are expected to be in the order of dst, src.
void SetGeometry(const Geometry *geometry)
Set the geometry that this contents will use to render.
void SetColors(std::vector< Color > colors)
HostBuffer & GetTransientsDataBuffer() const
Retrieve the current host buffer for transient storage of other non-index data.
void SetTransientsDataBuffer(std::shared_ptr< HostBuffer > host_buffer)
PipelineRef GetSolidFillPipeline(ContentContextOptions opts) const
void SetTransientsIndexesBuffer(std::shared_ptr< HostBuffer > host_buffer)
HostBuffer & GetTransientsIndexesBuffer() const
Retrieve the current host buffer for transient storage of indexes used for indexed draws.
std::shared_ptr< Context > GetContext() const
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
To do anything rendering related with Impeller, you need a context.
Definition context.h:65
virtual std::shared_ptr< CommandQueue > GetCommandQueue() const =0
Return the graphics queue for submitting command buffers.
virtual std::shared_ptr< CommandBuffer > CreateCommandBuffer() const =0
Create a new command buffer. Command buffers can be used to encode graphics, blit,...
virtual std::shared_ptr< Allocator > GetResourceAllocator() const =0
Returns the allocator used to create textures and buffers on the device.
static BufferView AsBufferView(std::shared_ptr< DeviceBuffer > buffer)
Create a buffer view of this entire buffer.
virtual uint8_t * OnGetContents() const =0
void SetTransform(const Matrix &transform)
Set the global transform matrix for this Entity.
Definition entity.cc:60
std::optional< Rect > GetCoverage() const
Definition entity.cc:64
BlendMode GetBlendMode() const
Definition entity.cc:101
void SetContents(std::shared_ptr< Contents > contents)
Definition entity.cc:72
void SetBlendMode(BlendMode blend_mode)
Definition entity.cc:97
bool Render(const ContentContext &renderer, RenderPass &parent_pass) const
Definition entity.cc:144
const Matrix & GetTransform() const
Get the global transform matrix for this Entity.
Definition entity.cc:44
static constexpr BlendMode kLastPipelineBlendMode
Definition entity.h:28
@ kNormal
Blurred inside and outside.
@ kOuter
Nothing inside, blurred outside.
@ kInner
Blurred inside, nothing outside.
@ kSolid
Solid inside, blurred outside.
static std::shared_ptr< FilterContents > MakeMorphology(FilterInput::Ref input, Radius radius_x, Radius radius_y, MorphType morph_type)
static std::shared_ptr< FilterContents > MakeBorderMaskBlur(FilterInput::Ref input, Sigma sigma_x, Sigma sigma_y, BlurStyle blur_style=BlurStyle::kNormal)
static std::shared_ptr< FilterContents > 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)
static std::shared_ptr< FilterContents > MakeYUVToRGBFilter(std::shared_ptr< Texture > y_texture, std::shared_ptr< Texture > uv_texture, YUVColorSpace yuv_color_space)
static FilterInput::Ref Make(Variant input, bool msaa_enabled=true)
static std::unique_ptr< Geometry > MakeFillPath(const flutter::DlPath &path, std::optional< Rect > inner_rect=std::nullopt)
Definition geometry.cc:62
static std::unique_ptr< Geometry > MakeRect(const Rect &rect)
Definition geometry.cc:83
static std::unique_ptr< Geometry > MakeStrokePath(const flutter::DlPath &path, const StrokeParameters &stroke={})
Definition geometry.cc:68
static std::unique_ptr< Geometry > MakeRoundSuperellipse(const Rect &rect, Scalar corner_radius)
Definition geometry.cc:130
static std::unique_ptr< Geometry > MakeCover()
Definition geometry.cc:79
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)
BufferView EmplaceUniform(const UniformType &uniform)
Emplace uniform data onto the host buffer. Ensure that backend specific uniform alignment requirement...
Definition host_buffer.h:47
void SetTileMode(Entity::TileMode tile_mode)
void SetColors(std::vector< Color > colors)
bool IsOpaque(const Matrix &transform) const override
Whether this Contents only emits opaque source colors from the fragment stage. This value does not ac...
A geometry class specialized for Canvas::DrawPoints.
std::optional< Rect > GetCoverage(const Matrix &transform) const override
bool IsOpaque(const Matrix &transform) const override
Whether this Contents only emits opaque source colors from the fragment stage. This value does not ac...
void SetTileMode(Entity::TileMode tile_mode)
void SetColors(std::vector< Color > colors)
Render passes encode render commands directed as one specific render target into an underlying comman...
Definition render_pass.h:30
virtual const std::vector< Command > & GetCommands() const
Accessor for the current Commands.
a wrapper around the impeller [Allocator] instance that can be used to provide caching of allocated r...
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)
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)
bool IsOpaque(const Matrix &transform) const override
Whether this Contents only emits opaque source colors from the fragment stage. This value does not ac...
A Geometry that produces fillable vertices representing the stroked outline of a |DlPath| object usin...
static Rational RoundScaledFontSize(Scalar scale)
Definition text_frame.cc:55
static std::shared_ptr< TextureContents > MakeRect(Rect destination)
bool IsOpaque(const Matrix &transform) const override
Whether this Contents only emits opaque source colors from the fragment stage. This value does not ac...
void SetTexture(std::shared_ptr< Texture > texture)
VertexBuffer CreateVertexBuffer(HostBuffer &data_host_buffer, HostBuffer &indexes_host_buffer) const
VertexBufferBuilder & AddVertices(std::initializer_list< VertexType_ > vertices)
std::shared_ptr< DeviceBuffer > OnCreateBuffer(const DeviceBufferDescriptor &desc) override
std::shared_ptr< Texture > OnCreateTexture(const TextureDescriptor &desc, bool threadsafe) override
ISize GetMaxTextureSizeSupported() const override
FlushTestContentContext(const std::shared_ptr< Context > &context, const std::shared_ptr< TypographerContext > &typographer_context, const std::shared_ptr< Allocator > &allocator)
bool OnCopyHostBuffer(const uint8_t *source, Range source_range, size_t offset)
void Flush(std::optional< Range > range) const override
bool SetLabel(std::string_view label, Range range) override
bool SetLabel(std::string_view label) override
FlushTestDeviceBuffer(const DeviceBufferDescriptor &desc)
static int input(yyscan_t yyscanner)
FlutterVulkanImage * image
#define APPLY_COLOR_FILTER_GRADIENT_TEST(name)
auto & d
Definition main.cc:28
uint32_t * target
FlutterDesktopBinaryReply callback
#define FML_DLOG(severity)
Definition logging.h:121
#define FML_CHECK(condition)
Definition logging.h:104
Vector2 blur_radius
Blur radius in source pixels based on scaled_sigma.
Vector2 padding
The halo padding in source space.
#define ASSERT_RECT_NEAR(a, b)
#define ASSERT_COLOR_NEAR(a, b)
FlTexture * texture
double y
it will be possible to load the file into Perfetto s trace viewer use test Running tests that layout and measure text will not yield consistent results across various platforms Enabling this option will make font resolution default to the Ahem test font on all disable asset Prevents usage of any non test fonts unless they were explicitly Loaded via prefetched default font Indicates whether the embedding started a prefetch of the default font manager before creating the engine run In non interactive keep the shell running after the Dart script has completed enable serial On low power devices with low core running concurrent GC tasks on threads can cause them to contend with the UI thread which could potentially lead to jank This option turns off all concurrent GC activities domain network JSON encoded network policy per domain This overrides the DisallowInsecureConnections switch Embedder can specify whether to allow or disallow insecure connections at a domain level old gen heap size
DEF_SWITCHES_START aot vmservice shared library Name of the *so containing AOT compiled Dart assets for launching the service isolate vm snapshot The VM snapshot data that will be memory mapped as read only SnapshotAssetPath must be present isolate snapshot The isolate snapshot data that will be memory mapped as read only SnapshotAssetPath must be present cache dir path
Definition switch_defs.h:52
DEF_SWITCHES_START aot vmservice shared library name
Definition switch_defs.h:27
static constexpr DlScalar kPi
static constexpr DlScalar kEhCloseEnough
Rect RectMakeCenterSize(Point center, Size size)
TEST_P(AiksTest, DrawAtlasNoColor)
static std::vector< std::shared_ptr< Texture > > CreateTestYUVTextures(Context *context, YUVColorSpace yuv_color_space)
static Vector3 RGBToYUV(Vector3 rgb, YUVColorSpace yuv_color_space)
YUVColorSpace
Definition color.h:54
Join
An enum that describes ways to join two segments of a path.
@ kPoint
Draws a point at each input vertex.
Point Vector2
Definition point.h:331
float Scalar
Definition scalar.h:19
Point DrawPlaygroundPoint(PlaygroundPoint &point)
Definition widgets.cc:11
std::tuple< Point, Point > DrawPlaygroundLine(PlaygroundPoint &point_a, PlaygroundPoint &point_b)
Definition widgets.cc:51
constexpr RuntimeStageBackend PlaygroundBackendToRuntimeStageBackend(PlaygroundBackend backend)
Definition playground.h:33
Cap
An enum that describes ways to decorate the end of a path contour.
LinePipeline::FragmentShader FS
constexpr float kPiOver2
Definition constants.h:32
BlendMode
Definition color.h:58
FramebufferBlendPipelineHandle FramebufferBlendColorBurnPipeline
Definition pipelines.h:117
LinePipeline::VertexShader VS
void MoveTo(PathBuilder *builder, Scalar x, Scalar y)
static constexpr size_t kPointArenaSize
The size of the point arena buffer stored on the tessellator.
Definition tessellator.h:25
void LineTo(PathBuilder *builder, Scalar x, Scalar y)
ContentContextOptions OptionsFromPass(const RenderPass &pass)
Definition contents.cc:19
ISize64 ISize
Definition size.h:162
void Close(PathBuilder *builder)
#define INSTANTIATE_PLAYGROUND_SUITE(playground)
int32_t height
int32_t width
Range GetRange() const
Definition buffer_view.h:27
const DeviceBuffer * GetBuffer() const
static constexpr Color LimeGreen()
Definition color.h:602
static constexpr Color MintCream()
Definition color.h:658
Scalar alpha
Definition color.h:143
static constexpr Color DeepPink()
Definition color.h:434
static constexpr Color Black()
Definition color.h:266
static constexpr Color CornflowerBlue()
Definition color.h:342
static constexpr Color White()
Definition color.h:264
constexpr Color WithAlpha(Scalar new_alpha) const
Definition color.h:278
static constexpr Color WhiteTransparent()
Definition color.h:268
static constexpr Color Coral()
Definition color.h:338
static constexpr Color Red()
Definition color.h:272
constexpr Color Premultiply() const
Definition color.h:212
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
static constexpr Color Blue()
Definition color.h:276
static constexpr Color Green()
Definition color.h:274
Scalar array[20]
Definition color.h:118
constexpr uint64_t ToKey() const
@ kNormal
The geometry has no overlapping triangles.
VertexBuffer vertex_buffer
Definition geometry.h:38
A 4x4 matrix using column-major storage.
Definition matrix.h:37
static constexpr Matrix MakeTranslation(const Vector3 &t)
Definition matrix.h:95
constexpr bool IsIdentity() const
Definition matrix.h:467
static Matrix MakeRotationY(Radians r)
Definition matrix.h:208
static constexpr Matrix MakeSkew(Scalar sx, Scalar sy)
Definition matrix.h:127
Scalar e[4][4]
Definition matrix.h:40
static Matrix MakeRotationZ(Radians r)
Definition matrix.h:223
static constexpr Matrix MakeScale(const Vector3 &s)
Definition matrix.h:104
static Matrix MakeRotationX(Radians r)
Definition matrix.h:193
For convolution filters, the "radius" is the size of the convolution kernel to use on the local space...
Definition sigma.h:48
size_t length
Definition range.h:15
size_t offset
Definition range.h:14
static RoundSuperellipse MakeRectRadii(const Rect &rect, const RoundingRadii &radii)
In filters that use Gaussian distributions, "sigma" is a size of one standard deviation in terms of t...
Definition sigma.h:32
static constexpr TRect MakeXYWH(Type x, Type y, Type width, Type height)
Definition rect.h:136
static constexpr TRect MakeSize(const TSize< U > &size)
Definition rect.h:150
constexpr TRect< T > Shift(T dx, T dy) const
Returns a new rectangle translated by the given offset.
Definition rect.h:602
static constexpr TRect MakeLTRB(Type left, Type top, Type right, Type bottom)
Definition rect.h:129
A lightweight object that describes the attributes of a texture that can then used an allocator to cr...
std::vector< Point > points