Flutter Engine
The Flutter Engine
Loading...
Searching...
No Matches
image_encoding_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 "flutter/lib/ui/painting/image_encoding.h"
6#include "flutter/lib/ui/painting/image_encoding_impl.h"
7
8#include "flutter/common/task_runners.h"
9#include "flutter/fml/synchronization/waitable_event.h"
10#include "flutter/lib/ui/painting/image.h"
11#include "flutter/runtime/dart_vm.h"
12#include "flutter/shell/common/shell_test.h"
13#include "flutter/shell/common/thread_host.h"
14#include "flutter/testing/testing.h"
15#include "gmock/gmock.h"
16#include "gtest/gtest.h"
17
18#if IMPELLER_SUPPORTS_RENDERING
19#include "flutter/lib/ui/painting/image_encoding_impeller.h"
21#endif // IMPELLER_SUPPORTS_RENDERING
22
23// CREATE_NATIVE_ENTRY is leaky by design
24// NOLINTBEGIN(clang-analyzer-core.StackAddressEscape)
25
26#pragma GCC diagnostic ignored "-Wunreachable-code"
27
28namespace flutter {
29namespace testing {
30
31namespace {
32fml::AutoResetWaitableEvent message_latch;
33
34class MockDlImage : public DlImage {
35 public:
36 MOCK_METHOD(sk_sp<SkImage>, skia_image, (), (const, override));
37 MOCK_METHOD(std::shared_ptr<impeller::Texture>,
38 impeller_texture,
39 (),
40 (const, override));
41 MOCK_METHOD(bool, isOpaque, (), (const, override));
42 MOCK_METHOD(bool, isTextureBacked, (), (const, override));
43 MOCK_METHOD(bool, isUIThreadSafe, (), (const, override));
44 MOCK_METHOD(SkISize, dimensions, (), (const, override));
45 MOCK_METHOD(size_t, GetApproximateByteSize, (), (const, override));
46};
47
48} // namespace
49
51 public:
52 struct Handlers {
53 Handlers& SetIfTrue(const std::function<void()>& handler) {
54 true_handler = handler;
55 return *this;
56 }
57 Handlers& SetIfFalse(const std::function<void()>& handler) {
58 false_handler = handler;
59 return *this;
60 }
61 std::function<void()> true_handler = [] {};
62 std::function<void()> false_handler = [] {};
63 };
64
65 MOCK_METHOD(void, Execute, (const Handlers& handlers), (const));
66 MOCK_METHOD(void, SetSwitch, (bool value));
67};
68
69TEST_F(ShellTest, EncodeImageGivesExternalTypedData) {
70 auto native_encode_image = [&](Dart_NativeArguments args) {
71 auto image_handle = Dart_GetNativeArgument(args, 0);
72 image_handle =
73 Dart_GetField(image_handle, Dart_NewStringFromCString("_image"));
74 ASSERT_FALSE(Dart_IsError(image_handle)) << Dart_GetError(image_handle);
75 ASSERT_FALSE(Dart_IsNull(image_handle));
76 auto format_handle = Dart_GetNativeArgument(args, 1);
77 auto callback_handle = Dart_GetNativeArgument(args, 2);
78
79 intptr_t peer = 0;
81 image_handle, tonic::DartWrappable::kPeerIndex, &peer);
82 ASSERT_FALSE(Dart_IsError(result));
83 CanvasImage* canvas_image = reinterpret_cast<CanvasImage*>(peer);
84
85 int64_t format = -1;
86 result = Dart_IntegerToInt64(format_handle, &format);
87 ASSERT_FALSE(Dart_IsError(result));
88
89 result = EncodeImage(canvas_image, format, callback_handle);
90 ASSERT_TRUE(Dart_IsNull(result));
91 };
92
93 auto nativeValidateExternal = [&](Dart_NativeArguments args) {
94 auto handle = Dart_GetNativeArgument(args, 0);
95
96 auto typed_data_type = Dart_GetTypeOfExternalTypedData(handle);
97 EXPECT_EQ(typed_data_type, Dart_TypedData_kUint8);
98
99 message_latch.Signal();
100 };
101
102 Settings settings = CreateSettingsForFixture();
103 TaskRunners task_runners("test", // label
104 GetCurrentTaskRunner(), // platform
105 CreateNewThread(), // raster
106 CreateNewThread(), // ui
107 CreateNewThread() // io
108 );
109
110 AddNativeCallback("EncodeImage", CREATE_NATIVE_ENTRY(native_encode_image));
111 AddNativeCallback("ValidateExternal",
112 CREATE_NATIVE_ENTRY(nativeValidateExternal));
113
114 std::unique_ptr<Shell> shell = CreateShell(settings, task_runners);
115
116 ASSERT_TRUE(shell->IsSetup());
117 auto configuration = RunConfiguration::InferFromSettings(settings);
118 configuration.SetEntrypoint("encodeImageProducesExternalUint8List");
119
120 shell->RunEngine(std::move(configuration), [&](auto result) {
122 });
123
124 message_latch.Wait();
125 DestroyShell(std::move(shell), task_runners);
126}
127
128TEST_F(ShellTest, EncodeImageAccessesSyncSwitch) {
129 Settings settings = CreateSettingsForFixture();
130 TaskRunners task_runners("test", // label
131 GetCurrentTaskRunner(), // platform
132 CreateNewThread(), // raster
133 CreateNewThread(), // ui
134 CreateNewThread() // io
135 );
136
137 auto native_encode_image = [&](Dart_NativeArguments args) {
138 auto image_handle = Dart_GetNativeArgument(args, 0);
139 image_handle =
140 Dart_GetField(image_handle, Dart_NewStringFromCString("_image"));
141 ASSERT_FALSE(Dart_IsError(image_handle)) << Dart_GetError(image_handle);
142 ASSERT_FALSE(Dart_IsNull(image_handle));
143 auto format_handle = Dart_GetNativeArgument(args, 1);
144
145 intptr_t peer = 0;
147 image_handle, tonic::DartWrappable::kPeerIndex, &peer);
148 ASSERT_FALSE(Dart_IsError(result));
149 CanvasImage* canvas_image = reinterpret_cast<CanvasImage*>(peer);
150
151 int64_t format = -1;
152 result = Dart_IntegerToInt64(format_handle, &format);
153 ASSERT_FALSE(Dart_IsError(result));
154
155 auto io_manager = UIDartState::Current()->GetIOManager();
157
158 task_runners.GetIOTaskRunner()->PostTask([&]() {
159 auto is_gpu_disabled_sync_switch =
160 std::make_shared<const MockSyncSwitch>();
161 EXPECT_CALL(*is_gpu_disabled_sync_switch, Execute)
162 .WillOnce([](const MockSyncSwitch::Handlers& handlers) {
163 handlers.true_handler();
164 });
165 ConvertToRasterUsingResourceContext(canvas_image->image()->skia_image(),
166 io_manager->GetResourceContext(),
167 is_gpu_disabled_sync_switch);
168 latch.Signal();
169 });
170
171 latch.Wait();
172
173 message_latch.Signal();
174 };
175
176 AddNativeCallback("EncodeImage", CREATE_NATIVE_ENTRY(native_encode_image));
177
178 std::unique_ptr<Shell> shell = CreateShell(settings, task_runners);
179
180 ASSERT_TRUE(shell->IsSetup());
181 auto configuration = RunConfiguration::InferFromSettings(settings);
182 configuration.SetEntrypoint("encodeImageProducesExternalUint8List");
183
184 shell->RunEngine(std::move(configuration), [&](auto result) {
186 });
187
188 message_latch.Wait();
189 DestroyShell(std::move(shell), task_runners);
190}
191
192#if IMPELLER_SUPPORTS_RENDERING
193using ::impeller::testing::MockAllocator;
194using ::impeller::testing::MockBlitPass;
195using ::impeller::testing::MockCommandBuffer;
196using ::impeller::testing::MockCommandQueue;
197using ::impeller::testing::MockDeviceBuffer;
198using ::impeller::testing::MockImpellerContext;
199using ::impeller::testing::MockTexture;
200using ::testing::_;
201using ::testing::DoAll;
202using ::testing::Invoke;
203using ::testing::InvokeArgument;
204using ::testing::Return;
205
206namespace {
207std::shared_ptr<impeller::Context> MakeConvertDlImageToSkImageContext(
208 std::vector<uint8_t>& buffer) {
209 auto context = std::make_shared<MockImpellerContext>();
210 auto command_buffer = std::make_shared<MockCommandBuffer>(context);
211 auto allocator = std::make_shared<MockAllocator>();
212 auto blit_pass = std::make_shared<MockBlitPass>();
213 auto command_queue = std::make_shared<MockCommandQueue>();
214 impeller::DeviceBufferDescriptor device_buffer_desc;
215 device_buffer_desc.size = buffer.size();
216 auto device_buffer = std::make_shared<MockDeviceBuffer>(device_buffer_desc);
217 EXPECT_CALL(*allocator, OnCreateBuffer).WillOnce(Return(device_buffer));
218 EXPECT_CALL(*blit_pass, IsValid).WillRepeatedly(Return(true));
219 EXPECT_CALL(*command_buffer, IsValid).WillRepeatedly(Return(true));
220 EXPECT_CALL(*command_buffer, OnCreateBlitPass).WillOnce(Return(blit_pass));
221 EXPECT_CALL(*context, GetResourceAllocator).WillRepeatedly(Return(allocator));
222 EXPECT_CALL(*context, CreateCommandBuffer).WillOnce(Return(command_buffer));
223 EXPECT_CALL(*device_buffer, OnGetContents).WillOnce(Return(buffer.data()));
224 EXPECT_CALL(*command_queue, Submit(_, _))
225 .WillRepeatedly(
226 DoAll(InvokeArgument<1>(impeller::CommandBuffer::Status::kCompleted),
227 Return(fml::Status())));
228 EXPECT_CALL(*context, GetCommandQueue).WillRepeatedly(Return(command_queue));
229 return context;
230}
231} // namespace
232
233TEST_F(ShellTest, EncodeImageRetries) {
234#ifndef FML_OS_MACOSX
235 // Only works on macos currently.
236 GTEST_SKIP();
237#endif
238 Settings settings = CreateSettingsForFixture();
239 settings.enable_impeller = true;
240 TaskRunners task_runners("test", // label
241 GetCurrentTaskRunner(), // platform
242 CreateNewThread(), // raster
243 CreateNewThread(), // ui
244 CreateNewThread() // io
245 );
246
247 std::unique_ptr<Shell> shell = CreateShell({
248 .settings = settings,
249 .task_runners = task_runners,
250 });
251
252 auto turn_off_gpu = [&](Dart_NativeArguments args) {
253 auto handle = Dart_GetNativeArgument(args, 0);
254 bool value = true;
255 ASSERT_TRUE(Dart_IsBoolean(handle));
256 Dart_BooleanValue(handle, &value);
257 TurnOffGPU(shell.get(), value);
258 };
259
260 AddNativeCallback("TurnOffGPU", CREATE_NATIVE_ENTRY(turn_off_gpu));
261
262 auto validate_not_null = [&](Dart_NativeArguments args) {
263 auto handle = Dart_GetNativeArgument(args, 0);
264 EXPECT_FALSE(Dart_IsNull(handle));
265 message_latch.Signal();
266 };
267
268 AddNativeCallback("ValidateNotNull", CREATE_NATIVE_ENTRY(validate_not_null));
269
270 ASSERT_TRUE(shell->IsSetup());
271 auto configuration = RunConfiguration::InferFromSettings(settings);
272 configuration.SetEntrypoint("toByteDataRetries");
273
274 shell->RunEngine(std::move(configuration), [&](auto result) {
276 });
277
278 message_latch.Wait();
279 DestroyShell(std::move(shell), task_runners);
280}
281
282TEST_F(ShellTest, EncodeImageFailsWithoutGPUImpeller) {
283#ifndef FML_OS_MACOSX
284 // Only works on macos currently.
285 GTEST_SKIP();
286#endif
287 Settings settings = CreateSettingsForFixture();
288 settings.enable_impeller = true;
289 TaskRunners task_runners("test", // label
290 GetCurrentTaskRunner(), // platform
291 CreateNewThread(), // raster
292 CreateNewThread(), // ui
293 CreateNewThread() // io
294 );
295
296 auto native_validate_error = [&](Dart_NativeArguments args) {
297 auto handle = Dart_GetNativeArgument(args, 0);
298
299 EXPECT_FALSE(Dart_IsNull(handle));
300
301 message_latch.Signal();
302 };
303
304 AddNativeCallback("ValidateError",
305 CREATE_NATIVE_ENTRY(native_validate_error));
306
307 std::unique_ptr<Shell> shell = CreateShell({
308 .settings = settings,
309 .task_runners = task_runners,
310 });
311
312 auto turn_off_gpu = [&](Dart_NativeArguments args) {
313 auto handle = Dart_GetNativeArgument(args, 0);
314 bool value = true;
315 ASSERT_TRUE(Dart_IsBoolean(handle));
316 Dart_BooleanValue(handle, &value);
317 TurnOffGPU(shell.get(), true);
318 };
319
320 AddNativeCallback("TurnOffGPU", CREATE_NATIVE_ENTRY(turn_off_gpu));
321
322 auto flush_awaiting_tasks = [&](Dart_NativeArguments args) {
323 task_runners.GetIOTaskRunner()->PostTask([&] {
324 std::shared_ptr<impeller::Context> impeller_context =
325 shell->GetIOManager()->GetImpellerContext();
326 // This will cause the stored tasks to overflow and start throwing them
327 // away.
328 for (int i = 0; i < impeller::Context::kMaxTasksAwaitingGPU; ++i) {
329 impeller_context->StoreTaskForGPU([] {});
330 }
331 });
332 };
333
334 AddNativeCallback("FlushGpuAwaitingTasks",
335 CREATE_NATIVE_ENTRY(flush_awaiting_tasks));
336
337 ASSERT_TRUE(shell->IsSetup());
338 auto configuration = RunConfiguration::InferFromSettings(settings);
339 configuration.SetEntrypoint("toByteDataWithoutGPU");
340
341 shell->RunEngine(std::move(configuration), [&](auto result) {
343 });
344
345 message_latch.Wait();
346 DestroyShell(std::move(shell), task_runners);
347}
348
349TEST(ImageEncodingImpellerTest, ConvertDlImageToSkImage16Float) {
350 sk_sp<MockDlImage> image(new MockDlImage());
351 EXPECT_CALL(*image, dimensions)
352 .WillRepeatedly(Return(SkISize::Make(100, 100)));
355 auto texture = std::make_shared<MockTexture>(desc);
356 EXPECT_CALL(*image, impeller_texture).WillOnce(Return(texture));
357 std::vector<uint8_t> buffer;
358 buffer.reserve(100 * 100 * 8);
359 auto context = MakeConvertDlImageToSkImageContext(buffer);
360 bool did_call = false;
362 image,
363 [&did_call](const fml::StatusOr<sk_sp<SkImage>>& image) {
364 did_call = true;
365 ASSERT_TRUE(image.ok());
366 ASSERT_TRUE(image.value());
367 EXPECT_EQ(100, image.value()->width());
368 EXPECT_EQ(100, image.value()->height());
369 EXPECT_EQ(kRGBA_F16_SkColorType, image.value()->colorType());
370 EXPECT_EQ(nullptr, image.value()->colorSpace());
371 },
372 context);
373 EXPECT_TRUE(did_call);
374}
375
376TEST(ImageEncodingImpellerTest, ConvertDlImageToSkImage10XR) {
377 sk_sp<MockDlImage> image(new MockDlImage());
378 EXPECT_CALL(*image, dimensions)
379 .WillRepeatedly(Return(SkISize::Make(100, 100)));
382 auto texture = std::make_shared<MockTexture>(desc);
383 EXPECT_CALL(*image, impeller_texture).WillOnce(Return(texture));
384 std::vector<uint8_t> buffer;
385 buffer.reserve(100 * 100 * 4);
386 auto context = MakeConvertDlImageToSkImageContext(buffer);
387 bool did_call = false;
389 image,
390 [&did_call](const fml::StatusOr<sk_sp<SkImage>>& image) {
391 did_call = true;
392 ASSERT_TRUE(image.ok());
393 ASSERT_TRUE(image.value());
394 EXPECT_EQ(100, image.value()->width());
395 EXPECT_EQ(100, image.value()->height());
396 EXPECT_EQ(kBGR_101010x_XR_SkColorType, image.value()->colorType());
397 EXPECT_EQ(nullptr, image.value()->colorSpace());
398 },
399 context);
400 EXPECT_TRUE(did_call);
401}
402
403TEST(ImageEncodingImpellerTest, PngEncoding10XR) {
404 int width = 100;
405 int height = 100;
408
410 SkCanvas* canvas = surface->getCanvas();
411
413 paint.setColor(SK_ColorBLUE);
414 paint.setAntiAlias(true);
415
416 canvas->clear(SK_ColorWHITE);
417 canvas->drawCircle(width / 2, height / 2, 100, paint);
418
419 sk_sp<SkImage> image = surface->makeImageSnapshot();
420
422 EXPECT_TRUE(png);
423}
424
425#endif // IMPELLER_SUPPORTS_RENDERING
426
427} // namespace testing
428} // namespace flutter
429
430// NOLINTEND(clang-analyzer-core.StackAddressEscape)
static void info(const char *fmt,...) SK_PRINTF_LIKE(1
Definition DM.cpp:213
#define TEST(S, s, D, expected)
kUnpremul_SkAlphaType
@ kRGBA_F16_SkColorType
pixel with half floats for red, green, blue, alpha;
Definition SkColorType.h:38
@ kBGR_101010x_XR_SkColorType
pixel with 10 bits each for blue, green, red; in 32-bit word, extended range
Definition SkColorType.h:31
constexpr SkColor SK_ColorBLUE
Definition SkColor.h:135
constexpr SkColor SK_ColorWHITE
Definition SkColor.h:122
void clear(SkColor color)
Definition SkCanvas.h:1199
void drawCircle(SkScalar cx, SkScalar cy, SkScalar radius, const SkPaint &paint)
SkColorSpace * colorSpace() const
Definition SkImage.cpp:156
int width() const
Definition SkImage.h:285
SkColorType colorType() const
Definition SkImage.cpp:152
int height() const
Definition SkImage.h:291
sk_sp< DlImage > image() const
Definition image.h:42
static void ConvertDlImageToSkImage(const sk_sp< DlImage > &dl_image, std::function< void(fml::StatusOr< sk_sp< SkImage > >)> encode_task, const std::shared_ptr< impeller::Context > &impeller_context)
static RunConfiguration InferFromSettings(const Settings &settings, const fml::RefPtr< fml::TaskRunner > &io_worker=nullptr, IsolateLaunchType launch_type=IsolateLaunchType::kNewGroup)
Attempts to infer a run configuration from the settings object. This tries to create a run configurat...
fml::RefPtr< fml::TaskRunner > GetIOTaskRunner() const
fml::WeakPtr< IOManager > GetIOManager() const
static UIDartState * Current()
MOCK_METHOD(void, Execute,(const Handlers &handlers),(const))
MOCK_METHOD(void, SetSwitch,(bool value))
virtual void PostTask(const fml::closure &task) override
static constexpr int32_t kMaxTasksAwaitingGPU
Definition context.h:60
const Paint & paint
DART_EXPORT Dart_TypedData_Type Dart_GetTypeOfExternalTypedData(Dart_Handle object)
DART_EXPORT bool Dart_IsBoolean(Dart_Handle object)
DART_EXPORT Dart_Handle Dart_IntegerToInt64(Dart_Handle integer, int64_t *value)
struct _Dart_Handle * Dart_Handle
Definition dart_api.h:258
DART_EXPORT Dart_Handle Dart_GetNativeArgument(Dart_NativeArguments args, int index)
DART_EXPORT Dart_Handle Dart_GetNativeInstanceField(Dart_Handle obj, int index, intptr_t *value)
struct _Dart_NativeArguments * Dart_NativeArguments
Definition dart_api.h:3010
@ Dart_TypedData_kUint8
Definition dart_api.h:2606
DART_EXPORT bool Dart_IsNull(Dart_Handle object)
DART_EXPORT Dart_Handle Dart_NewStringFromCString(const char *str)
DART_EXPORT DART_WARN_UNUSED_RESULT Dart_Handle Dart_GetField(Dart_Handle container, Dart_Handle name)
DART_EXPORT bool Dart_IsError(Dart_Handle handle)
DART_EXPORT const char * Dart_GetError(Dart_Handle handle)
DART_EXPORT Dart_Handle Dart_BooleanValue(Dart_Handle boolean_obj, bool *value)
VkSurfaceKHR surface
Definition main.cc:49
sk_sp< SkImage > image
Definition examples.cpp:29
G_BEGIN_DECLS G_MODULE_EXPORT FlValue * args
uint8_t value
GAsyncResult * result
FlTexture * texture
fml::RefPtr< fml::TaskRunner > CreateNewThread(const std::string &name)
fml::RefPtr< fml::TaskRunner > GetCurrentTaskRunner()
SK_API sk_sp< SkSurface > Raster(const SkImageInfo &imageInfo, size_t rowBytes, const SkSurfaceProps *surfaceProps)
TEST_F(DisplayListTest, Defaults)
sk_sp< SkImage > ConvertToRasterUsingResourceContext(const sk_sp< SkImage > &image, const fml::WeakPtr< GrDirectContext > &resource_context, const std::shared_ptr< const SyncSwitch > &is_gpu_disabled_sync_switch)
DEF_SWITCHES_START aot vmservice shared library Name of the *so containing AOT compiled Dart assets for launching the service isolate vm snapshot The VM snapshot data that will be memory mapped as read only SnapshotAssetPath must be present isolate snapshot The isolate snapshot data that will be memory mapped as read only SnapshotAssetPath must be present cache dir Path to the cache directory This is different from the persistent_cache_path in embedder which is used for Skia shader cache icu native lib Path to the library file that exports the ICU data vm service The hostname IP address on which the Dart VM Service should be served If not defaults to or::depending on whether ipv6 is specified vm service A custom Dart VM Service port The default is to pick a randomly available open port disable vm Disable the Dart VM Service The Dart VM Service is never available in release mode disable vm service Disable mDNS Dart VM Service publication Bind to the IPv6 localhost address for the Dart VM Service Ignored if vm service host is set endless trace buffer
Definition switches.h:126
DEF_SWITCHES_START aot vmservice shared library Name of the *so containing AOT compiled Dart assets for launching the service isolate vm snapshot The VM snapshot data that will be memory mapped as read only SnapshotAssetPath must be present isolate snapshot The isolate snapshot data that will be memory mapped as read only SnapshotAssetPath must be present cache dir Path to the cache directory This is different from the persistent_cache_path in embedder which is used for Skia shader cache icu native lib Path to the library file that exports the ICU data vm service The hostname IP address on which the Dart VM Service should be served If not defaults to or::depending on whether ipv6 is specified vm service A custom Dart VM Service port The default is to pick a randomly available open port disable vm Disable the Dart VM Service The Dart VM Service is never available in release mode disable vm service Disable mDNS Dart VM Service publication Bind to the IPv6 localhost address for the Dart VM Service Ignored if vm service host is set endless trace Enable an endless trace buffer The default is a ring buffer This is useful when very old events need to viewed For during application launch Memory usage will continue to grow indefinitely however Start app with an specific route defined on the framework flutter assets Path to the Flutter assets directory enable service port Allow the VM service to fallback to automatic port selection if binding to a specified port fails trace Trace early application lifecycle Automatically switches to an endless trace buffer trace skia Filters out all Skia trace event categories except those that are specified in this comma separated list dump skp on shader Automatically dump the skp that triggers new shader compilations This is useful for writing custom ShaderWarmUp to reduce jank By this is not enabled to reduce the overhead purge persistent Remove all existing persistent cache This is mainly for debugging purposes such as reproducing the shader compilation jank trace to Write the timeline trace to a file at the specified path The file will be in Perfetto s proto format
Definition switches.h:203
Dart_Handle EncodeImage(CanvasImage *canvas_image, int format, Dart_Handle callback_handle)
int32_t height
int32_t width
static constexpr SkISize Make(int32_t w, int32_t h)
Definition SkSize.h:20
static SkImageInfo Make(int width, int height, SkColorType ct, SkAlphaType at)
Handlers & SetIfTrue(const std::function< void()> &handler)
Handlers & SetIfFalse(const std::function< void()> &handler)
A lightweight object that describes the attributes of a texture that can then used an allocator to cr...
#define CREATE_NATIVE_ENTRY(native_entry)
#define EXPECT_TRUE(handle)
Definition unit_test.h:685