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"
20 
21 namespace impeller {
22 namespace testing {
23 
24 std::unique_ptr<Canvas> CreateTestCanvas(
25  ContentContext& context,
26  std::optional<Rect> cull_rect = std::nullopt,
27  bool requires_readback = false) {
28  TextureDescriptor onscreen_desc;
29  onscreen_desc.size = {100, 100};
30  onscreen_desc.format =
32  onscreen_desc.usage = TextureUsage::kRenderTarget;
34  onscreen_desc.sample_count = SampleCount::kCount1;
35  std::shared_ptr<Texture> onscreen =
36  context.GetContext()->GetResourceAllocator()->CreateTexture(
37  onscreen_desc);
38 
39  ColorAttachment color0;
41  if (context.GetContext()->GetCapabilities()->SupportsOffscreenMSAA()) {
42  TextureDescriptor onscreen_msaa_desc = onscreen_desc;
43  onscreen_msaa_desc.sample_count = SampleCount::kCount4;
44  onscreen_msaa_desc.storage_mode = StorageMode::kDeviceTransient;
45  onscreen_msaa_desc.type = TextureType::kTexture2DMultisample;
46 
47  std::shared_ptr<Texture> onscreen_msaa =
48  context.GetContext()->GetResourceAllocator()->CreateTexture(
49  onscreen_msaa_desc);
50  color0.resolve_texture = onscreen;
51  color0.texture = onscreen_msaa;
53  } else {
54  color0.texture = onscreen;
55  }
56 
57  RenderTarget render_target;
58  render_target.SetColorAttachment(color0, 0);
59 
60  if (cull_rect.has_value()) {
61  return std::make_unique<Canvas>(
62  context, render_target, /*is_onscreen=*/false,
63  /*requires_readback=*/requires_readback, cull_rect.value());
64  }
65  return std::make_unique<Canvas>(context, render_target, /*is_onscreen=*/false,
66  /*requires_readback=*/requires_readback);
67 }
68 
69 TEST_P(AiksTest, TransformMultipliesCorrectly) {
70  ContentContext context(GetContext(), nullptr);
71  auto canvas = CreateTestCanvas(context);
72 
73  ASSERT_MATRIX_NEAR(canvas->GetCurrentTransform(), Matrix());
74 
75  // clang-format off
76  canvas->Translate(Vector3(100, 200));
78  canvas->GetCurrentTransform(),
79  Matrix( 1, 0, 0, 0,
80  0, 1, 0, 0,
81  0, 0, 1, 0,
82  100, 200, 0, 1));
83 
84  canvas->Rotate(Radians(kPiOver2));
86  canvas->GetCurrentTransform(),
87  Matrix( 0, 1, 0, 0,
88  -1, 0, 0, 0,
89  0, 0, 1, 0,
90  100, 200, 0, 1));
91 
92  canvas->Scale(Vector3(2, 3));
94  canvas->GetCurrentTransform(),
95  Matrix( 0, 2, 0, 0,
96  -3, 0, 0, 0,
97  0, 0, 0, 0,
98  100, 200, 0, 1));
99 
100  canvas->Translate(Vector3(100, 200));
102  canvas->GetCurrentTransform(),
103  Matrix( 0, 2, 0, 0,
104  -3, 0, 0, 0,
105  0, 0, 0, 0,
106  -500, 400, 0, 1));
107  // clang-format on
108 }
109 
110 TEST_P(AiksTest, CanvasCanPushPopCTM) {
111  ContentContext context(GetContext(), nullptr);
112  auto canvas = CreateTestCanvas(context);
113 
114  ASSERT_EQ(canvas->GetSaveCount(), 1u);
115  ASSERT_EQ(canvas->Restore(), false);
116 
117  canvas->Translate(Size{100, 100});
118  canvas->Save(10);
119  ASSERT_EQ(canvas->GetSaveCount(), 2u);
120  ASSERT_MATRIX_NEAR(canvas->GetCurrentTransform(),
121  Matrix::MakeTranslation({100.0, 100.0, 0.0}));
122  ASSERT_TRUE(canvas->Restore());
123  ASSERT_EQ(canvas->GetSaveCount(), 1u);
124  ASSERT_MATRIX_NEAR(canvas->GetCurrentTransform(),
125  Matrix::MakeTranslation({100.0, 100.0, 0.0}));
126 }
127 
128 TEST_P(AiksTest, CanvasCTMCanBeUpdated) {
129  ContentContext context(GetContext(), nullptr);
130  auto canvas = CreateTestCanvas(context);
131 
132  Matrix identity;
133  ASSERT_MATRIX_NEAR(canvas->GetCurrentTransform(), identity);
134  canvas->Translate(Size{100, 100});
135  ASSERT_MATRIX_NEAR(canvas->GetCurrentTransform(),
136  Matrix::MakeTranslation({100.0, 100.0, 0.0}));
137 }
138 
139 TEST_P(AiksTest, BackdropCountDownNormal) {
140  ContentContext context(GetContext(), nullptr);
142  GTEST_SKIP() << "Test requires device with framebuffer fetch";
143  }
144  auto canvas = CreateTestCanvas(context, Rect::MakeLTRB(0, 0, 100, 100),
145  /*requires_readback=*/true);
146  // 3 backdrop filters
147  canvas->SetBackdropData({}, 3);
148 
149  auto blur =
150  flutter::DlImageFilter::MakeBlur(4, 4, flutter::DlTileMode::kClamp);
151  flutter::DlRect rect = flutter::DlRect::MakeLTRB(0, 0, 50, 50);
152 
153  EXPECT_TRUE(canvas->RequiresReadback());
154  canvas->DrawRect(rect, {.color = Color::Azure()});
155  canvas->SaveLayer({}, rect, blur.get(),
157  /*total_content_depth=*/1);
158  canvas->Restore();
159  EXPECT_TRUE(canvas->RequiresReadback());
160 
161  canvas->SaveLayer({}, rect, blur.get(),
163  /*total_content_depth=*/1);
164  canvas->Restore();
165  EXPECT_TRUE(canvas->RequiresReadback());
166 
167  canvas->SaveLayer({}, rect, blur.get(),
169  /*total_content_depth=*/1);
170  canvas->Restore();
171  EXPECT_FALSE(canvas->RequiresReadback());
172 }
173 
174 TEST_P(AiksTest, BackdropCountDownBackdropId) {
175  ContentContext context(GetContext(), nullptr);
177  GTEST_SKIP() << "Test requires device with framebuffer fetch";
178  }
179  auto canvas = CreateTestCanvas(context, Rect::MakeLTRB(0, 0, 100, 100),
180  /*requires_readback=*/true);
181  // 3 backdrop filters all with same id.
182  std::unordered_map<int64_t, BackdropData> data;
183  data[1] = BackdropData{.backdrop_count = 3};
184  canvas->SetBackdropData(data, 3);
185 
186  auto blur =
187  flutter::DlImageFilter::MakeBlur(4, 4, flutter::DlTileMode::kClamp);
188 
189  EXPECT_TRUE(canvas->RequiresReadback());
190  canvas->DrawRect(flutter::DlRect::MakeLTRB(0, 0, 50, 50),
191  {.color = Color::Azure()});
192  canvas->SaveLayer({}, std::nullopt, blur.get(),
194  /*total_content_depth=*/1, /*can_distribute_opacity=*/false,
195  /*backdrop_id=*/1);
196  canvas->Restore();
197  EXPECT_FALSE(canvas->RequiresReadback());
198 
199  canvas->SaveLayer({}, std::nullopt, blur.get(),
201  /*total_content_depth=*/1, /*can_distribute_opacity=*/false,
202  /*backdrop_id=*/1);
203  canvas->Restore();
204  EXPECT_FALSE(canvas->RequiresReadback());
205 
206  canvas->SaveLayer({}, std::nullopt, blur.get(),
208  /*total_content_depth=*/1, /*can_distribute_opacity=*/false,
209  /*backdrop_id=*/1);
210  canvas->Restore();
211  EXPECT_FALSE(canvas->RequiresReadback());
212 }
213 
214 TEST_P(AiksTest, BackdropCountDownBackdropIdMixed) {
215  ContentContext context(GetContext(), nullptr);
217  GTEST_SKIP() << "Test requires device with framebuffer fetch";
218  }
219  auto canvas = CreateTestCanvas(context, Rect::MakeLTRB(0, 0, 100, 100),
220  /*requires_readback=*/true);
221  // 3 backdrop filters, 2 with same id.
222  std::unordered_map<int64_t, BackdropData> data;
223  data[1] = BackdropData{.backdrop_count = 2};
224  canvas->SetBackdropData(data, 3);
225 
226  auto blur =
227  flutter::DlImageFilter::MakeBlur(4, 4, flutter::DlTileMode::kClamp);
228 
229  EXPECT_TRUE(canvas->RequiresReadback());
230  canvas->DrawRect(flutter::DlRect::MakeLTRB(0, 0, 50, 50),
231  {.color = Color::Azure()});
232  canvas->SaveLayer({}, std::nullopt, blur.get(),
234  canvas->Restore();
235  EXPECT_TRUE(canvas->RequiresReadback());
236 
237  canvas->SaveLayer({}, std::nullopt, blur.get(),
239  canvas->Restore();
240  EXPECT_FALSE(canvas->RequiresReadback());
241 
242  canvas->SaveLayer({}, std::nullopt, blur.get(),
244  canvas->Restore();
245  EXPECT_FALSE(canvas->RequiresReadback());
246 }
247 
248 // We only know the total number of backdrop filters, not the number of backdrop
249 // filters in the root pass. If we reach a count of 0 while in a nested
250 // saveLayer, we should not restore to the onscreen.
251 TEST_P(AiksTest, BackdropCountDownWithNestedSaveLayers) {
252  ContentContext context(GetContext(), nullptr);
254  GTEST_SKIP() << "Test requires device with framebuffer fetch";
255  }
256  auto canvas = CreateTestCanvas(context, Rect::MakeLTRB(0, 0, 100, 100),
257  /*requires_readback=*/true);
258 
259  canvas->SetBackdropData({}, 2);
260 
261  auto blur =
262  flutter::DlImageFilter::MakeBlur(4, 4, flutter::DlTileMode::kClamp);
263 
264  EXPECT_TRUE(canvas->RequiresReadback());
265  canvas->DrawRect(flutter::DlRect::MakeLTRB(0, 0, 50, 50),
266  {.color = Color::Azure()});
267  canvas->SaveLayer({}, std::nullopt, blur.get(),
269  /*total_content_depth=*/3);
270 
271  // This filter is nested in the first saveLayer. We cannot restore to onscreen
272  // here.
273  canvas->SaveLayer({}, std::nullopt, blur.get(),
275  /*total_content_depth=*/1);
276  canvas->Restore();
277  EXPECT_TRUE(canvas->RequiresReadback());
278 
279  canvas->Restore();
280  EXPECT_TRUE(canvas->RequiresReadback());
281 }
282 
283 TEST_P(AiksTest, DrawVerticesLinearGradientWithEmptySize) {
284  RenderCallback callback = [&](RenderTarget& render_target) {
285  ContentContext context(GetContext(), nullptr);
286  Canvas canvas(context, render_target, true, false);
287 
288  std::vector<flutter::DlPoint> vertex_coordinates = {
289  flutter::DlPoint(0, 0),
290  flutter::DlPoint(600, 0),
291  flutter::DlPoint(0, 600),
292  };
293  std::vector<flutter::DlPoint> texture_coordinates = {
294  flutter::DlPoint(0, 0),
295  flutter::DlPoint(500, 0),
296  flutter::DlPoint(0, 500),
297  };
298  std::vector<uint16_t> indices = {0, 1, 2};
299  flutter::DlVertices::Builder vertices_builder(
300  flutter::DlVertexMode::kTriangleStrip, vertex_coordinates.size(),
301  flutter::DlVertices::Builder::kHasTextureCoordinates, indices.size());
302  vertices_builder.store_vertices(vertex_coordinates.data());
303  vertices_builder.store_indices(indices.data());
304  vertices_builder.store_texture_coordinates(texture_coordinates.data());
305  auto vertices = vertices_builder.build();
306 
307  // The start and end points of the gradient form an empty rectangle.
308  std::vector<flutter::DlColor> colors = {flutter::DlColor::kBlue(),
309  flutter::DlColor::kRed()};
310  std::vector<Scalar> stops = {0.0, 1.0};
311  auto gradient = flutter::DlColorSource::MakeLinear(
312  {0, 0}, {0, 600}, 2, colors.data(), stops.data(),
313  flutter::DlTileMode::kClamp);
314 
315  Paint paint;
316  paint.color_source = gradient.get();
317  canvas.DrawVertices(std::make_shared<DlVerticesGeometry>(vertices, context),
318  BlendMode::kSrcOver, paint);
319 
320  canvas.EndReplay();
321  return true;
322  };
323 
324  ASSERT_TRUE(Playground::OpenPlaygroundHere(callback));
325 }
326 
327 TEST_P(AiksTest, DrawVerticesWithEmptyTextureCoordinates) {
328  auto runtime_stages =
329  OpenAssetAsRuntimeStage("runtime_stage_simple.frag.iplr");
330 
331  auto runtime_stage =
332  runtime_stages[PlaygroundBackendToRuntimeStageBackend(GetBackend())];
333  ASSERT_TRUE(runtime_stage);
334 
335  auto runtime_effect = flutter::DlRuntimeEffect::MakeImpeller(runtime_stage);
336  auto uniform_data = std::make_shared<std::vector<uint8_t>>();
337  auto color_source = flutter::DlColorSource::MakeRuntimeEffect(
338  runtime_effect, {}, uniform_data);
339 
340  RenderCallback callback = [&](RenderTarget& render_target) {
341  ContentContext context(GetContext(), nullptr);
342  Canvas canvas(context, render_target, true, false);
343 
344  std::vector<flutter::DlPoint> vertex_coordinates = {
345  flutter::DlPoint(100, 100),
346  flutter::DlPoint(300, 100),
347  flutter::DlPoint(100, 300),
348  };
349  // The bounding box of the texture coordinates is empty.
350  std::vector<flutter::DlPoint> texture_coordinates = {
351  flutter::DlPoint(0, 0),
352  flutter::DlPoint(0, 100),
353  flutter::DlPoint(0, 0),
354  };
355  std::vector<uint16_t> indices = {0, 1, 2};
356  flutter::DlVertices::Builder vertices_builder(
357  flutter::DlVertexMode::kTriangleStrip, vertex_coordinates.size(),
358  flutter::DlVertices::Builder::kHasTextureCoordinates, indices.size());
359  vertices_builder.store_vertices(vertex_coordinates.data());
360  vertices_builder.store_indices(indices.data());
361  vertices_builder.store_texture_coordinates(texture_coordinates.data());
362  auto vertices = vertices_builder.build();
363 
364  Paint paint;
365  paint.color_source = color_source.get();
366  canvas.DrawVertices(std::make_shared<DlVerticesGeometry>(vertices, context),
367  BlendMode::kSrcOver, paint);
368 
369  canvas.EndReplay();
370  return true;
371  };
372 
373  ASSERT_TRUE(Playground::OpenPlaygroundHere(callback));
374 }
375 
376 TEST_P(AiksTest, SupportsBlitToOnscreen) {
377  ContentContext context(GetContext(), nullptr);
378  auto canvas = CreateTestCanvas(context, Rect::MakeLTRB(0, 0, 100, 100),
379  /*requires_readback=*/true);
380 
381  if (GetBackend() != PlaygroundBackend::kMetal) {
382  EXPECT_FALSE(canvas->SupportsBlitToOnscreen());
383  } else {
384  EXPECT_TRUE(canvas->SupportsBlitToOnscreen());
385  }
386 }
387 
388 } // namespace testing
389 } // namespace impeller
void DrawVertices(const std::shared_ptr< VerticesGeometry > &vertices, BlendMode blend_mode, const Paint &paint)
Definition: canvas.cc:864
void EndReplay()
Definition: canvas.cc:1943
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)
flutter::DlRect DlRect
Definition: dl_dispatcher.h:25
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:37
static constexpr Color Azure()
Definition: color.h:298
A 4x4 matrix using column-major storage.
Definition: matrix.h:37
static constexpr Matrix MakeTranslation(const Vector3 &t)
Definition: matrix.h:95
const flutter::DlColorSource * color_source
Definition: paint.h:75
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:67