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, GetContentContext()->GetTransientsDataBuffer(),
1927 runtime_stage->GetUniforms()[0],
1928 GetContentContext()
1929 ->GetTransientsDataBuffer()
1930 .GetMinimumUniformAlignment());
1931
1932 // 16 bytes:
1933 // 8 bytes for iResolution
1934 // 4 bytes for iTime
1935 // 4 bytes padding
1936 EXPECT_EQ(buffer_view.GetRange().length, 16u);
1937}
1938
1939TEST_P(EntityTest, ColorFilterWithForegroundColorAdvancedBlend) {
1940 auto image = CreateTextureForFixture("boston.jpg");
1941 auto filter = ColorFilterContents::MakeBlend(
1943
1944 auto callback = [&](ContentContext& context, RenderPass& pass) -> bool {
1945 Entity entity;
1946 entity.SetTransform(Matrix::MakeScale(GetContentScale()) *
1947 Matrix::MakeTranslation({500, 300}) *
1948 Matrix::MakeScale(Vector2{0.5, 0.5}));
1949 entity.SetContents(filter);
1950 return entity.Render(context, pass);
1951 };
1952 ASSERT_TRUE(OpenPlaygroundHere(callback));
1953}
1954
1955TEST_P(EntityTest, ColorFilterWithForegroundColorClearBlend) {
1956 auto image = CreateTextureForFixture("boston.jpg");
1957 auto filter = ColorFilterContents::MakeBlend(
1959
1960 auto callback = [&](ContentContext& context, RenderPass& pass) -> bool {
1961 Entity entity;
1962 entity.SetTransform(Matrix::MakeScale(GetContentScale()) *
1963 Matrix::MakeTranslation({500, 300}) *
1964 Matrix::MakeScale(Vector2{0.5, 0.5}));
1965 entity.SetContents(filter);
1966 return entity.Render(context, pass);
1967 };
1968 ASSERT_TRUE(OpenPlaygroundHere(callback));
1969}
1970
1971TEST_P(EntityTest, ColorFilterWithForegroundColorSrcBlend) {
1972 auto image = CreateTextureForFixture("boston.jpg");
1973 auto filter = ColorFilterContents::MakeBlend(
1975
1976 auto callback = [&](ContentContext& context, RenderPass& pass) -> bool {
1977 Entity entity;
1978 entity.SetTransform(Matrix::MakeScale(GetContentScale()) *
1979 Matrix::MakeTranslation({500, 300}) *
1980 Matrix::MakeScale(Vector2{0.5, 0.5}));
1981 entity.SetContents(filter);
1982 return entity.Render(context, pass);
1983 };
1984 ASSERT_TRUE(OpenPlaygroundHere(callback));
1985}
1986
1987TEST_P(EntityTest, ColorFilterWithForegroundColorDstBlend) {
1988 auto image = CreateTextureForFixture("boston.jpg");
1989 auto filter = ColorFilterContents::MakeBlend(
1991
1992 auto callback = [&](ContentContext& context, RenderPass& pass) -> bool {
1993 Entity entity;
1994 entity.SetTransform(Matrix::MakeScale(GetContentScale()) *
1995 Matrix::MakeTranslation({500, 300}) *
1996 Matrix::MakeScale(Vector2{0.5, 0.5}));
1997 entity.SetContents(filter);
1998 return entity.Render(context, pass);
1999 };
2000 ASSERT_TRUE(OpenPlaygroundHere(callback));
2001}
2002
2003TEST_P(EntityTest, ColorFilterWithForegroundColorSrcInBlend) {
2004 auto image = CreateTextureForFixture("boston.jpg");
2005 auto filter = ColorFilterContents::MakeBlend(
2007
2008 auto callback = [&](ContentContext& context, RenderPass& pass) -> bool {
2009 Entity entity;
2010 entity.SetTransform(Matrix::MakeScale(GetContentScale()) *
2011 Matrix::MakeTranslation({500, 300}) *
2012 Matrix::MakeScale(Vector2{0.5, 0.5}));
2013 entity.SetContents(filter);
2014 return entity.Render(context, pass);
2015 };
2016 ASSERT_TRUE(OpenPlaygroundHere(callback));
2017}
2018
2019TEST_P(EntityTest, CoverageForStrokePathWithNegativeValuesInTransform) {
2020 auto arrow_head = flutter::DlPathBuilder{}
2021 .MoveTo({50, 120})
2022 .LineTo({120, 190})
2023 .LineTo({190, 120})
2024 .TakePath();
2025 auto geometry = Geometry::MakeStrokePath(arrow_head, //
2026 {
2027 .width = 15.0f,
2028 .cap = Cap::kRound,
2029 .join = Join::kRound,
2030 .miter_limit = 4.0f,
2031 });
2032
2033 auto transform = Matrix::MakeTranslation({300, 300}) *
2035 // Note that e[0][0] used to be tested here, but it was -epsilon solely
2036 // due to floating point inaccuracy in the transcendental trig functions.
2037 // e[1][0] is the intended negative value that we care about (-1.0) as it
2038 // comes from the rotation of pi/2.
2039 EXPECT_LT(transform.e[1][0], 0.0f);
2040 auto coverage = geometry->GetCoverage(transform);
2041 ASSERT_RECT_NEAR(coverage.value(), Rect::MakeXYWH(102.5, 342.5, 85, 155));
2042}
2043
2044TEST_P(EntityTest, SolidColorContentsIsOpaque) {
2045 Matrix matrix;
2046 SolidColorContents contents;
2047 auto geom = Geometry::MakeRect(Rect::MakeLTRB(0, 0, 10, 10));
2048 contents.SetGeometry(geom.get());
2049
2050 contents.SetColor(Color::CornflowerBlue());
2051 EXPECT_TRUE(contents.IsOpaque(matrix));
2052 contents.SetColor(Color::CornflowerBlue().WithAlpha(0.5));
2053 EXPECT_FALSE(contents.IsOpaque(matrix));
2054
2055 // Create stroked path that required alpha coverage.
2056 geom = Geometry::MakeStrokePath(flutter::DlPath::MakeLine({0, 0}, {100, 100}),
2057 {.width = 0.05});
2058 contents.SetGeometry(geom.get());
2059 contents.SetColor(Color::CornflowerBlue());
2060
2061 EXPECT_FALSE(contents.IsOpaque(matrix));
2062}
2063
2064TEST_P(EntityTest, ConicalGradientContentsIsOpaque) {
2065 Matrix matrix;
2066 ConicalGradientContents contents;
2067 auto geom = Geometry::MakeRect(Rect::MakeLTRB(0, 0, 10, 10));
2068 contents.SetGeometry(geom.get());
2069
2070 contents.SetColors({Color::CornflowerBlue()});
2071 EXPECT_FALSE(contents.IsOpaque(matrix));
2072 contents.SetColors({Color::CornflowerBlue().WithAlpha(0.5)});
2073 EXPECT_FALSE(contents.IsOpaque(matrix));
2074
2075 // Create stroked path that required alpha coverage.
2077 flutter::DlPathBuilder{}.MoveTo({0, 0}).LineTo({100, 100}).TakePath(),
2078 {.width = 0.05f});
2079 contents.SetGeometry(geom.get());
2080 contents.SetColors({Color::CornflowerBlue()});
2081
2082 EXPECT_FALSE(contents.IsOpaque(matrix));
2083}
2084
2085TEST_P(EntityTest, LinearGradientContentsIsOpaque) {
2086 Matrix matrix;
2087 LinearGradientContents contents;
2088 auto geom = Geometry::MakeRect(Rect::MakeLTRB(0, 0, 10, 10));
2089 contents.SetGeometry(geom.get());
2090
2091 contents.SetColors({Color::CornflowerBlue()});
2092 EXPECT_TRUE(contents.IsOpaque(matrix));
2093 contents.SetColors({Color::CornflowerBlue().WithAlpha(0.5)});
2094 EXPECT_FALSE(contents.IsOpaque(matrix));
2095 contents.SetColors({Color::CornflowerBlue()});
2097 EXPECT_FALSE(contents.IsOpaque(matrix));
2098
2099 // Create stroked path that required alpha coverage.
2101 flutter::DlPathBuilder{}.MoveTo({0, 0}).LineTo({100, 100}).TakePath(),
2102 {.width = 0.05f});
2103 contents.SetGeometry(geom.get());
2104 contents.SetColors({Color::CornflowerBlue()});
2105
2106 EXPECT_FALSE(contents.IsOpaque(matrix));
2107}
2108
2109TEST_P(EntityTest, RadialGradientContentsIsOpaque) {
2110 Matrix matrix;
2111 RadialGradientContents contents;
2112 auto geom = Geometry::MakeRect(Rect::MakeLTRB(0, 0, 10, 10));
2113 contents.SetGeometry(geom.get());
2114
2115 contents.SetColors({Color::CornflowerBlue()});
2116 EXPECT_TRUE(contents.IsOpaque(matrix));
2117 contents.SetColors({Color::CornflowerBlue().WithAlpha(0.5)});
2118 EXPECT_FALSE(contents.IsOpaque(matrix));
2119 contents.SetColors({Color::CornflowerBlue()});
2121 EXPECT_FALSE(contents.IsOpaque(matrix));
2122
2123 // Create stroked path that required alpha coverage.
2125 flutter::DlPathBuilder{}.MoveTo({0, 0}).LineTo({100, 100}).TakePath(),
2126 {.width = 0.05});
2127 contents.SetGeometry(geom.get());
2128 contents.SetColors({Color::CornflowerBlue()});
2129
2130 EXPECT_FALSE(contents.IsOpaque(matrix));
2131}
2132
2133TEST_P(EntityTest, SweepGradientContentsIsOpaque) {
2134 Matrix matrix;
2135 RadialGradientContents contents;
2136 auto geom = Geometry::MakeRect(Rect::MakeLTRB(0, 0, 10, 10));
2137 contents.SetGeometry(geom.get());
2138
2139 contents.SetColors({Color::CornflowerBlue()});
2140 EXPECT_TRUE(contents.IsOpaque(matrix));
2141 contents.SetColors({Color::CornflowerBlue().WithAlpha(0.5)});
2142 EXPECT_FALSE(contents.IsOpaque(matrix));
2143 contents.SetColors({Color::CornflowerBlue()});
2145 EXPECT_FALSE(contents.IsOpaque(matrix));
2146
2147 // Create stroked path that required alpha coverage.
2149 flutter::DlPathBuilder{}.MoveTo({0, 0}).LineTo({100, 100}).TakePath(),
2150 {.width = 0.05f});
2151 contents.SetGeometry(geom.get());
2152 contents.SetColors({Color::CornflowerBlue()});
2153
2154 EXPECT_FALSE(contents.IsOpaque(matrix));
2155}
2156
2157TEST_P(EntityTest, TiledTextureContentsIsOpaque) {
2158 Matrix matrix;
2159 auto bay_bridge = CreateTextureForFixture("bay_bridge.jpg");
2160 TiledTextureContents contents;
2161 contents.SetTexture(bay_bridge);
2162 // This is a placeholder test. Images currently never decompress as opaque
2163 // (whether in Flutter or the playground), and so this should currently always
2164 // return false in practice.
2165 EXPECT_FALSE(contents.IsOpaque(matrix));
2166}
2167
2168TEST_P(EntityTest, PointFieldGeometryCoverage) {
2169 std::vector<Point> points = {{10, 20}, {100, 200}};
2170 PointFieldGeometry geometry(points.data(), 2, 5.0, false);
2171 ASSERT_EQ(geometry.GetCoverage(Matrix()), Rect::MakeLTRB(5, 15, 105, 205));
2172 ASSERT_EQ(geometry.GetCoverage(Matrix::MakeTranslation({30, 0, 0})),
2173 Rect::MakeLTRB(35, 15, 135, 205));
2174}
2175
2176TEST_P(EntityTest, ColorFilterContentsWithLargeGeometry) {
2177 Entity entity;
2178 entity.SetTransform(Matrix::MakeScale(GetContentScale()));
2179 auto src_contents = std::make_shared<SolidColorContents>();
2180 auto src_geom = Geometry::MakeRect(Rect::MakeLTRB(-300, -500, 30000, 50000));
2181 src_contents->SetGeometry(src_geom.get());
2182 src_contents->SetColor(Color::Red());
2183
2184 auto dst_contents = std::make_shared<SolidColorContents>();
2185 auto dst_geom = Geometry::MakeRect(Rect::MakeLTRB(300, 500, 20000, 30000));
2186 dst_contents->SetGeometry(dst_geom.get());
2187 dst_contents->SetColor(Color::Blue());
2188
2189 auto contents = ColorFilterContents::MakeBlend(
2190 BlendMode::kSrcOver, {FilterInput::Make(dst_contents, false),
2191 FilterInput::Make(src_contents, false)});
2192 entity.SetContents(std::move(contents));
2193 ASSERT_TRUE(OpenPlaygroundHere(std::move(entity)));
2194}
2195
2196TEST_P(EntityTest, TextContentsCeilsGlyphScaleToDecimal) {
2197 ASSERT_EQ(TextFrame::RoundScaledFontSize(0.4321111f), Rational(43, 100));
2198 ASSERT_EQ(TextFrame::RoundScaledFontSize(0.5321111f), Rational(53, 100));
2199 ASSERT_EQ(TextFrame::RoundScaledFontSize(2.1f), Rational(21, 10));
2200 ASSERT_EQ(TextFrame::RoundScaledFontSize(0.0f), Rational(0, 1));
2201 ASSERT_EQ(TextFrame::RoundScaledFontSize(100000000.0f), Rational(48, 1));
2202}
2203
2204TEST_P(EntityTest, SpecializationConstantsAreAppliedToVariants) {
2205 auto content_context = GetContentContext();
2206
2207 auto default_gyph = content_context->GetGlyphAtlasPipeline({
2208 .color_attachment_pixel_format = PixelFormat::kR8G8B8A8UNormInt,
2209 .has_depth_stencil_attachments = false,
2210 });
2211 auto alt_gyph = content_context->GetGlyphAtlasPipeline(
2212 {.color_attachment_pixel_format = PixelFormat::kR8G8B8A8UNormInt,
2213 .has_depth_stencil_attachments = true});
2214
2215 EXPECT_NE(default_gyph, alt_gyph);
2216 EXPECT_EQ(default_gyph->GetDescriptor().GetSpecializationConstants(),
2217 alt_gyph->GetDescriptor().GetSpecializationConstants());
2218
2219 auto use_a8 = GetContext()->GetCapabilities()->GetDefaultGlyphAtlasFormat() ==
2221
2222 std::vector<Scalar> expected_constants = {static_cast<Scalar>(use_a8)};
2223 EXPECT_EQ(default_gyph->GetDescriptor().GetSpecializationConstants(),
2224 expected_constants);
2225}
2226
2227TEST_P(EntityTest, DecalSpecializationAppliedToMorphologyFilter) {
2228 auto content_context = GetContentContext();
2229 auto default_color_burn = content_context->GetMorphologyFilterPipeline({
2230 .color_attachment_pixel_format = PixelFormat::kR8G8B8A8UNormInt,
2231 });
2232
2233 auto decal_supported = static_cast<Scalar>(
2234 GetContext()->GetCapabilities()->SupportsDecalSamplerAddressMode());
2235 std::vector<Scalar> expected_constants = {decal_supported};
2236 ASSERT_EQ(default_color_burn->GetDescriptor().GetSpecializationConstants(),
2237 expected_constants);
2238}
2239
2240// This doesn't really tell you if the hashes will have frequent
2241// collisions, but since this type is only used to hash a bounded
2242// set of options, we can just compare benchmarks.
2243TEST_P(EntityTest, ContentContextOptionsHasReasonableHashFunctions) {
2245 auto hash_a = opts.ToKey();
2246
2248 auto hash_b = opts.ToKey();
2249
2250 opts.has_depth_stencil_attachments = false;
2251 auto hash_c = opts.ToKey();
2252
2254 auto hash_d = opts.ToKey();
2255
2256 EXPECT_NE(hash_a, hash_b);
2257 EXPECT_NE(hash_b, hash_c);
2258 EXPECT_NE(hash_c, hash_d);
2259}
2260
2261#ifdef FML_OS_LINUX
2262TEST_P(EntityTest, FramebufferFetchVulkanBindingOffsetIsTheSame) {
2263 // Using framebuffer fetch on Vulkan requires that we maintain a subpass input
2264 // binding that we don't have a good route for configuring with the
2265 // current metadata approach. This test verifies that the binding value
2266 // doesn't change
2267 // from the expected constant.
2268 // See also:
2269 // * impeller/renderer/backend/vulkan/binding_helpers_vk.cc
2270 // * impeller/entity/shaders/blending/framebuffer_blend.frag
2271 // This test only works on Linux because macOS hosts incorrectly
2272 // populate the
2273 // Vulkan descriptor sets based on the MSL compiler settings.
2274
2275 bool expected_layout = false;
2277 FragmentShader::kDescriptorSetLayouts) {
2278 if (layout.binding == 64 &&
2279 layout.descriptor_type == DescriptorType::kInputAttachment) {
2280 expected_layout = true;
2281 }
2282 }
2283 EXPECT_TRUE(expected_layout);
2284}
2285#endif
2286
2287TEST_P(EntityTest, FillPathGeometryGetPositionBufferReturnsExpectedMode) {
2289 testing::MockRenderPass mock_pass(GetContext(), target);
2290
2291 auto get_result = [this, &mock_pass](const flutter::DlPath& path) {
2292 auto geometry = Geometry::MakeFillPath(
2293 path, /* inner rect */ Rect::MakeLTRB(0, 0, 100, 100));
2294 return geometry->GetPositionBuffer(*GetContentContext(), {}, mock_pass);
2295 };
2296
2297 // Convex path
2298 {
2299 GeometryResult result =
2300 get_result(flutter::DlPath::MakeRect(Rect::MakeLTRB(0, 0, 100, 100)));
2301 EXPECT_EQ(result.mode, GeometryResult::Mode::kNormal);
2302 }
2303
2304 // Concave path
2305 {
2307 .MoveTo({0, 0})
2308 .LineTo({100, 0})
2309 .LineTo({100, 100})
2310 .LineTo({51, 50})
2311 .Close()
2312 .TakePath();
2313 GeometryResult result = get_result(path);
2314 EXPECT_EQ(result.mode, GeometryResult::Mode::kNonZero);
2315 }
2316}
2317
2318TEST_P(EntityTest, StrokeArcGeometryGetPositionBufferReturnsExpectedMode) {
2320 testing::MockRenderPass mock_pass(GetContext(), target);
2321 Rect oval_bounds = Rect::MakeLTRB(100, 100, 200, 200);
2322
2323 // Butt caps never overlap
2324 {
2325 StrokeParameters stroke = {.width = 50.0f, .cap = Cap::kButt};
2326 for (auto start = 0; start < 360; start += 60) {
2327 for (auto sweep = 0; sweep < 360; sweep += 12) {
2328 auto geometry = Geometry::MakeStrokedArc(oval_bounds, Degrees(start),
2329 Degrees(sweep), stroke);
2330
2331 GeometryResult result =
2332 geometry->GetPositionBuffer(*GetContentContext(), {}, mock_pass);
2333
2334 EXPECT_EQ(result.mode, GeometryResult::Mode::kNormal)
2335 << "start: " << start << " sweep: " << sweep;
2336 }
2337 }
2338 }
2339
2340 // Round caps with 10 stroke width overlap starting at 348.6 degrees
2341 {
2342 StrokeParameters stroke = {.width = 10.0f, .cap = Cap::kRound};
2343 for (auto start = 0; start < 360; start += 60) {
2344 for (auto sweep = 0; sweep < 360; sweep += 12) {
2345 auto geometry = Geometry::MakeStrokedArc(oval_bounds, Degrees(start),
2346 Degrees(sweep), stroke);
2347
2348 GeometryResult result =
2349 geometry->GetPositionBuffer(*GetContentContext(), {}, mock_pass);
2350
2351 if (sweep < 348.6) {
2352 EXPECT_EQ(result.mode, GeometryResult::Mode::kNormal)
2353 << "start: " << start << " sweep: " << sweep;
2354 } else {
2356 << "start: " << start << " sweep: " << sweep;
2357 }
2358 }
2359 }
2360 }
2361
2362 // Round caps with 50 stroke width overlap starting at 300.1 degrees
2363 {
2364 StrokeParameters stroke = {.width = 50.0f, .cap = Cap::kRound};
2365 for (auto start = 0; start < 360; start += 60) {
2366 for (auto sweep = 0; sweep < 360; sweep += 12) {
2367 auto geometry = Geometry::MakeStrokedArc(oval_bounds, Degrees(start),
2368 Degrees(sweep), stroke);
2369
2370 GeometryResult result =
2371 geometry->GetPositionBuffer(*GetContentContext(), {}, mock_pass);
2372
2373 if (sweep < 300.0) {
2374 EXPECT_EQ(result.mode, GeometryResult::Mode::kNormal)
2375 << "start: " << start << " sweep: " << sweep;
2376 } else {
2378 << "start: " << start << " sweep: " << sweep;
2379 }
2380 }
2381 }
2382 }
2383
2384 // Square caps with 10 stroke width overlap starting at 347.4 degrees
2385 {
2386 StrokeParameters stroke = {.width = 10.0f, .cap = Cap::kSquare};
2387 for (auto start = 0; start < 360; start += 60) {
2388 for (auto sweep = 0; sweep < 360; sweep += 12) {
2389 auto geometry = Geometry::MakeStrokedArc(oval_bounds, Degrees(start),
2390 Degrees(sweep), stroke);
2391
2392 GeometryResult result =
2393 geometry->GetPositionBuffer(*GetContentContext(), {}, mock_pass);
2394
2395 if (sweep < 347.4) {
2396 EXPECT_EQ(result.mode, GeometryResult::Mode::kNormal)
2397 << "start: " << start << " sweep: " << sweep;
2398 } else {
2400 << "start: " << start << " sweep: " << sweep;
2401 }
2402 }
2403 }
2404 }
2405
2406 // Square caps with 50 stroke width overlap starting at 270.1 degrees
2407 {
2408 StrokeParameters stroke = {.width = 50.0f, .cap = Cap::kSquare};
2409 for (auto start = 0; start < 360; start += 60) {
2410 for (auto sweep = 0; sweep < 360; sweep += 12) {
2411 auto geometry = Geometry::MakeStrokedArc(oval_bounds, Degrees(start),
2412 Degrees(sweep), stroke);
2413
2414 GeometryResult result =
2415 geometry->GetPositionBuffer(*GetContentContext(), {}, mock_pass);
2416
2417 if (sweep < 270.1) {
2418 EXPECT_EQ(result.mode, GeometryResult::Mode::kNormal)
2419 << "start: " << start << " sweep: " << sweep;
2420 } else {
2422 << "start: " << start << " sweep: " << sweep;
2423 }
2424 }
2425 }
2426 }
2427}
2428
2429TEST_P(EntityTest, FailOnValidationError) {
2430 if (GetParam() != PlaygroundBackend::kVulkan) {
2431 GTEST_SKIP() << "Validation is only fatal on Vulkan backend.";
2432 }
2433 EXPECT_DEATH(
2434 // The easiest way to trigger a validation error is to try to compile
2435 // a shader with an unsupported pixel format.
2436 GetContentContext()->GetBlendColorBurnPipeline({
2437 .color_attachment_pixel_format = PixelFormat::kUnknown,
2438 .has_depth_stencil_attachments = false,
2439 }),
2440 "");
2441}
2442
2443TEST_P(EntityTest, CanComputeGeometryForEmptyPathsWithoutCrashing) {
2445
2446 EXPECT_TRUE(path.GetBounds().IsEmpty());
2447
2448 auto geom = Geometry::MakeFillPath(path);
2449
2450 Entity entity;
2452 GetContentContext()->GetRenderTargetCache()->CreateOffscreen(
2453 *GetContext(), {1, 1}, 1u);
2454 testing::MockRenderPass render_pass(GetContext(), target);
2455 auto position_result =
2456 geom->GetPositionBuffer(*GetContentContext(), entity, render_pass);
2457
2458 EXPECT_EQ(position_result.vertex_buffer.vertex_count, 0u);
2459
2460 EXPECT_EQ(geom->GetResultMode(), GeometryResult::Mode::kNormal);
2461}
2462
2463TEST_P(EntityTest, CanRenderEmptyPathsWithoutCrashing) {
2465
2466 EXPECT_TRUE(path.GetBounds().IsEmpty());
2467
2468 auto contents = std::make_shared<SolidColorContents>();
2469 std::unique_ptr<Geometry> geom = Geometry::MakeFillPath(path);
2470 contents->SetGeometry(geom.get());
2471 contents->SetColor(Color::Red());
2472
2473 Entity entity;
2474 entity.SetTransform(Matrix::MakeScale(GetContentScale()));
2475 entity.SetContents(contents);
2476
2477 ASSERT_TRUE(OpenPlaygroundHere(std::move(entity)));
2478}
2479
2480TEST_P(EntityTest, DrawSuperEllipse) {
2481 auto callback = [&](ContentContext& context, RenderPass& pass) -> bool {
2482 // UI state.
2483 static float alpha = 10;
2484 static float beta = 10;
2485 static float radius = 40;
2486 static int degree = 4;
2487 static Color color = Color::Red();
2488
2489 ImGui::Begin("Controls", nullptr, ImGuiWindowFlags_AlwaysAutoResize);
2490 ImGui::SliderFloat("Alpha", &alpha, 0, 100);
2491 ImGui::SliderFloat("Beta", &beta, 0, 100);
2492 ImGui::SliderInt("Degreee", &degree, 1, 20);
2493 ImGui::SliderFloat("Radius", &radius, 0, 400);
2494 ImGui::ColorEdit4("Color", reinterpret_cast<float*>(&color));
2495 ImGui::End();
2496
2497 auto contents = std::make_shared<SolidColorContents>();
2498 std::unique_ptr<SuperellipseGeometry> geom =
2499 std::make_unique<SuperellipseGeometry>(Point{400, 400}, radius, degree,
2500 alpha, beta);
2501 contents->SetColor(color);
2502 contents->SetGeometry(geom.get());
2503
2504 Entity entity;
2505 entity.SetContents(contents);
2506
2507 return entity.Render(context, pass);
2508 };
2509
2510 ASSERT_TRUE(OpenPlaygroundHere(callback));
2511}
2512
2513TEST_P(EntityTest, DrawRoundSuperEllipse) {
2514 auto callback = [&](ContentContext& context, RenderPass& pass) -> bool {
2515 // UI state.
2516 static int style_index = 0;
2517 static float center[2] = {830, 830};
2518 static float size[2] = {600, 600};
2519 static bool horizontal_symmetry = true;
2520 static bool vertical_symmetry = true;
2521 static bool corner_symmetry = true;
2522
2523 const char* style_options[] = {"Fill", "Stroke"};
2524
2525 // Initially radius_tl[0] will be mirrored to all 8 values since all 3
2526 // symmetries are enabled.
2527 static std::array<float, 2> radius_tl = {200};
2528 static std::array<float, 2> radius_tr;
2529 static std::array<float, 2> radius_bl;
2530 static std::array<float, 2> radius_br;
2531
2532 auto AddRadiusControl = [](std::array<float, 2>& radii, const char* tb_name,
2533 const char* lr_name) {
2534 std::string name = "Radius";
2535 if (!horizontal_symmetry || !vertical_symmetry) {
2536 name += ":";
2537 }
2538 if (!vertical_symmetry) {
2539 name = name + " " + tb_name;
2540 }
2541 if (!horizontal_symmetry) {
2542 name = name + " " + lr_name;
2543 }
2544 if (corner_symmetry) {
2545 ImGui::SliderFloat(name.c_str(), radii.data(), 0, 1000);
2546 } else {
2547 ImGui::SliderFloat2(name.c_str(), radii.data(), 0, 1000);
2548 }
2549 };
2550
2551 if (corner_symmetry) {
2552 radius_tl[1] = radius_tl[0];
2553 radius_tr[1] = radius_tr[0];
2554 radius_bl[1] = radius_bl[0];
2555 radius_br[1] = radius_br[0];
2556 }
2557
2558 ImGui::Begin("Controls", nullptr, ImGuiWindowFlags_AlwaysAutoResize);
2559 {
2560 ImGui::Combo("Style", &style_index, style_options,
2561 sizeof(style_options) / sizeof(char*));
2562 ImGui::SliderFloat2("Center", center, 0, 1000);
2563 ImGui::SliderFloat2("Size", size, 0, 1000);
2564 ImGui::Checkbox("Symmetry: Horizontal", &horizontal_symmetry);
2565 ImGui::Checkbox("Symmetry: Vertical", &vertical_symmetry);
2566 ImGui::Checkbox("Symmetry: Corners", &corner_symmetry);
2567 AddRadiusControl(radius_tl, "Top", "Left");
2568 if (!horizontal_symmetry) {
2569 AddRadiusControl(radius_tr, "Top", "Right");
2570 } else {
2571 radius_tr = radius_tl;
2572 }
2573 if (!vertical_symmetry) {
2574 AddRadiusControl(radius_bl, "Bottom", "Left");
2575 } else {
2576 radius_bl = radius_tl;
2577 }
2578 if (!horizontal_symmetry && !vertical_symmetry) {
2579 AddRadiusControl(radius_br, "Bottom", "Right");
2580 } else {
2581 if (horizontal_symmetry) {
2582 radius_br = radius_bl;
2583 } else {
2584 radius_br = radius_tr;
2585 }
2586 }
2587 }
2588
2589 ImGui::End();
2590
2591 RoundingRadii radii{
2592 .top_left = {radius_tl[0], radius_tl[1]},
2593 .top_right = {radius_tr[0], radius_tr[1]},
2594 .bottom_left = {radius_bl[0], radius_bl[1]},
2595 .bottom_right = {radius_br[0], radius_br[1]},
2596 };
2597
2599 RectMakeCenterSize({center[0], center[1]}, {size[0], size[1]}), radii);
2600
2602 std::unique_ptr<Geometry> geom;
2603 if (style_index == 0) {
2604 geom = std::make_unique<RoundSuperellipseGeometry>(
2605 RectMakeCenterSize({center[0], center[1]}, {size[0], size[1]}),
2606 radii);
2607 } else {
2609 geom = Geometry::MakeStrokePath(path, {.width = 2.0f});
2610 }
2611
2612 auto contents = std::make_shared<SolidColorContents>();
2613 contents->SetColor(Color::Red());
2614 contents->SetGeometry(geom.get());
2615
2616 Entity entity;
2617 entity.SetContents(contents);
2618
2619 return entity.Render(context, pass);
2620 };
2621
2622 ASSERT_TRUE(OpenPlaygroundHere(callback));
2623}
2624
2625TEST_P(EntityTest, CanDrawRoundSuperEllipseWithTinyRadius) {
2626 // Regression test for https://github.com/flutter/flutter/issues/176894
2627 // Verify that a radius marginally below the minimum threshold can be
2628 // processed safely. The expectation is that the rounded corners degenerate
2629 // into sharp corners (four corner points) and that no NaNs or crashes occur.
2631 Rect::MakeLTRB(200, 200, 300, 300), 0.5 * kEhCloseEnough);
2632
2633 ContentContext content_context(GetContext(), /*typographer_context=*/nullptr);
2634 Entity entity;
2635
2636 auto cmd_buffer = content_context.GetContext()->CreateCommandBuffer();
2637
2639 content_context.GetContext()->GetResourceAllocator());
2640
2641 auto render_target = allocator.CreateOffscreen(
2642 *content_context.GetContext(), /*size=*/{500, 500}, /*mip_count=*/1);
2643 auto pass = cmd_buffer->CreateRenderPass(render_target);
2644
2645 GeometryResult result =
2646 geom->GetPositionBuffer(content_context, entity, *pass);
2647
2648 EXPECT_EQ(result.vertex_buffer.vertex_count, 4u);
2649 Point* written_data = reinterpret_cast<Point*>(
2652
2653 std::vector<Point> expected = {Point(300.0, 200.0), Point(300.0, 300.0),
2654 Point(200.0, 200.0), Point(200.0, 300.0)};
2655
2656 for (size_t i = 0; i < expected.size(); i++) {
2657 const Point& point = written_data[i];
2658 EXPECT_NEAR(point.x, expected[i].x, 0.1);
2659 EXPECT_NEAR(point.y, expected[i].y, 0.1);
2660 }
2661}
2662
2663TEST_P(EntityTest, CanDrawRoundSuperEllipseWithJustEnoughRadius) {
2664 // Regression test for https://github.com/flutter/flutter/issues/176894
2665 // Verify that a radius marginally above the minimum threshold can be
2666 // processed safely. The expectation is that the rounded corners are
2667 // drawn as rounded and that no NaNs or crashes occur.
2669 Rect::MakeLTRB(200, 200, 300, 300), 1.1 * kEhCloseEnough);
2670
2671 ContentContext content_context(GetContext(), /*typographer_context=*/nullptr);
2672 Entity entity;
2673
2674 auto cmd_buffer = content_context.GetContext()->CreateCommandBuffer();
2675
2677 content_context.GetContext()->GetResourceAllocator());
2678
2679 auto render_target = allocator.CreateOffscreen(
2680 *content_context.GetContext(), /*size=*/{500, 500}, /*mip_count=*/1);
2681 auto pass = cmd_buffer->CreateRenderPass(render_target);
2682
2683 GeometryResult result =
2684 geom->GetPositionBuffer(content_context, entity, *pass);
2685
2686 EXPECT_EQ(result.vertex_buffer.vertex_count, 200u);
2687 Point* written_data = reinterpret_cast<Point*>(
2690
2691 std::vector<Point> expected_head = {Point(250.0, 200.0), Point(299.9, 200.0),
2692 Point(200.1, 200.0), Point(299.9, 200.0)};
2693
2694 for (size_t i = 0; i < expected_head.size(); i++) {
2695 const Point& point = written_data[i];
2696 EXPECT_NEAR(point.x, expected_head[i].x, 0.1);
2697 EXPECT_NEAR(point.y, expected_head[i].y, 0.1);
2698 }
2699}
2700
2701TEST_P(EntityTest, SolidColorApplyColorFilter) {
2702 auto contents = SolidColorContents();
2703 contents.SetColor(Color::CornflowerBlue().WithAlpha(0.75));
2704 auto result = contents.ApplyColorFilter([](const Color& color) {
2705 return color.Blend(Color::LimeGreen().WithAlpha(0.75), BlendMode::kScreen);
2706 });
2707 ASSERT_TRUE(result);
2708 ASSERT_COLOR_NEAR(contents.GetColor(),
2709 Color(0.424452, 0.828743, 0.79105, 0.9375));
2710}
2711
2712#define APPLY_COLOR_FILTER_GRADIENT_TEST(name) \
2713 TEST_P(EntityTest, name##GradientApplyColorFilter) { \
2714 auto contents = name##GradientContents(); \
2715 contents.SetColors({Color::CornflowerBlue().WithAlpha(0.75)}); \
2716 auto result = contents.ApplyColorFilter([](const Color& color) { \
2717 return color.Blend(Color::LimeGreen().WithAlpha(0.75), \
2718 BlendMode::kScreen); \
2719 }); \
2720 ASSERT_TRUE(result); \
2721 \
2722 std::vector<Color> expected = {Color(0.433247, 0.879523, 0.825324, 0.75)}; \
2723 ASSERT_COLORS_NEAR(contents.GetColors(), expected); \
2724 }
2725
2730
2731TEST_P(EntityTest, GiantStrokePathAllocation) {
2732 flutter::DlPathBuilder builder;
2733 for (int i = 0; i < 10000; i++) {
2734 builder.LineTo(Point(i, i));
2735 }
2736 flutter::DlPath path = builder.TakePath();
2737 auto geom = Geometry::MakeStrokePath(path, {.width = 10.0f});
2738
2739 ContentContext content_context(GetContext(), /*typographer_context=*/nullptr);
2740 Entity entity;
2741
2742 auto cmd_buffer = content_context.GetContext()->CreateCommandBuffer();
2743
2745 content_context.GetContext()->GetResourceAllocator());
2746
2747 auto render_target = allocator.CreateOffscreen(
2748 *content_context.GetContext(), /*size=*/{10, 10}, /*mip_count=*/1);
2749 auto pass = cmd_buffer->CreateRenderPass(render_target);
2750
2751 GeometryResult result =
2752 geom->GetPositionBuffer(content_context, entity, *pass);
2753
2754 // Validate the buffer data overflowed the small buffer
2755 EXPECT_GT(result.vertex_buffer.vertex_count, kPointArenaSize);
2756
2757 // Validate that there are no uninitialized points near the gap.
2758 Point* written_data = reinterpret_cast<Point*>(
2761
2762 std::vector<Point> expected = {
2763 Point(2043.46, 2050.54), //
2764 Point(2050.54, 2043.46), //
2765 Point(2044.46, 2051.54), //
2766 Point(2051.54, 2044.46), //
2767 Point(2045.46, 2052.54) //
2768 };
2769
2770 Point point = written_data[kPointArenaSize - 2];
2771 EXPECT_NEAR(point.x, expected[0].x, 0.1);
2772 EXPECT_NEAR(point.y, expected[0].y, 0.1);
2773
2774 point = written_data[kPointArenaSize - 1];
2775 EXPECT_NEAR(point.x, expected[1].x, 0.1);
2776 EXPECT_NEAR(point.y, expected[1].y, 0.1);
2777
2778 point = written_data[kPointArenaSize];
2779 EXPECT_NEAR(point.x, expected[2].x, 0.1);
2780 EXPECT_NEAR(point.y, expected[2].y, 0.1);
2781
2782 point = written_data[kPointArenaSize + 1];
2783 EXPECT_NEAR(point.x, expected[3].x, 0.1);
2784 EXPECT_NEAR(point.y, expected[3].y, 0.1);
2785
2786 point = written_data[kPointArenaSize + 2];
2787 EXPECT_NEAR(point.x, expected[4].x, 0.1);
2788 EXPECT_NEAR(point.y, expected[4].y, 0.1);
2789}
2790
2792 public:
2794 : DeviceBuffer(desc), storage_(desc.size) {}
2795
2796 bool SetLabel(std::string_view label) override { return true; }
2797 bool SetLabel(std::string_view label, Range range) override { return true; }
2798 bool OnCopyHostBuffer(const uint8_t* source,
2799 Range source_range,
2800 size_t offset) {
2801 return true;
2802 }
2803
2804 uint8_t* OnGetContents() const override {
2805 return const_cast<uint8_t*>(storage_.data());
2806 }
2807
2808 void Flush(std::optional<Range> range) const override {
2809 flush_called_ = true;
2810 }
2811
2812 bool flush_called() const { return flush_called_; }
2813
2814 private:
2815 std::vector<uint8_t> storage_;
2816 mutable bool flush_called_ = false;
2817};
2818
2820 public:
2822 return ISize(1024, 1024);
2823 };
2824
2825 std::shared_ptr<DeviceBuffer> OnCreateBuffer(
2826 const DeviceBufferDescriptor& desc) override {
2827 return std::make_shared<FlushTestDeviceBuffer>(desc);
2828 };
2829
2830 std::shared_ptr<Texture> OnCreateTexture(const TextureDescriptor& desc,
2831 bool threadsafe) override {
2832 return nullptr;
2833 }
2834};
2835
2837 public:
2839 const std::shared_ptr<Context>& context,
2840 const std::shared_ptr<TypographerContext>& typographer_context,
2841 const std::shared_ptr<Allocator>& allocator)
2842 : ContentContext(context, typographer_context) {
2844 allocator, context->GetIdleWaiter(),
2845 context->GetCapabilities()->GetMinimumUniformAlignment()));
2847 allocator, context->GetIdleWaiter(),
2848 context->GetCapabilities()->GetMinimumUniformAlignment()));
2849 }
2850};
2851
2852TEST_P(EntityTest, RoundSuperellipseGetPositionBufferFlushes) {
2854 testing::MockRenderPass mock_pass(GetContext(), target);
2855
2856 auto content_context = std::make_shared<FlushTestContentContext>(
2857 GetContext(), GetTypographerContext(),
2858 std::make_shared<FlushTestAllocator>());
2859 auto geometry =
2861 auto result = geometry->GetPositionBuffer(*content_context, {}, mock_pass);
2862
2863 auto device_buffer = reinterpret_cast<const FlushTestDeviceBuffer*>(
2864 result.vertex_buffer.vertex_buffer.GetBuffer());
2865 EXPECT_TRUE(device_buffer->flush_called());
2866}
2867
2868} // namespace testing
2869} // namespace impeller
2870
2871// 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 EmplaceVulkanUniform(const std::shared_ptr< const std::vector< uint8_t > > &input_data, HostBuffer &host_buffer, const RuntimeUniformDescription &uniform, size_t minimum_uniform_alignment)
bool IsOpaque(const Matrix &transform) const override
Whether this Contents only emits opaque source colors from the fragment stage. This value does not ac...
A Geometry that produces fillable vertices representing the stroked outline of a |DlPath| object usin...
static Rational RoundScaledFontSize(Scalar scale)
Definition text_frame.cc:55
static std::shared_ptr< TextureContents > MakeRect(Rect destination)
bool IsOpaque(const Matrix &transform) const override
Whether this Contents only emits opaque source colors from the fragment stage. This value does not ac...
void SetTexture(std::shared_ptr< Texture > texture)
VertexBuffer CreateVertexBuffer(HostBuffer &data_host_buffer, HostBuffer &indexes_host_buffer) const
VertexBufferBuilder & AddVertices(std::initializer_list< VertexType_ > vertices)
std::shared_ptr< DeviceBuffer > OnCreateBuffer(const DeviceBufferDescriptor &desc) override
std::shared_ptr< Texture > OnCreateTexture(const TextureDescriptor &desc, bool threadsafe) override
ISize GetMaxTextureSizeSupported() const override
FlushTestContentContext(const std::shared_ptr< Context > &context, const std::shared_ptr< TypographerContext > &typographer_context, const std::shared_ptr< Allocator > &allocator)
bool OnCopyHostBuffer(const uint8_t *source, Range source_range, size_t offset)
void Flush(std::optional< Range > range) const override
bool SetLabel(std::string_view label, Range range) override
bool SetLabel(std::string_view label) override
FlushTestDeviceBuffer(const DeviceBufferDescriptor &desc)
static int input(yyscan_t yyscanner)
FlutterVulkanImage * image
#define APPLY_COLOR_FILTER_GRADIENT_TEST(name)
auto & d
Definition main.cc:28
uint32_t * target
FlutterDesktopBinaryReply callback
#define FML_DLOG(severity)
Definition logging.h:121
#define FML_CHECK(condition)
Definition logging.h:104
Vector2 blur_radius
Blur radius in source pixels based on scaled_sigma.
Vector2 padding
The halo padding in source space.
#define ASSERT_RECT_NEAR(a, b)
#define ASSERT_COLOR_NEAR(a, b)
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
static Matrix MakeRotationY(Radians r)
Definition matrix.h:208
static constexpr Matrix MakeSkew(Scalar sx, Scalar sy)
Definition matrix.h:127
Scalar e[4][4]
Definition matrix.h:40
static Matrix MakeRotationZ(Radians r)
Definition matrix.h:223
static constexpr Matrix MakeScale(const Vector3 &s)
Definition matrix.h:104
static Matrix MakeRotationX(Radians r)
Definition matrix.h:193
For convolution filters, the "radius" is the size of the convolution kernel to use on the local space...
Definition sigma.h:48
size_t length
Definition range.h:15
size_t offset
Definition range.h:14
static RoundSuperellipse MakeRectRadii(const Rect &rect, const RoundingRadii &radii)
In filters that use Gaussian distributions, "sigma" is a size of one standard deviation in terms of t...
Definition sigma.h:32
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