| 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"
13 #include "impeller/core/texture_descriptor.h"
14 #include "impeller/display_list/aiks_unittests.h"
15 #include "impeller/display_list/canvas.h"
16 #include "impeller/display_list/dl_vertices_geometry.h"
17 #include "impeller/geometry/geometry_asserts.h"
18 #include "impeller/playground/playground.h"
19 #include "impeller/renderer/render_target.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 =
31 context.GetDeviceCapabilities().GetDefaultColorFormat();
32 onscreen_desc.usage = TextureUsage::kRenderTarget;
33 onscreen_desc.storage_mode = StorageMode::kDevicePrivate;
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;
40 color0.load_action = LoadAction::kClear;
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;
52 color0.store_action = StoreAction::kMultisampleResolve;
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));
77 ASSERT_MATRIX_NEAR(
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));
85 ASSERT_MATRIX_NEAR(
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));
93 ASSERT_MATRIX_NEAR(
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));
101 ASSERT_MATRIX_NEAR(
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);
141 if (!context.GetDeviceCapabilities().SupportsFramebufferFetch()) {
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(),
156 ContentBoundsPromise::kContainsContents,
157 /*total_content_depth=*/1);
158 canvas->Restore();
159 EXPECT_TRUE(canvas->RequiresReadback());
160
161 canvas->SaveLayer({}, rect, blur.get(),
162 ContentBoundsPromise::kContainsContents,
163 /*total_content_depth=*/1);
164 canvas->Restore();
165 EXPECT_TRUE(canvas->RequiresReadback());
166
167 canvas->SaveLayer({}, rect, blur.get(),
168 ContentBoundsPromise::kContainsContents,
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);
176 if (!context.GetDeviceCapabilities().SupportsFramebufferFetch()) {
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(),
193 ContentBoundsPromise::kContainsContents,
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(),
200 ContentBoundsPromise::kContainsContents,
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(),
207 ContentBoundsPromise::kContainsContents,
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);
216 if (!context.GetDeviceCapabilities().SupportsFramebufferFetch()) {
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(),
233 ContentBoundsPromise::kContainsContents, 1, false);
234 canvas->Restore();
235 EXPECT_TRUE(canvas->RequiresReadback());
236
237 canvas->SaveLayer({}, std::nullopt, blur.get(),
238 ContentBoundsPromise::kContainsContents, 1, false, 1);
239 canvas->Restore();
240 EXPECT_FALSE(canvas->RequiresReadback());
241
242 canvas->SaveLayer({}, std::nullopt, blur.get(),
243 ContentBoundsPromise::kContainsContents, 1, false, 1);
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);
253 if (!context.GetDeviceCapabilities().SupportsFramebufferFetch()) {
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(),
268 ContentBoundsPromise::kContainsContents,
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(),
274 ContentBoundsPromise::kContainsContents,
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::kSourceOver, 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::kSourceOver, 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::kOpenGLES) {
382 EXPECT_FALSE(canvas->SupportsBlitToOnscreen());
383 } else {
384 EXPECT_TRUE(canvas->SupportsBlitToOnscreen());
385 }
386 }
387
388 } // namespace testing
389 } // namespace impeller
aiks_unittests.h
canvas.h
impeller::AiksPlayground
Definition: aiks_playground.h:16
impeller::Canvas
Definition: canvas.h:115
impeller::Canvas::DrawVertices
void DrawVertices(const std::shared_ptr< VerticesGeometry > &vertices, BlendMode blend_mode, const Paint &paint)
Definition: canvas.cc:739
impeller::Canvas::EndReplay
void EndReplay()
Definition: canvas.cc:1759
impeller::Capabilities::SupportsFramebufferFetch
virtual bool SupportsFramebufferFetch() const =0
Whether the context backend is able to support pipelines with shaders that read from the framebuffer ...
impeller::Capabilities::GetDefaultColorFormat
virtual PixelFormat GetDefaultColorFormat() const =0
Returns a supported PixelFormat for textures that store 4-channel colors (red/green/blue/alpha).
impeller::ContentContext
Definition: content_context.h:371
impeller::ContentContext::GetDeviceCapabilities
const Capabilities & GetDeviceCapabilities() const
Definition: content_context.cc:617
impeller::ContentContext::GetContext
std::shared_ptr< Context > GetContext() const
Definition: content_context.cc:613
impeller::Playground::OpenPlaygroundHere
bool OpenPlaygroundHere(const RenderCallback &render_callback)
Definition: playground.cc:201
impeller::RenderTarget
Definition: render_target.h:38
impeller::RenderTarget::SetColorAttachment
RenderTarget & SetColorAttachment(const ColorAttachment &attachment, size_t index)
Definition: render_target.cc:199
formats.h
dl_vertices_geometry.h
geometry_asserts.h
ASSERT_MATRIX_NEAR
#define ASSERT_MATRIX_NEAR(a, b)
Definition: geometry_asserts.h:189
impeller::testing::CreateTestCanvas
std::unique_ptr< Canvas > CreateTestCanvas(ContentContext &context, std::optional< Rect > cull_rect=std::nullopt, bool requires_readback=false)
Definition: canvas_unittests.cc:24
impeller::testing::TEST_P
TEST_P(AiksTest, DrawAtlasNoColor)
Definition: aiks_dl_atlas_unittests.cc:57
impeller
Definition: allocation.cc:12
impeller::DlRect
flutter::DlRect DlRect
Definition: dl_dispatcher.h:25
impeller::PlaygroundBackendToRuntimeStageBackend
constexpr RuntimeStageBackend PlaygroundBackendToRuntimeStageBackend(PlaygroundBackend backend)
Definition: playground.h:33
impeller::StorageMode::kDevicePrivate
@ kDevicePrivate
impeller::StorageMode::kDeviceTransient
@ kDeviceTransient
impeller::DlPoint
flutter::DlPoint DlPoint
Definition: dl_dispatcher.h:24
impeller::kPiOver2
constexpr float kPiOver2
Definition: constants.h:32
impeller::TextureType::kTexture2DMultisample
@ kTexture2DMultisample
impeller::BlendMode::kSourceOver
@ kSourceOver
impeller::ContentBoundsPromise::kContainsContents
@ kContainsContents
The caller claims the bounds are a reasonably tight estimate of the coverage of the contents and shou...
impeller::PlaygroundBackend::kOpenGLES
@ kOpenGLES
impeller::LoadAction::kClear
@ kClear
impeller::StoreAction::kMultisampleResolve
@ kMultisampleResolve
impeller::TextureUsage::kRenderTarget
@ kRenderTarget
impeller::SampleCount::kCount4
@ kCount4
impeller::SampleCount::kCount1
@ kCount1
playground.h
render_target.h
impeller::Attachment::resolve_texture
std::shared_ptr< Texture > resolve_texture
Definition: formats.h:658
impeller::Attachment::load_action
LoadAction load_action
Definition: formats.h:659
impeller::Attachment::texture
std::shared_ptr< Texture > texture
Definition: formats.h:657
impeller::Attachment::store_action
StoreAction store_action
Definition: formats.h:660
impeller::BackdropData
Definition: canvas.h:35
impeller::BackdropData::backdrop_count
size_t backdrop_count
Definition: canvas.h:36
impeller::ColorAttachment
Definition: formats.h:665
impeller::Color::Azure
static constexpr Color Azure()
Definition: color.h:297
impeller::Matrix
A 4x4 matrix using column-major storage.
Definition: matrix.h:37
impeller::Matrix::MakeTranslation
static constexpr Matrix MakeTranslation(const Vector3 &t)
Definition: matrix.h:95
impeller::Paint
Definition: paint.h:26
impeller::Paint::color_source
const flutter::DlColorSource * color_source
Definition: paint.h:75
impeller::Radians
Definition: scalar.h:43
impeller::TRect< Scalar >::MakeLTRB
constexpr static TRect MakeLTRB(Type left, Type top, Type right, Type bottom)
Definition: rect.h:129
impeller::TSize< Scalar >
impeller::TextureDescriptor
A lightweight object that describes the attributes of a texture that can then used an allocator to cr...
Definition: texture_descriptor.h:38
impeller::TextureDescriptor::usage
TextureUsageMask usage
Definition: texture_descriptor.h:44
impeller::TextureDescriptor::type
TextureType type
Definition: texture_descriptor.h:40
impeller::TextureDescriptor::size
ISize size
Definition: texture_descriptor.h:42
impeller::TextureDescriptor::storage_mode
StorageMode storage_mode
Definition: texture_descriptor.h:39
impeller::TextureDescriptor::sample_count
SampleCount sample_count
Definition: texture_descriptor.h:45
impeller::TextureDescriptor::format
PixelFormat format
Definition: texture_descriptor.h:41
impeller::Vector3
Definition: vector.h:20
texture_descriptor.h
data
std::shared_ptr< const fml::Mapping > data
Definition: texture_gles.cc:64