Flutter Impeller
aiks_dl_runtime_effect_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 <memory>
6 
7 #include "absl/status/statusor.h"
8 
9 #include "flutter/display_list/dl_builder.h"
10 #include "flutter/display_list/dl_paint.h"
11 #include "flutter/display_list/effects/dl_color_source.h"
12 #include "flutter/display_list/effects/dl_image_filter.h"
13 #include "flutter/display_list/effects/dl_runtime_effect.h"
17 #include "third_party/abseil-cpp/absl/status/status_matchers.h"
18 
19 namespace impeller {
20 namespace testing {
21 
22 using namespace flutter;
23 
24 namespace {
25 absl::StatusOr<std::shared_ptr<DlColorSource>> MakeRuntimeEffect(
26  AiksTest* test,
27  std::string_view name,
28  const std::shared_ptr<std::vector<uint8_t>>& uniform_data = {},
29  const std::vector<std::shared_ptr<DlColorSource>>& samplers = {}) {
30  auto runtime_stages_result = test->OpenAssetAsRuntimeStage(name.data());
31  if (!runtime_stages_result.ok()) {
32  return runtime_stages_result.status();
33  }
34  std::shared_ptr<RuntimeStage> runtime_stage =
35  runtime_stages_result
36  .value()[PlaygroundBackendToRuntimeStageBackend(test->GetBackend())];
37  if (!runtime_stage) {
38  return absl::InternalError("Runtime stage not found for backend.");
39  }
40  if (!runtime_stage->IsDirty()) {
41  return absl::InternalError("Runtime stage is not dirty.");
42  }
43 
44  auto dl_runtime_effect = DlRuntimeEffectImpeller::Make(runtime_stage);
45 
46  return DlColorSource::MakeRuntimeEffect(dl_runtime_effect, samplers,
47  uniform_data);
48 }
49 } // namespace
50 
51 // Regression test for https://github.com/flutter/flutter/issues/126701 .
52 TEST_P(AiksTest, CanRenderClippedRuntimeEffects) {
53  struct FragUniforms {
54  Vector2 iResolution;
55  Scalar iTime;
56  } frag_uniforms = {.iResolution = Vector2(400, 400), .iTime = 100.0};
57  auto uniform_data = std::make_shared<std::vector<uint8_t>>();
58  uniform_data->resize(sizeof(FragUniforms));
59  memcpy(uniform_data->data(), &frag_uniforms, sizeof(FragUniforms));
60 
61  DlPaint paint;
62  auto effect =
63  MakeRuntimeEffect(this, "runtime_stage_example.frag.iplr", uniform_data);
64  ABSL_ASSERT_OK(effect);
65  paint.setColorSource(effect.value());
66 
67  DisplayListBuilder builder;
68  builder.Save();
69  builder.ClipRoundRect(
70  DlRoundRect::MakeRectXY(DlRect::MakeXYWH(0, 0, 400, 400), 10.0, 10.0),
71  DlClipOp::kIntersect);
72  builder.DrawRect(DlRect::MakeXYWH(0, 0, 400, 400), paint);
73  builder.Restore();
74 
75  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
76 }
77 
78 TEST_P(AiksTest, DrawPaintTransformsBounds) {
79  struct FragUniforms {
80  Size size;
81  } frag_uniforms = {.size = Size::MakeWH(400, 400)};
82  auto uniform_data = std::make_shared<std::vector<uint8_t>>();
83  uniform_data->resize(sizeof(FragUniforms));
84  memcpy(uniform_data->data(), &frag_uniforms, sizeof(FragUniforms));
85 
86  DlPaint paint;
87  auto effect = MakeRuntimeEffect(this, "gradient.frag.iplr", uniform_data);
88  ABSL_ASSERT_OK(effect);
89  paint.setColorSource(effect.value());
90 
91  DisplayListBuilder builder;
92  builder.Save();
93  builder.Scale(GetContentScale().x, GetContentScale().y);
94  builder.DrawPaint(paint);
95  builder.Restore();
96 
97  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
98 }
99 
100 TEST_P(AiksTest, CanRenderRuntimeEffectFilter) {
101  auto runtime_stages_result =
102  OpenAssetAsRuntimeStage("runtime_stage_filter_example.frag.iplr");
103  ABSL_ASSERT_OK(runtime_stages_result);
104  std::shared_ptr<RuntimeStage> runtime_stage =
105  runtime_stages_result
106  .value()[PlaygroundBackendToRuntimeStageBackend(GetBackend())];
107  ASSERT_TRUE(runtime_stage);
108  ASSERT_TRUE(runtime_stage->IsDirty());
109 
110  std::vector<std::shared_ptr<DlColorSource>> sampler_inputs = {
111  nullptr,
112  };
113  auto uniform_data = std::make_shared<std::vector<uint8_t>>();
114  uniform_data->resize(sizeof(Vector2));
115 
116  DlPaint paint;
117  paint.setColor(DlColor::kAqua());
118  paint.setImageFilter(DlImageFilter::MakeRuntimeEffect(
119  DlRuntimeEffectImpeller::Make(runtime_stage), sampler_inputs,
120  uniform_data));
121 
122  DisplayListBuilder builder;
123  builder.DrawRect(DlRect::MakeXYWH(0, 0, 400, 400), paint);
124 
125  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
126 }
127 
128 TEST_P(AiksTest, RuntimeEffectWithInvalidSamplerDoesNotCrash) {
129  ScopedValidationDisable disable_validation;
130 
131  // Create a sampler that is not usable as an input to the runtime effect.
132  std::vector<flutter::DlColor> colors = {flutter::DlColor::kBlue(),
133  flutter::DlColor::kRed()};
134  const float stops[2] = {0.0, 1.0};
135  auto linear = flutter::DlColorSource::MakeLinear({0.0, 0.0}, {300.0, 300.0},
136  2, colors.data(), stops,
137  flutter::DlTileMode::kClamp);
138  std::vector<std::shared_ptr<DlColorSource>> sampler_inputs = {
139  linear,
140  };
141 
142  auto uniform_data = std::make_shared<std::vector<uint8_t>>();
143  uniform_data->resize(sizeof(Vector2));
144 
145  DlPaint paint;
146  auto effect =
147  MakeRuntimeEffect(this, "runtime_stage_filter_example.frag.iplr",
148  uniform_data, sampler_inputs);
149  ABSL_ASSERT_OK(effect);
150  paint.setColorSource(effect.value());
151 
152  DisplayListBuilder builder;
153  builder.DrawPaint(paint);
154 
155  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
156 }
157 
158 TEST_P(AiksTest, ComposePaintRuntimeOuter) {
159  DisplayListBuilder builder;
160  DlPaint background;
161  background.setColor(DlColor(1.0, 0.1, 0.1, 0.1, DlColorSpace::kSRGB));
162  builder.DrawPaint(background);
163 
164  DlPaint paint;
165  paint.setColor(DlColor::kGreen());
166  float matrix[] = {
167  0, 1, 0, 0, 0, //
168  1, 0, 0, 0, 0, //
169  0, 0, 1, 0, 0, //
170  0, 0, 0, 1, 0 //
171  };
172  std::shared_ptr<DlImageFilter> color_filter =
173  DlImageFilter::MakeColorFilter(DlColorFilter::MakeMatrix(matrix));
174 
175  auto runtime_stages_result =
176  OpenAssetAsRuntimeStage("runtime_stage_filter_warp.frag.iplr");
177  ABSL_ASSERT_OK(runtime_stages_result);
178  std::shared_ptr<RuntimeStage> runtime_stage =
179  runtime_stages_result
180  .value()[PlaygroundBackendToRuntimeStageBackend(GetBackend())];
181  ASSERT_TRUE(runtime_stage);
182  ASSERT_TRUE(runtime_stage->IsDirty());
183 
184  std::vector<std::shared_ptr<DlColorSource>> sampler_inputs = {
185  nullptr,
186  };
187  auto uniform_data = std::make_shared<std::vector<uint8_t>>();
188  uniform_data->resize(sizeof(Vector2));
189 
190  auto runtime_filter = DlImageFilter::MakeRuntimeEffect(
191  DlRuntimeEffectImpeller::Make(runtime_stage), sampler_inputs,
192  uniform_data);
193 
194  builder.Translate(50, 50);
195  builder.Scale(0.7, 0.7);
196 
197  paint.setImageFilter(
198  DlImageFilter::MakeCompose(runtime_filter, color_filter));
199  auto image = DlImageImpeller::Make(CreateTextureForFixture("kalimba.jpg"));
200  builder.DrawImage(image, DlPoint(100.0, 100.0),
201  DlImageSampling::kNearestNeighbor, &paint);
202 
203  DlPaint green;
204  green.setColor(DlColor::kGreen());
205  builder.DrawLine({100, 100}, {200, 100}, green);
206  builder.DrawLine({100, 100}, {100, 200}, green);
207 
208  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
209 }
210 
211 TEST_P(AiksTest, ComposePaintRuntimeInner) {
212  auto runtime_stages_result =
213  OpenAssetAsRuntimeStage("runtime_stage_filter_warp.frag.iplr");
214  ABSL_ASSERT_OK(runtime_stages_result);
215  std::shared_ptr<RuntimeStage> runtime_stage =
216  runtime_stages_result
217  .value()[PlaygroundBackendToRuntimeStageBackend(GetBackend())];
218  ASSERT_TRUE(runtime_stage);
219  ASSERT_TRUE(runtime_stage->IsDirty());
220  Scalar xoffset = 50;
221  Scalar yoffset = 50;
222  Scalar xscale = 0.7;
223  Scalar yscale = 0.7;
224  bool compare = false;
225 
226  auto callback = [&]() -> sk_sp<DisplayList> {
227  if (AiksTest::ImGuiBegin("Controls", nullptr,
228  ImGuiWindowFlags_AlwaysAutoResize)) {
229  ImGui::SliderFloat("xoffset", &xoffset, -50, 50);
230  ImGui::SliderFloat("yoffset", &yoffset, -50, 50);
231  ImGui::SliderFloat("xscale", &xscale, 0, 1);
232  ImGui::SliderFloat("yscale", &yscale, 0, 1);
233  ImGui::Checkbox("compare", &compare);
234  ImGui::End();
235  }
236  DisplayListBuilder builder;
237  DlPaint background;
238  background.setColor(DlColor(1.0, 0.1, 0.1, 0.1, DlColorSpace::kSRGB));
239  builder.DrawPaint(background);
240 
241  DlPaint paint;
242  paint.setColor(DlColor::kGreen());
243  float matrix[] = {
244  0, 1, 0, 0, 0, //
245  1, 0, 0, 0, 0, //
246  0, 0, 1, 0, 0, //
247  0, 0, 0, 1, 0 //
248  };
249  std::shared_ptr<DlImageFilter> color_filter =
250  DlImageFilter::MakeColorFilter(DlColorFilter::MakeMatrix(matrix));
251 
252  std::vector<std::shared_ptr<DlColorSource>> sampler_inputs = {
253  nullptr,
254  };
255  auto uniform_data = std::make_shared<std::vector<uint8_t>>();
256  uniform_data->resize(sizeof(Vector2));
257 
258  auto runtime_filter = DlImageFilter::MakeRuntimeEffect(
259  DlRuntimeEffectImpeller::Make(runtime_stage), sampler_inputs,
260  uniform_data);
261 
262  builder.Translate(xoffset, yoffset);
263  builder.Scale(xscale, yscale);
264 
265  paint.setImageFilter(
266  DlImageFilter::MakeCompose(color_filter, runtime_filter));
267  auto image = DlImageImpeller::Make(CreateTextureForFixture("kalimba.jpg"));
268  builder.DrawImage(image, DlPoint(100.0, 100.0),
269  DlImageSampling::kNearestNeighbor, &paint);
270 
271  if (compare) {
272  paint.setImageFilter(
273  DlImageFilter::MakeCompose(runtime_filter, color_filter));
274  builder.DrawImage(image, DlPoint(800.0, 100.0),
275  DlImageSampling::kNearestNeighbor, &paint);
276 
277  paint.setImageFilter(runtime_filter);
278  builder.DrawImage(image, DlPoint(100.0, 800.0),
279  DlImageSampling::kNearestNeighbor, &paint);
280  }
281 
282  DlPaint green;
283  green.setColor(DlColor::kGreen());
284  builder.DrawLine({100, 100}, {200, 100}, green);
285  builder.DrawLine({100, 100}, {100, 200}, green);
286  if (compare) {
287  builder.DrawLine({800, 100}, {900, 100}, green);
288  builder.DrawLine({800, 100}, {800, 200}, green);
289  builder.DrawLine({100, 800}, {200, 800}, green);
290  builder.DrawLine({100, 800}, {100, 900}, green);
291  }
292 
293  return builder.Build();
294  };
295 
296  ASSERT_TRUE(OpenPlaygroundHere(callback));
297 }
298 
299 TEST_P(AiksTest, ComposeBackdropRuntimeOuterBlurInner) {
300  auto runtime_stages_result =
301  OpenAssetAsRuntimeStage("runtime_stage_filter_circle.frag.iplr");
302  ABSL_ASSERT_OK(runtime_stages_result);
303  std::shared_ptr<RuntimeStage> runtime_stage =
304  runtime_stages_result
305  .value()[PlaygroundBackendToRuntimeStageBackend(GetBackend())];
306  ASSERT_TRUE(runtime_stage);
307  ASSERT_TRUE(runtime_stage->IsDirty());
308  Scalar sigma = 20.0;
309 
310  auto callback = [&]() -> sk_sp<DisplayList> {
311  if (AiksTest::ImGuiBegin("Controls", nullptr,
312  ImGuiWindowFlags_AlwaysAutoResize)) {
313  ImGui::SliderFloat("sigma", &sigma, 0, 20);
314  ImGui::End();
315  }
316  DisplayListBuilder builder;
317  DlPaint background;
318  background.setColor(DlColor(1.0, 0.1, 0.1, 0.1, DlColorSpace::kSRGB));
319  builder.DrawPaint(background);
320 
321  auto blur_filter =
322  DlImageFilter::MakeBlur(sigma, sigma, DlTileMode::kClamp);
323 
324  std::vector<std::shared_ptr<DlColorSource>> sampler_inputs = {
325  nullptr,
326  };
327  auto uniform_data = std::make_shared<std::vector<uint8_t>>();
328  uniform_data->resize(sizeof(Vector2));
329 
330  auto runtime_filter = DlImageFilter::MakeRuntimeEffect(
331  DlRuntimeEffectImpeller::Make(runtime_stage), sampler_inputs,
332  uniform_data);
333 
334  auto backdrop_filter = DlImageFilter::MakeCompose(/*outer=*/runtime_filter,
335  /*inner=*/blur_filter);
336 
337  DlPaint paint;
338  auto image = DlImageImpeller::Make(CreateTextureForFixture("kalimba.jpg"));
339  builder.DrawImage(image, DlPoint(100.0, 100.0),
340  DlImageSampling::kNearestNeighbor, &paint);
341 
342  DlPaint save_paint;
343  save_paint.setBlendMode(DlBlendMode::kSrc);
344  builder.SaveLayer(std::nullopt, &save_paint, backdrop_filter.get());
345  builder.Restore();
346 
347  DlPaint green;
348  green.setColor(DlColor::kGreen());
349  builder.DrawLine({100, 100}, {200, 100}, green);
350  builder.DrawLine({100, 100}, {100, 200}, green);
351 
352  return builder.Build();
353  };
354 
355  ASSERT_TRUE(OpenPlaygroundHere(callback));
356 }
357 
358 TEST_P(AiksTest, ComposeBackdropRuntimeOuterBlurInnerSmallSigma) {
359  auto runtime_stages_result =
360  OpenAssetAsRuntimeStage("runtime_stage_filter_circle.frag.iplr");
361  ABSL_ASSERT_OK(runtime_stages_result);
362  std::shared_ptr<RuntimeStage> runtime_stage =
363  runtime_stages_result
364  .value()[PlaygroundBackendToRuntimeStageBackend(GetBackend())];
365  ASSERT_TRUE(runtime_stage);
366  ASSERT_TRUE(runtime_stage->IsDirty());
367  Scalar sigma = 5.0;
368 
369  auto callback = [&]() -> sk_sp<DisplayList> {
370  if (AiksTest::ImGuiBegin("Controls", nullptr,
371  ImGuiWindowFlags_AlwaysAutoResize)) {
372  ImGui::SliderFloat("sigma", &sigma, 0, 20);
373  ImGui::End();
374  }
375  DisplayListBuilder builder;
376  DlPaint background;
377  background.setColor(DlColor(1.0, 0.1, 0.1, 0.1, DlColorSpace::kSRGB));
378  builder.DrawPaint(background);
379 
380  auto blur_filter =
381  DlImageFilter::MakeBlur(sigma, sigma, DlTileMode::kClamp);
382 
383  std::vector<std::shared_ptr<DlColorSource>> sampler_inputs = {
384  nullptr,
385  };
386  auto uniform_data = std::make_shared<std::vector<uint8_t>>();
387  uniform_data->resize(sizeof(Vector2));
388 
389  auto runtime_filter = DlImageFilter::MakeRuntimeEffect(
390  DlRuntimeEffectImpeller::Make(runtime_stage), sampler_inputs,
391  uniform_data);
392 
393  auto backdrop_filter = DlImageFilter::MakeCompose(/*outer=*/runtime_filter,
394  /*inner=*/blur_filter);
395 
396  DlPaint paint;
397  auto image = DlImageImpeller::Make(CreateTextureForFixture("kalimba.jpg"));
398  builder.DrawImage(image, DlPoint(100.0, 100.0),
399  DlImageSampling::kNearestNeighbor, &paint);
400 
401  DlPaint save_paint;
402  save_paint.setBlendMode(DlBlendMode::kSrc);
403  builder.SaveLayer(std::nullopt, &save_paint, backdrop_filter.get());
404  builder.Restore();
405 
406  DlPaint green;
407  green.setColor(DlColor::kGreen());
408  builder.DrawLine({100, 100}, {200, 100}, green);
409  builder.DrawLine({100, 100}, {100, 200}, green);
410 
411  return builder.Build();
412  };
413 
414  ASSERT_TRUE(OpenPlaygroundHere(callback));
415 }
416 
417 TEST_P(AiksTest, ClippedBackdropFilterWithShader) {
418  struct FragUniforms {
419  Vector2 uSize;
420  } frag_uniforms = {.uSize = Vector2(400, 400)};
421  auto uniform_data = std::make_shared<std::vector<uint8_t>>();
422  uniform_data->resize(sizeof(FragUniforms));
423  memcpy(uniform_data->data(), &frag_uniforms, sizeof(FragUniforms));
424 
425  auto runtime_stages_result =
426  OpenAssetAsRuntimeStage("runtime_stage_border.frag.iplr");
427  ABSL_ASSERT_OK(runtime_stages_result);
428  std::shared_ptr<RuntimeStage> runtime_stage =
429  runtime_stages_result
430  .value()[PlaygroundBackendToRuntimeStageBackend(GetBackend())];
431  ASSERT_TRUE(runtime_stage);
432  ASSERT_TRUE(runtime_stage->IsDirty());
433 
434  std::vector<std::shared_ptr<DlColorSource>> sampler_inputs = {
435  nullptr,
436  };
437 
438  auto runtime_filter = DlImageFilter::MakeRuntimeEffect(
439  DlRuntimeEffectImpeller::Make(runtime_stage), sampler_inputs,
440  uniform_data);
441 
442  DisplayListBuilder builder;
443 
444  // Draw a background so the backdrop filter has something to affect
445  DlPaint background_paint;
446  background_paint.setColor(DlColor::kWhite());
447  builder.DrawPaint(background_paint);
448 
449  // Draw some pattern to verify the filter effect
450  DlPaint pattern_paint;
451  pattern_paint.setColor(DlColor::kRed());
452  builder.DrawRect(DlRect::MakeXYWH(0, 0, 200, 200), pattern_paint);
453  pattern_paint.setColor(DlColor::kBlue());
454  builder.DrawRect(DlRect::MakeXYWH(200, 200, 200, 200), pattern_paint);
455 
456  builder.Save();
457 
458  // Replicate the clip rect (inset by 66)
459  // Assuming a 400x400 screen, inset 66 gives roughly 66, 66, 268, 268
460  builder.ClipRect(DlRect::MakeXYWH(66, 66, 268, 268));
461 
462  DlPaint save_paint;
463  // The Flutter code uses a backdrop filter layer.
464  // In DisplayList, this corresponds to SaveLayer with a backdrop filter.
465  builder.SaveLayer(std::nullopt, &save_paint, runtime_filter.get());
466 
467  // The child was empty in the Flutter example, so we don't draw anything
468  // inside the SaveLayer
469 
470  builder.Restore(); // Restore SaveLayer
471  builder.Restore(); // Restore Save (Clip)
472 
473  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
474 }
475 
476 TEST_P(AiksTest, RuntimeEffectImageFilterRotated) {
477  auto image = DlImageImpeller::Make(CreateTextureForFixture("kalimba.jpg"));
478  auto size = image->GetBounds().GetSize();
479 
480  struct FragUniforms {
481  Size size;
482  } frag_uniforms = {.size = Size(size.width, size.height)};
483  auto uniform_data = std::make_shared<std::vector<uint8_t>>();
484  uniform_data->resize(sizeof(FragUniforms));
485  memcpy(uniform_data->data(), &frag_uniforms, sizeof(FragUniforms));
486 
487  auto runtime_stages_result = OpenAssetAsRuntimeStage("gradient.frag.iplr");
488  ABSL_ASSERT_OK(runtime_stages_result);
489  std::shared_ptr<RuntimeStage> runtime_stage =
490  runtime_stages_result
491  .value()[PlaygroundBackendToRuntimeStageBackend(GetBackend())];
492  ASSERT_TRUE(runtime_stage);
493  ASSERT_TRUE(runtime_stage->IsDirty());
494 
495  std::vector<std::shared_ptr<DlColorSource>> sampler_inputs = {
496  nullptr,
497  };
498 
499  auto runtime_filter = DlImageFilter::MakeRuntimeEffect(
500  DlRuntimeEffectImpeller::Make(runtime_stage), sampler_inputs,
501  uniform_data);
502 
503  Scalar rotation = 45;
504 
505  auto callback = [&]() -> sk_sp<DisplayList> {
506  if (AiksTest::ImGuiBegin("Controls", nullptr,
507  ImGuiWindowFlags_AlwaysAutoResize)) {
508  ImGui::SliderFloat("rotation", &rotation, 0, 360);
509  ImGui::End();
510  }
511  DisplayListBuilder builder;
512  builder.Translate(size.width * 0.5, size.height * 0.5);
513  builder.Rotate(rotation);
514  builder.Translate(-size.width * 0.5, -size.height * 0.5);
515 
516  DlPaint paint;
517  paint.setImageFilter(runtime_filter);
518  builder.DrawImage(image, DlPoint(0.0, 0.0),
519  DlImageSampling::kNearestNeighbor, &paint);
520 
521  return builder.Build();
522  };
523 
524  ASSERT_TRUE(OpenPlaygroundHere(callback));
525 }
526 
527 } // namespace testing
528 } // namespace impeller
static sk_sp< DlRuntimeEffect > Make(std::shared_ptr< impeller::RuntimeStage > runtime_stage)
static bool ImGuiBegin(const char *name, bool *p_open, ImGuiWindowFlags flags)
static sk_sp< DlImageImpeller > Make(std::shared_ptr< Texture > texture, OwningContext owning_context=OwningContext::kIO)
int32_t x
AiksPlayground AiksTest
TEST_P(AiksTest, DrawAtlasNoColor)
Point Vector2
Definition: point.h:429
float Scalar
Definition: scalar.h:19
constexpr RuntimeStageBackend PlaygroundBackendToRuntimeStageBackend(PlaygroundBackend backend)
Definition: playground.h:33
flutter::DlPoint DlPoint
Definition: dl_dispatcher.h:24
TSize< Scalar > Size
Definition: size.h:159
static constexpr TSize MakeWH(Type width, Type height)
Definition: size.h:43