Flutter Engine Uber Docs
Docs for the entire Flutter Engine repo.
 
Loading...
Searching...
No Matches
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
11#include "gtest/gtest.h"
22#include "third_party/abseil-cpp/absl/status/status_matchers.h"
23
24namespace impeller {
25namespace testing {
26
27std::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;
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
72TEST_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
113TEST_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
131TEST_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
142TEST_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 =
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
177TEST_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;
187 canvas->SetBackdropData(data, 3);
188
189 auto blur =
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
217TEST_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;
227 canvas->SetBackdropData(data, 3);
228
229 auto blur =
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.
254TEST_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 =
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
286TEST_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(),
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(),
313 std::vector<Scalar> stops = {0.0, 1.0};
315 {0, 0}, {0, 600}, 2, colors.data(), stops.data(),
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
328}
329
330TEST_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>>();
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(),
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
378}
379
380TEST_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
392TEST_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};
408 {0, 0}, {1000, 1000}, 2, colors.data(), stops.data(),
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
452 RoundSuperellipse::MakeRectRadius(left_bounds, radius), paint);
453
454 // Right: Direct draw
455 paint.color_source = color_source.get();
457 RoundSuperellipse::MakeRectRadius(right_bounds, radius), paint);
458
459 canvas.EndReplay();
460 return true;
461 };
462
464}
465
466} // namespace testing
467} // namespace impeller
static std::shared_ptr< DlColorSource > MakeLinear(const DlPoint start_point, const DlPoint end_point, uint32_t stop_count, const DlColor *colors, const float *stops, DlTileMode tile_mode, const DlMatrix *matrix=nullptr)
static std::shared_ptr< DlColorSource > MakeRuntimeEffect(sk_sp< DlRuntimeEffect > runtime_effect, std::vector< std::shared_ptr< DlColorSource > > samplers, std::shared_ptr< std::vector< uint8_t > > uniform_data)
static std::shared_ptr< DlImageFilter > MakeBlur(DlScalar sigma_x, DlScalar sigma_y, DlTileMode tile_mode)
static sk_sp< DlRuntimeEffect > Make(std::shared_ptr< impeller::RuntimeStage > runtime_stage)
A utility class to build up a |DlVertices| object one set of data at a time.
Definition dl_vertices.h:73
void store_vertices(const DlPoint vertices[])
Copies the indicated list of points as vertices.
void store_indices(const uint16_t indices[])
Copies the indicated list of 16-bit indices as vertex indices.
void store_texture_coordinates(const DlPoint points[])
Copies the indicated list of points as texture coordinates.
std::shared_ptr< DlVertices > build()
Finalizes and the constructed DlVertices object.
static constexpr Flags kHasTextureCoordinates
Definition dl_vertices.h:95
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
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)
RenderTarget & SetColorAttachment(const ColorAttachment &attachment, size_t index)
FlutterDesktopBinaryReply callback
#define ASSERT_MATRIX_NEAR(a, b)
it will be possible to load the file into Perfetto s trace viewer use test Running tests that layout and measure text will not yield consistent results across various platforms Enabling this option will make font resolution default to the Ahem test font on all disable asset Prevents usage of any non test fonts unless they were explicitly Loaded via prefetched default font Indicates whether the embedding started a prefetch of the default font manager before creating the engine run In non interactive keep the shell running after the Dart script has completed enable serial On low power devices with low core running concurrent GC tasks on threads can cause them to contend with the UI thread which could potentially lead to jank This option turns off all concurrent GC activities domain network JSON encoded network policy per domain This overrides the DisallowInsecureConnections switch Embedder can specify whether to allow or disallow insecure connections at a domain level old gen heap size
DEF_SWITCHES_START aot vmservice shared library Name of the *so containing AOT compiled Dart assets for launching the service isolate vm snapshot data
Definition switch_defs.h:36
impeller::Point DlPoint
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
float Scalar
Definition scalar.h:19
Point DrawPlaygroundPoint(PlaygroundPoint &point)
Definition widgets.cc:11
constexpr RuntimeStageBackend PlaygroundBackendToRuntimeStageBackend(PlaygroundBackend backend)
Definition playground.h:33
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...
static constexpr DlColor kBlue()
Definition dl_color.h:73
static constexpr DlColor kRed()
Definition dl_color.h:71
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
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
static constexpr TRect MakeOriginSize(const TPoint< Type > &origin, const TSize< Type > &size)
Definition rect.h:144
static constexpr 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...