Flutter Engine
 
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
23namespace impeller {
24namespace testing {
25
26std::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;
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
71TEST_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
112TEST_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
130TEST_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
141TEST_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 =
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
176TEST_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;
186 canvas->SetBackdropData(data, 3);
187
188 auto blur =
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
216TEST_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;
226 canvas->SetBackdropData(data, 3);
227
228 auto blur =
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.
253TEST_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 =
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
285TEST_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(),
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(),
312 std::vector<Scalar> stops = {0.0, 1.0};
314 {0, 0}, {0, 600}, 2, colors.data(), stops.data(),
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
327}
328
329TEST_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>>();
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(),
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
376}
377
378TEST_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
390TEST_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};
406 {0, 0}, {1000, 1000}, 2, colors.data(), stops.data(),
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
450 RoundSuperellipse::MakeRectRadius(left_bounds, radius), paint);
451
452 // Right: Direct draw
453 paint.color_source = color_source.get();
455 RoundSuperellipse::MakeRectRadius(right_bounds, radius), paint);
456
457 canvas.EndReplay();
458 return true;
459 };
460
462}
463
464} // namespace testing
465} // 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:812
void DrawVertices(const std::shared_ptr< VerticesGeometry > &vertices, BlendMode blend_mode, const Paint &paint)
Definition canvas.cc:1040
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:331
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:658
LoadAction load_action
Definition formats.h:659
std::shared_ptr< Texture > texture
Definition formats.h:657
StoreAction store_action
Definition formats.h:660
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...