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