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