Flutter Impeller
canvas_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 <unordered_map>
6 
7 #include "flutter/display_list/dl_tile_mode.h"
8 #include "flutter/display_list/effects/dl_image_filter.h"
9 #include "flutter/display_list/geometry/dl_geometry_types.h"
10 #include "flutter/testing/testing.h"
11 #include "gtest/gtest.h"
12 #include "impeller/core/formats.h"
22 #include "third_party/abseil-cpp/absl/status/status_matchers.h"
23 
24 namespace impeller {
25 namespace testing {
26 
27 std::unique_ptr<Canvas> CreateTestCanvas(
28  ContentContext& context,
29  std::optional<Rect> cull_rect = std::nullopt,
30  bool requires_readback = false) {
31  TextureDescriptor onscreen_desc;
32  onscreen_desc.size = {100, 100};
33  onscreen_desc.format =
35  onscreen_desc.usage = TextureUsage::kRenderTarget;
37  onscreen_desc.sample_count = SampleCount::kCount1;
38  std::shared_ptr<Texture> onscreen =
39  context.GetContext()->GetResourceAllocator()->CreateTexture(
40  onscreen_desc);
41 
42  ColorAttachment color0;
44  if (context.GetContext()->GetCapabilities()->SupportsOffscreenMSAA()) {
45  TextureDescriptor onscreen_msaa_desc = onscreen_desc;
46  onscreen_msaa_desc.sample_count = SampleCount::kCount4;
47  onscreen_msaa_desc.storage_mode = StorageMode::kDeviceTransient;
48  onscreen_msaa_desc.type = TextureType::kTexture2DMultisample;
49 
50  std::shared_ptr<Texture> onscreen_msaa =
51  context.GetContext()->GetResourceAllocator()->CreateTexture(
52  onscreen_msaa_desc);
53  color0.resolve_texture = onscreen;
54  color0.texture = onscreen_msaa;
56  } else {
57  color0.texture = onscreen;
58  }
59 
60  RenderTarget render_target;
61  render_target.SetColorAttachment(color0, 0);
62 
63  if (cull_rect.has_value()) {
64  return std::make_unique<Canvas>(
65  context, render_target, /*is_onscreen=*/false,
66  /*requires_readback=*/requires_readback, cull_rect.value());
67  }
68  return std::make_unique<Canvas>(context, render_target, /*is_onscreen=*/false,
69  /*requires_readback=*/requires_readback);
70 }
71 
72 TEST_P(AiksTest, TransformMultipliesCorrectly) {
73  ContentContext context(GetContext(), nullptr);
74  auto canvas = CreateTestCanvas(context);
75 
76  ASSERT_MATRIX_NEAR(canvas->GetCurrentTransform(), Matrix());
77 
78  // clang-format off
79  canvas->Translate(Vector3(100, 200));
81  canvas->GetCurrentTransform(),
82  Matrix( 1, 0, 0, 0,
83  0, 1, 0, 0,
84  0, 0, 1, 0,
85  100, 200, 0, 1));
86 
87  canvas->Rotate(Radians(kPiOver2));
89  canvas->GetCurrentTransform(),
90  Matrix( 0, 1, 0, 0,
91  -1, 0, 0, 0,
92  0, 0, 1, 0,
93  100, 200, 0, 1));
94 
95  canvas->Scale(Vector3(2, 3));
97  canvas->GetCurrentTransform(),
98  Matrix( 0, 2, 0, 0,
99  -3, 0, 0, 0,
100  0, 0, 0, 0,
101  100, 200, 0, 1));
102 
103  canvas->Translate(Vector3(100, 200));
105  canvas->GetCurrentTransform(),
106  Matrix( 0, 2, 0, 0,
107  -3, 0, 0, 0,
108  0, 0, 0, 0,
109  -500, 400, 0, 1));
110  // clang-format on
111 }
112 
113 TEST_P(AiksTest, CanvasCanPushPopCTM) {
114  ContentContext context(GetContext(), nullptr);
115  auto canvas = CreateTestCanvas(context);
116 
117  ASSERT_EQ(canvas->GetSaveCount(), 1u);
118  ASSERT_EQ(canvas->Restore(), false);
119 
120  canvas->Translate(Size{100, 100});
121  canvas->Save(10);
122  ASSERT_EQ(canvas->GetSaveCount(), 2u);
123  ASSERT_MATRIX_NEAR(canvas->GetCurrentTransform(),
124  Matrix::MakeTranslation({100.0, 100.0, 0.0}));
125  ASSERT_TRUE(canvas->Restore());
126  ASSERT_EQ(canvas->GetSaveCount(), 1u);
127  ASSERT_MATRIX_NEAR(canvas->GetCurrentTransform(),
128  Matrix::MakeTranslation({100.0, 100.0, 0.0}));
129 }
130 
131 TEST_P(AiksTest, CanvasCTMCanBeUpdated) {
132  ContentContext context(GetContext(), nullptr);
133  auto canvas = CreateTestCanvas(context);
134 
135  Matrix identity;
136  ASSERT_MATRIX_NEAR(canvas->GetCurrentTransform(), identity);
137  canvas->Translate(Size{100, 100});
138  ASSERT_MATRIX_NEAR(canvas->GetCurrentTransform(),
139  Matrix::MakeTranslation({100.0, 100.0, 0.0}));
140 }
141 
142 TEST_P(AiksTest, BackdropCountDownNormal) {
143  ContentContext context(GetContext(), nullptr);
145  GTEST_SKIP() << "Test requires device with framebuffer fetch";
146  }
147  auto canvas = CreateTestCanvas(context, Rect::MakeLTRB(0, 0, 100, 100),
148  /*requires_readback=*/true);
149  // 3 backdrop filters
150  canvas->SetBackdropData({}, 3);
151 
152  auto blur =
153  flutter::DlImageFilter::MakeBlur(4, 4, flutter::DlTileMode::kClamp);
154  flutter::DlRect rect = flutter::DlRect::MakeLTRB(0, 0, 50, 50);
155 
156  EXPECT_TRUE(canvas->RequiresReadback());
157  canvas->DrawRect(rect, {.color = Color::Azure()});
158  canvas->SaveLayer({}, rect, blur.get(),
160  /*total_content_depth=*/1);
161  canvas->Restore();
162  EXPECT_TRUE(canvas->RequiresReadback());
163 
164  canvas->SaveLayer({}, rect, blur.get(),
166  /*total_content_depth=*/1);
167  canvas->Restore();
168  EXPECT_TRUE(canvas->RequiresReadback());
169 
170  canvas->SaveLayer({}, rect, blur.get(),
172  /*total_content_depth=*/1);
173  canvas->Restore();
174  EXPECT_FALSE(canvas->RequiresReadback());
175 }
176 
177 TEST_P(AiksTest, BackdropCountDownBackdropId) {
178  ContentContext context(GetContext(), nullptr);
180  GTEST_SKIP() << "Test requires device with framebuffer fetch";
181  }
182  auto canvas = CreateTestCanvas(context, Rect::MakeLTRB(0, 0, 100, 100),
183  /*requires_readback=*/true);
184  // 3 backdrop filters all with same id.
185  std::unordered_map<int64_t, BackdropData> data;
186  data[1] = BackdropData{.backdrop_count = 3};
187  canvas->SetBackdropData(data, 3);
188 
189  auto blur =
190  flutter::DlImageFilter::MakeBlur(4, 4, flutter::DlTileMode::kClamp);
191 
192  EXPECT_TRUE(canvas->RequiresReadback());
193  canvas->DrawRect(flutter::DlRect::MakeLTRB(0, 0, 50, 50),
194  {.color = Color::Azure()});
195  canvas->SaveLayer({}, std::nullopt, blur.get(),
197  /*total_content_depth=*/1, /*can_distribute_opacity=*/false,
198  /*backdrop_id=*/1);
199  canvas->Restore();
200  EXPECT_FALSE(canvas->RequiresReadback());
201 
202  canvas->SaveLayer({}, std::nullopt, blur.get(),
204  /*total_content_depth=*/1, /*can_distribute_opacity=*/false,
205  /*backdrop_id=*/1);
206  canvas->Restore();
207  EXPECT_FALSE(canvas->RequiresReadback());
208 
209  canvas->SaveLayer({}, std::nullopt, blur.get(),
211  /*total_content_depth=*/1, /*can_distribute_opacity=*/false,
212  /*backdrop_id=*/1);
213  canvas->Restore();
214  EXPECT_FALSE(canvas->RequiresReadback());
215 }
216 
217 TEST_P(AiksTest, BackdropCountDownBackdropIdMixed) {
218  ContentContext context(GetContext(), nullptr);
220  GTEST_SKIP() << "Test requires device with framebuffer fetch";
221  }
222  auto canvas = CreateTestCanvas(context, Rect::MakeLTRB(0, 0, 100, 100),
223  /*requires_readback=*/true);
224  // 3 backdrop filters, 2 with same id.
225  std::unordered_map<int64_t, BackdropData> data;
226  data[1] = BackdropData{.backdrop_count = 2};
227  canvas->SetBackdropData(data, 3);
228 
229  auto blur =
230  flutter::DlImageFilter::MakeBlur(4, 4, flutter::DlTileMode::kClamp);
231 
232  EXPECT_TRUE(canvas->RequiresReadback());
233  canvas->DrawRect(flutter::DlRect::MakeLTRB(0, 0, 50, 50),
234  {.color = Color::Azure()});
235  canvas->SaveLayer({}, std::nullopt, blur.get(),
237  canvas->Restore();
238  EXPECT_TRUE(canvas->RequiresReadback());
239 
240  canvas->SaveLayer({}, std::nullopt, blur.get(),
242  canvas->Restore();
243  EXPECT_FALSE(canvas->RequiresReadback());
244 
245  canvas->SaveLayer({}, std::nullopt, blur.get(),
247  canvas->Restore();
248  EXPECT_FALSE(canvas->RequiresReadback());
249 }
250 
251 // We only know the total number of backdrop filters, not the number of backdrop
252 // filters in the root pass. If we reach a count of 0 while in a nested
253 // saveLayer, we should not restore to the onscreen.
254 TEST_P(AiksTest, BackdropCountDownWithNestedSaveLayers) {
255  ContentContext context(GetContext(), nullptr);
257  GTEST_SKIP() << "Test requires device with framebuffer fetch";
258  }
259  auto canvas = CreateTestCanvas(context, Rect::MakeLTRB(0, 0, 100, 100),
260  /*requires_readback=*/true);
261 
262  canvas->SetBackdropData({}, 2);
263 
264  auto blur =
265  flutter::DlImageFilter::MakeBlur(4, 4, flutter::DlTileMode::kClamp);
266 
267  EXPECT_TRUE(canvas->RequiresReadback());
268  canvas->DrawRect(flutter::DlRect::MakeLTRB(0, 0, 50, 50),
269  {.color = Color::Azure()});
270  canvas->SaveLayer({}, std::nullopt, blur.get(),
272  /*total_content_depth=*/3);
273 
274  // This filter is nested in the first saveLayer. We cannot restore to onscreen
275  // here.
276  canvas->SaveLayer({}, std::nullopt, blur.get(),
278  /*total_content_depth=*/1);
279  canvas->Restore();
280  EXPECT_TRUE(canvas->RequiresReadback());
281 
282  canvas->Restore();
283  EXPECT_TRUE(canvas->RequiresReadback());
284 }
285 
286 TEST_P(AiksTest, DrawVerticesLinearGradientWithEmptySize) {
287  RenderCallback callback = [&](RenderTarget& render_target) {
288  ContentContext context(GetContext(), nullptr);
289  Canvas canvas(context, render_target, true, false);
290 
291  std::vector<flutter::DlPoint> vertex_coordinates = {
292  flutter::DlPoint(0, 0),
293  flutter::DlPoint(600, 0),
294  flutter::DlPoint(0, 600),
295  };
296  std::vector<flutter::DlPoint> texture_coordinates = {
297  flutter::DlPoint(0, 0),
298  flutter::DlPoint(500, 0),
299  flutter::DlPoint(0, 500),
300  };
301  std::vector<uint16_t> indices = {0, 1, 2};
302  flutter::DlVertices::Builder vertices_builder(
303  flutter::DlVertexMode::kTriangleStrip, vertex_coordinates.size(),
304  flutter::DlVertices::Builder::kHasTextureCoordinates, indices.size());
305  vertices_builder.store_vertices(vertex_coordinates.data());
306  vertices_builder.store_indices(indices.data());
307  vertices_builder.store_texture_coordinates(texture_coordinates.data());
308  auto vertices = vertices_builder.build();
309 
310  // The start and end points of the gradient form an empty rectangle.
311  std::vector<flutter::DlColor> colors = {flutter::DlColor::kBlue(),
312  flutter::DlColor::kRed()};
313  std::vector<Scalar> stops = {0.0, 1.0};
314  auto gradient = flutter::DlColorSource::MakeLinear(
315  {0, 0}, {0, 600}, 2, colors.data(), stops.data(),
316  flutter::DlTileMode::kClamp);
317 
318  Paint paint;
319  paint.color_source = gradient.get();
320  canvas.DrawVertices(std::make_shared<DlVerticesGeometry>(vertices, context),
321  BlendMode::kSrcOver, paint);
322 
323  canvas.EndReplay();
324  return true;
325  };
326 
327  ASSERT_TRUE(Playground::OpenPlaygroundHere(callback));
328 }
329 
330 TEST_P(AiksTest, DrawVerticesWithEmptyTextureCoordinates) {
331  auto runtime_stages_result =
332  OpenAssetAsRuntimeStage("runtime_stage_simple.frag.iplr");
333  ABSL_ASSERT_OK(runtime_stages_result);
334  std::shared_ptr<RuntimeStage> runtime_stage =
335  runtime_stages_result
336  .value()[PlaygroundBackendToRuntimeStageBackend(GetBackend())];
337  ASSERT_TRUE(runtime_stage);
338 
339  auto runtime_effect = flutter::DlRuntimeEffectImpeller::Make(runtime_stage);
340  auto uniform_data = std::make_shared<std::vector<uint8_t>>();
341  auto color_source = flutter::DlColorSource::MakeRuntimeEffect(
342  runtime_effect, {}, uniform_data);
343 
344  RenderCallback callback = [&](RenderTarget& render_target) {
345  ContentContext context(GetContext(), nullptr);
346  Canvas canvas(context, render_target, true, false);
347 
348  std::vector<flutter::DlPoint> vertex_coordinates = {
349  flutter::DlPoint(100, 100),
350  flutter::DlPoint(300, 100),
351  flutter::DlPoint(100, 300),
352  };
353  // The bounding box of the texture coordinates is empty.
354  std::vector<flutter::DlPoint> texture_coordinates = {
355  flutter::DlPoint(0, 0),
356  flutter::DlPoint(0, 100),
357  flutter::DlPoint(0, 0),
358  };
359  std::vector<uint16_t> indices = {0, 1, 2};
360  flutter::DlVertices::Builder vertices_builder(
361  flutter::DlVertexMode::kTriangleStrip, vertex_coordinates.size(),
362  flutter::DlVertices::Builder::kHasTextureCoordinates, indices.size());
363  vertices_builder.store_vertices(vertex_coordinates.data());
364  vertices_builder.store_indices(indices.data());
365  vertices_builder.store_texture_coordinates(texture_coordinates.data());
366  auto vertices = vertices_builder.build();
367 
368  Paint paint;
369  paint.color_source = color_source.get();
370  canvas.DrawVertices(std::make_shared<DlVerticesGeometry>(vertices, context),
371  BlendMode::kSrcOver, paint);
372 
373  canvas.EndReplay();
374  return true;
375  };
376 
377  ASSERT_TRUE(Playground::OpenPlaygroundHere(callback));
378 }
379 
380 TEST_P(AiksTest, SupportsBlitToOnscreen) {
381  ContentContext context(GetContext(), nullptr);
382  auto canvas = CreateTestCanvas(context, Rect::MakeLTRB(0, 0, 100, 100),
383  /*requires_readback=*/true);
384 
385  if (GetBackend() != PlaygroundBackend::kMetal) {
386  EXPECT_FALSE(canvas->SupportsBlitToOnscreen());
387  } else {
388  EXPECT_TRUE(canvas->SupportsBlitToOnscreen());
389  }
390 }
391 
392 TEST_P(AiksTest, RoundSuperellipseShadowComparison) {
393  // Config
394  Size default_size(600, 400);
395  Point left_center(400, 700);
396  Point right_center(1300, 700);
397  Color color = Color::Red();
398 
399  // Convert `color` to a `color_source`. This forces
400  // `canvas.DrawRoundSuperellipse` to use the regular shadow algorithm
401  // (blurring) instead of the fast shadow algorithm.
402  std::shared_ptr<flutter::DlColorSource> color_source;
403  {
404  flutter::DlColor dl_color = flutter::DlColor(color.ToARGB());
405  std::vector<flutter::DlColor> colors = {dl_color, dl_color};
406  std::vector<Scalar> stops = {0.0, 1.0};
407  color_source = flutter::DlColorSource::MakeLinear(
408  {0, 0}, {1000, 1000}, 2, colors.data(), stops.data(),
409  flutter::DlTileMode::kClamp);
410  }
411 
412  auto RectMakeCenterHalfSize = [](Point center, Point half_size) {
413  Size size(half_size.x * 2, half_size.y * 2);
414  return Rect::MakeOriginSize(center - half_size, size);
415  };
416 
417  RenderCallback callback = [&](RenderTarget& render_target) {
418  ContentContext context(GetContext(), nullptr);
419  Canvas canvas(context, render_target, true, false);
420  // Somehow there's a scaling factor between PlaygroundPoint and Canvas.
421  Matrix ctm = Matrix::MakeScale(Vector2(1, 1) * 0.5);
422  Matrix i_ctm = ctm.Invert();
423 
424  static Scalar sigma = 0.05;
425  static Scalar radius = 200;
426 
427  // Define the ImGui
428  ImGui::Begin("Shadow", nullptr, ImGuiWindowFlags_AlwaysAutoResize);
429  {
430  ImGui::SliderFloat("Sigma", &sigma, 0, 100);
431  ImGui::SliderFloat("Radius", &radius, 0, 1000);
432  }
433  ImGui::End();
434 
435  static PlaygroundPoint right_reference_var(
436  ctm * (right_center + default_size / 2), 30, Color::White());
437  Point right_reference = i_ctm * DrawPlaygroundPoint(right_reference_var);
438  Point half_size = (right_reference - right_center).Abs();
439  Rect left_bounds = RectMakeCenterHalfSize(left_center, half_size);
440  Rect right_bounds = RectMakeCenterHalfSize(right_center, half_size);
441 
442  Paint paint{
443  .color = color,
444  .mask_blur_descriptor =
446  .sigma = Sigma(sigma),
447  },
448  };
449 
450  // Left: Draw with canvas
451  canvas.DrawRoundSuperellipse(
452  RoundSuperellipse::MakeRectRadius(left_bounds, radius), paint);
453 
454  // Right: Direct draw
455  paint.color_source = color_source.get();
456  canvas.DrawRoundSuperellipse(
457  RoundSuperellipse::MakeRectRadius(right_bounds, radius), paint);
458 
459  canvas.EndReplay();
460  return true;
461  };
462 
463  ASSERT_TRUE(Playground::OpenPlaygroundHere(callback));
464 }
465 
466 } // namespace testing
467 } // namespace impeller
static sk_sp< DlRuntimeEffect > Make(std::shared_ptr< impeller::RuntimeStage > runtime_stage)
void DrawRoundSuperellipse(const RoundSuperellipse &rse, const Paint &paint)
Definition: canvas.cc:973
void DrawVertices(const std::shared_ptr< VerticesGeometry > &vertices, BlendMode blend_mode, const Paint &paint)
Definition: canvas.cc:1203
void EndReplay()
Definition: canvas.cc:2291
virtual bool SupportsFramebufferFetch() const =0
Whether the context backend is able to support pipelines with shaders that read from the framebuffer ...
virtual PixelFormat GetDefaultColorFormat() const =0
Returns a supported PixelFormat for textures that store 4-channel colors (red/green/blue/alpha).
const Capabilities & GetDeviceCapabilities() const
std::shared_ptr< Context > GetContext() const
bool OpenPlaygroundHere(const RenderCallback &render_callback)
Definition: playground.cc:201
RenderTarget & SetColorAttachment(const ColorAttachment &attachment, size_t index)
#define ASSERT_MATRIX_NEAR(a, b)
std::unique_ptr< Canvas > CreateTestCanvas(ContentContext &context, std::optional< Rect > cull_rect=std::nullopt, bool requires_readback=false)
TEST_P(AiksTest, DrawAtlasNoColor)
Point Vector2
Definition: point.h:429
flutter::DlRect DlRect
Definition: dl_dispatcher.h:25
float Scalar
Definition: scalar.h:19
Point DrawPlaygroundPoint(PlaygroundPoint &point)
Definition: widgets.cc:11
constexpr RuntimeStageBackend PlaygroundBackendToRuntimeStageBackend(PlaygroundBackend backend)
Definition: playground.h:33
flutter::DlPoint DlPoint
Definition: dl_dispatcher.h:24
constexpr float kPiOver2
Definition: constants.h:32
@ kContainsContents
The caller claims the bounds are a reasonably tight estimate of the coverage of the contents and shou...
std::shared_ptr< Texture > resolve_texture
Definition: formats.h:662
LoadAction load_action
Definition: formats.h:663
std::shared_ptr< Texture > texture
Definition: formats.h:661
StoreAction store_action
Definition: formats.h:664
size_t backdrop_count
Definition: canvas.h:41
uint32_t ToARGB() const
Convert to ARGB 32 bit color.
Definition: color.h:259
static constexpr Color Azure()
Definition: color.h:298
static constexpr Color White()
Definition: color.h:264
static constexpr Color Red()
Definition: color.h:272
A 4x4 matrix using column-major storage.
Definition: matrix.h:37
static constexpr Matrix MakeTranslation(const Vector3 &t)
Definition: matrix.h:95
Matrix Invert() const
Definition: matrix.cc:99
static constexpr Matrix MakeScale(const Vector3 &s)
Definition: matrix.h:104
const flutter::DlColorSource * color_source
Definition: paint.h:76
Color color
Definition: paint.h:75
static RoundSuperellipse MakeRectRadius(const Rect &rect, Scalar radius)
In filters that use Gaussian distributions, "sigma" is a size of one standard deviation in terms of t...
Definition: sigma.h:32
constexpr static TRect MakeOriginSize(const TPoint< Type > &origin, const TSize< Type > &size)
Definition: rect.h:144
constexpr static TRect MakeLTRB(Type left, Type top, Type right, Type bottom)
Definition: rect.h:129
A lightweight object that describes the attributes of a texture that can then used an allocator to cr...
std::shared_ptr< const fml::Mapping > data
Definition: texture_gles.cc:69