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