Flutter Engine Uber Docs
Docs for the entire Flutter Engine repo.
 
Loading...
Searching...
No Matches
image_decoder_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
26#include "fml/logging.h"
29#include "third_party/skia/include/codec/SkCodecAnimation.h"
30#include "third_party/skia/include/core/SkData.h"
31#include "third_party/skia/include/core/SkImage.h"
32#include "third_party/skia/include/core/SkImageInfo.h"
33#include "third_party/skia/include/core/SkSize.h"
34#include "third_party/skia/include/encode/SkPngEncoder.h"
35
36// CREATE_NATIVE_ENTRY is leaky by design
37// NOLINTBEGIN(clang-analyzer-core.StackAddressEscape)
38// NOLINTBEGIN(clang-analyzer-cplusplus.NewDeleteLeaks)
39
40namespace impeller {
41
43 public:
45
46 BackendType GetBackendType() const override { return BackendType::kMetal; }
47
48 std::string DescribeGpuModel() const override { return "TestGpu"; }
49
50 bool IsValid() const override { return true; }
51
52 const std::shared_ptr<const Capabilities>& GetCapabilities() const override {
53 return capabilities_;
54 }
55
56 std::shared_ptr<Allocator> GetResourceAllocator() const override {
57 return std::make_shared<TestImpellerAllocator>();
58 }
59
60 std::shared_ptr<ShaderLibrary> GetShaderLibrary() const override {
61 return nullptr;
62 }
63
64 std::shared_ptr<SamplerLibrary> GetSamplerLibrary() const override {
65 return nullptr;
66 }
67
68 std::shared_ptr<PipelineLibrary> GetPipelineLibrary() const override {
69 return nullptr;
70 }
71
72 std::shared_ptr<CommandQueue> GetCommandQueue() const override {
74 }
75
76 std::shared_ptr<CommandBuffer> CreateCommandBuffer() const override {
78 return nullptr;
79 }
80
81 void StoreTaskForGPU(const std::function<void()>& task,
82 const std::function<void()>& failure) override {
83 tasks_.push_back(PendingTask{task, failure});
84 }
85
86 void FlushTasks(bool fail = false) {
87 for (auto& task : tasks_) {
88 if (fail) {
89 task.task();
90 } else {
91 task.failure();
92 }
93 }
94 tasks_.clear();
95 }
96
97 void DisposeThreadLocalCachedResources() override { did_dispose_ = true; }
98
99 void Shutdown() override {}
100
104
105 bool DidDisposeResources() const { return did_dispose_; }
106
107 mutable size_t command_buffer_count_ = 0;
108
109 private:
110 struct PendingTask {
111 std::function<void()> task;
112 std::function<void()> failure;
113 };
114 std::vector<PendingTask> tasks_;
115 std::shared_ptr<const Capabilities> capabilities_;
116 bool did_dispose_ = false;
117};
118
119} // namespace impeller
120
121namespace flutter {
122namespace testing {
123
124class TestIOManager final : public IOManager {
125 public:
126 explicit TestIOManager(const fml::RefPtr<fml::TaskRunner>& task_runner,
127 bool has_gpu_context = true)
128 : gl_surface_(DlISize(1, 1)),
129 impeller_context_(std::make_shared<impeller::TestImpellerContext>()),
130 gl_context_(has_gpu_context ? gl_surface_.CreateGrContext() : nullptr),
131 weak_gl_context_factory_(
132 has_gpu_context
133 ? std::make_unique<fml::WeakPtrFactory<GrDirectContext>>(
134 gl_context_.get())
135 : nullptr),
136 unref_queue_(fml::MakeRefCounted<SkiaUnrefQueue>(
137 task_runner,
138 fml::TimeDelta::FromNanoseconds(0),
139 gl_context_)),
140 runner_(task_runner),
141 is_gpu_disabled_sync_switch_(std::make_shared<fml::SyncSwitch>()),
142 weak_factory_(this) {
143 FML_CHECK(task_runner->RunsTasksOnCurrentThread())
144 << "The IO manager must be initialized its primary task runner. The "
145 "test harness may not be set up correctly/safely.";
146 weak_prototype_ = weak_factory_.GetWeakPtr();
147 }
148
149 ~TestIOManager() override {
152 [&latch, queue = unref_queue_]() {
153 queue->Drain();
154 latch.Signal();
155 });
156 latch.Wait();
157 }
158
159 // |IOManager|
161 return weak_prototype_;
162 }
163
164 // |IOManager|
166 return weak_gl_context_factory_ ? weak_gl_context_factory_->GetWeakPtr()
168 }
169
170 // |IOManager|
172 return unref_queue_;
173 }
174
175 // |IOManager|
176 std::shared_ptr<const fml::SyncSwitch> GetIsGpuDisabledSyncSwitch() override {
178 return is_gpu_disabled_sync_switch_;
179 }
180
181 // |IOManager|
182 std::shared_ptr<impeller::Context> GetImpellerContext() const override {
183 return impeller_context_;
184 }
185
186 void SetGpuDisabled(bool disabled) {
187 is_gpu_disabled_sync_switch_->SetSwitch(disabled);
188 }
189
191
192 private:
193 TestGLSurface gl_surface_;
194 std::shared_ptr<impeller::Context> impeller_context_;
195 sk_sp<GrDirectContext> gl_context_;
196 std::unique_ptr<fml::WeakPtrFactory<GrDirectContext>>
197 weak_gl_context_factory_;
198 fml::RefPtr<SkiaUnrefQueue> unref_queue_;
199 fml::WeakPtr<TestIOManager> weak_prototype_;
201 std::shared_ptr<fml::SyncSwitch> is_gpu_disabled_sync_switch_;
203
205};
206
208
209TEST_F(ImageDecoderFixtureTest, CanCreateImageDecoder) {
211 auto thread_task_runner = CreateNewThread();
212 TaskRunners runners(GetCurrentTestName(), // label
213 thread_task_runner, // platform
214 thread_task_runner, // raster
215 thread_task_runner, // ui
216 thread_task_runner // io
217
218 );
219
220 PostTaskSync(runners.GetIOTaskRunner(), [&]() {
221 TestIOManager manager(runners.GetIOTaskRunner());
222 Settings settings;
223 auto decoder = ImageDecoder::Make(settings, runners, loop->GetTaskRunner(),
224 manager.GetWeakIOManager(),
225 std::make_shared<fml::SyncSwitch>());
226 ASSERT_NE(decoder, nullptr);
227 });
228}
229
230/// An Image generator that pretends it can't recognize the data it was given.
232 public:
233 UnknownImageGenerator() : info_(SkImageInfo::MakeUnknown()) {};
235 const SkImageInfo& GetInfo() { return info_; }
236
237 unsigned int GetFrameCount() const { return 1; }
238
239 unsigned int GetPlayCount() const { return 1; }
240
241 const ImageGenerator::FrameInfo GetFrameInfo(unsigned int frame_index) {
242 return {std::nullopt, 0, SkCodecAnimation::DisposalMethod::kKeep};
243 }
244
245 SkISize GetScaledDimensions(float scale) {
246 return SkISize::Make(info_.width(), info_.height());
247 }
248
249 bool GetPixels(const SkImageInfo& info,
250 void* pixels,
251 size_t row_bytes,
252 unsigned int frame_index,
253 std::optional<unsigned int> prior_frame) {
254 return false;
255 };
256
257 private:
258 SkImageInfo info_;
259};
260
261TEST_F(ImageDecoderFixtureTest, InvalidImageResultsError) {
263 auto thread_task_runner = CreateNewThread();
264 TaskRunners runners(GetCurrentTestName(), // label
265 thread_task_runner, // platform
266 thread_task_runner, // raster
267 thread_task_runner, // ui
268 thread_task_runner // io
269 );
270
272 thread_task_runner->PostTask([&]() {
274 Settings settings;
275 auto decoder = ImageDecoder::Make(settings, runners, loop->GetTaskRunner(),
276 manager.GetWeakIOManager(),
277 std::make_shared<fml::SyncSwitch>());
278
279 auto data = flutter::testing::OpenFixtureAsSkData("ThisDoesNotExist.jpg");
280 ASSERT_FALSE(data);
281
282 fml::RefPtr<ImageDescriptor> image_descriptor =
283 fml::MakeRefCounted<ImageDescriptor>(
284 std::move(data), std::make_unique<UnknownImageGenerator>());
285
286 ImageDecoder::ImageResult callback = [&](const sk_sp<DlImage>& image,
287 const std::string& decode_error) {
288 ASSERT_TRUE(runners.GetUITaskRunner()->RunsTasksOnCurrentThread());
289 ASSERT_FALSE(image);
290 latch.Signal();
291 };
292 decoder->Decode(image_descriptor, {.target_width = 0, .target_height = 0},
293 callback);
294 });
295 latch.Wait();
296}
297
298TEST_F(ImageDecoderFixtureTest, ValidImageResultsInSuccess) {
300 TaskRunners runners(GetCurrentTestName(), // label
301 CreateNewThread("platform"), // platform
302 CreateNewThread("raster"), // raster
303 CreateNewThread("ui"), // ui
304 CreateNewThread("io") // io
305 );
306
308
309 std::unique_ptr<TestIOManager> io_manager;
310
311 auto release_io_manager = [&]() {
312 io_manager.reset();
313 latch.Signal();
314 };
315 auto decode_image = [&]() {
316 Settings settings;
317 std::unique_ptr<ImageDecoder> image_decoder = ImageDecoder::Make(
318 settings, runners, loop->GetTaskRunner(),
319 io_manager->GetWeakIOManager(), std::make_shared<fml::SyncSwitch>());
320
321 auto data = flutter::testing::OpenFixtureAsSkData("DashInNooglerHat.jpg");
322
323 ASSERT_TRUE(data);
324 ASSERT_GE(data->size(), 0u);
325
326 ImageGeneratorRegistry registry;
327 std::shared_ptr<ImageGenerator> generator =
329 ASSERT_TRUE(generator);
330
331 auto descriptor = fml::MakeRefCounted<ImageDescriptor>(
332 std::move(data), std::move(generator));
333
334 ImageDecoder::ImageResult callback = [&](const sk_sp<DlImage>& image,
335 const std::string& decode_error) {
336 ASSERT_TRUE(runners.GetUITaskRunner()->RunsTasksOnCurrentThread());
337 ASSERT_TRUE(image && image->skia_image());
338 EXPECT_TRUE(io_manager->did_access_is_gpu_disabled_sync_switch_);
339 runners.GetIOTaskRunner()->PostTask(release_io_manager);
340 };
341 EXPECT_FALSE(io_manager->did_access_is_gpu_disabled_sync_switch_);
342 image_decoder->Decode(
343 descriptor,
344 {.target_width = static_cast<uint32_t>(descriptor->width()),
345 .target_height = static_cast<uint32_t>(descriptor->height())},
346 callback);
347 };
348
349 auto set_up_io_manager_and_decode = [&]() {
350 io_manager = std::make_unique<TestIOManager>(runners.GetIOTaskRunner());
351 runners.GetUITaskRunner()->PostTask(decode_image);
352 };
353
354 runners.GetIOTaskRunner()->PostTask(set_up_io_manager_and_decode);
355 latch.Wait();
356}
357
358TEST_F(ImageDecoderFixtureTest, ImpellerUploadToSharedNoGpu) {
359#if !IMPELLER_SUPPORTS_RENDERING
360 GTEST_SKIP() << "Impeller only test.";
361#endif // IMPELLER_SUPPORTS_RENDERING
362
363 auto no_gpu_access_context =
364 std::make_shared<impeller::TestImpellerContext>();
365 auto gpu_disabled_switch = std::make_shared<fml::SyncSwitch>(true);
366
367 auto info = SkImageInfo::Make(10, 10, SkColorType::kRGBA_8888_SkColorType,
368 SkAlphaType::kPremul_SkAlphaType);
369 ImageDecoderImpeller::ImageInfo decoder_info = {
370 .size = impeller::ISize(10, 10),
372 };
373 auto bitmap = std::make_shared<SkBitmap>();
374 bitmap->allocPixels(info, 10 * 4);
376 desc.size = bitmap->computeByteSize();
377 auto buffer = std::make_shared<impeller::TestImpellerDeviceBuffer>(desc);
378
379 bool invoked = false;
380 auto cb = [&invoked](const sk_sp<DlImage>& image,
381 const std::string& message) { invoked = true; };
382
384 cb, no_gpu_access_context, buffer, decoder_info, std::nullopt,
385 gpu_disabled_switch);
386
387 EXPECT_EQ(no_gpu_access_context->command_buffer_count_, 0ul);
388 EXPECT_FALSE(invoked);
389 EXPECT_EQ(no_gpu_access_context->DidDisposeResources(), false);
390
392 no_gpu_access_context, bitmap);
393
394 ASSERT_EQ(no_gpu_access_context->command_buffer_count_, 0ul);
395 ASSERT_EQ(result.second, "");
396 EXPECT_EQ(no_gpu_access_context->DidDisposeResources(), true);
397 EXPECT_EQ(
398 result.first->impeller_texture()->GetTextureDescriptor().storage_mode,
400
401 no_gpu_access_context->FlushTasks(/*fail=*/true);
402}
403
405 ImpellerUploadToSharedNoGpuTaskFlushingFailure) {
406#if !IMPELLER_SUPPORTS_RENDERING
407 GTEST_SKIP() << "Impeller only test.";
408#endif // IMPELLER_SUPPORTS_RENDERING
409
410 auto no_gpu_access_context =
411 std::make_shared<impeller::TestImpellerContext>();
412 auto gpu_disabled_switch = std::make_shared<fml::SyncSwitch>(true);
413
414 auto info = SkImageInfo::Make(10, 10, SkColorType::kRGBA_8888_SkColorType,
415 SkAlphaType::kPremul_SkAlphaType);
416 ImageDecoderImpeller::ImageInfo decoder_info = {
417 .size = impeller::ISize(10, 10),
419 };
420 auto bitmap = std::make_shared<SkBitmap>();
421 bitmap->allocPixels(info, 10 * 4);
423 desc.size = bitmap->computeByteSize();
424 auto buffer = std::make_shared<impeller::TestImpellerDeviceBuffer>(desc);
425
426 sk_sp<DlImage> image;
427 std::string message;
428 bool invoked = false;
429 auto cb = [&invoked, &image, &message](sk_sp<DlImage> p_image,
430 std::string p_message) {
431 invoked = true;
432 image = std::move(p_image);
433 message = std::move(p_message);
434 };
435
437 cb, no_gpu_access_context, buffer, decoder_info, std::nullopt,
438 gpu_disabled_switch);
439
440 EXPECT_EQ(no_gpu_access_context->command_buffer_count_, 0ul);
441 EXPECT_FALSE(invoked);
442
443 no_gpu_access_context->FlushTasks(/*fail=*/true);
444
445 EXPECT_TRUE(invoked);
446 // Creation of the dl image will still fail with the mocked context.
447 EXPECT_NE(message, "");
448}
449
450TEST_F(ImageDecoderFixtureTest, ImpellerNullColorspace) {
451 auto info = SkImageInfo::Make(10, 10, SkColorType::kRGBA_8888_SkColorType,
452 SkAlphaType::kPremul_SkAlphaType);
453 SkBitmap bitmap;
454 bitmap.allocPixels(info, 10 * 4);
455 auto data = SkData::MakeWithoutCopy(bitmap.getPixels(), 10 * 10 * 4);
456 auto image = SkImages::RasterFromBitmap(bitmap);
457 ASSERT_TRUE(image != nullptr);
458 EXPECT_EQ(SkISize::Make(10, 10), image->dimensions());
459 EXPECT_EQ(nullptr, image->colorSpace());
460
461 auto descriptor = fml::MakeRefCounted<ImageDescriptor>(
462 std::move(data), ImageDescriptor::CreateImageInfo(image->imageInfo()),
463 10 * 4);
464
465#if IMPELLER_SUPPORTS_RENDERING
466 std::shared_ptr<impeller::Capabilities> capabilities =
469 .Build();
470 std::shared_ptr<impeller::Allocator> allocator =
471 std::make_shared<impeller::TestImpellerAllocator>();
472 absl::StatusOr<ImageDecoderImpeller::DecompressResult> decompressed =
474 descriptor.get(), {.target_width = 100, .target_height = 100},
475 {100, 100},
476 /*supports_wide_gamut=*/true, capabilities, allocator);
477 ASSERT_TRUE(decompressed.ok());
478 EXPECT_EQ(decompressed->image_info.format,
480#endif // IMPELLER_SUPPORTS_RENDERING
481}
482
483TEST_F(ImageDecoderFixtureTest, ImpellerPixelConversion32F) {
484 auto info = SkImageInfo::Make(10, 10, SkColorType::kRGBA_F32_SkColorType,
485 SkAlphaType::kUnpremul_SkAlphaType);
486 SkBitmap bitmap;
487 bitmap.allocPixels(info, 10 * 16);
488 auto data = SkData::MakeWithoutCopy(bitmap.getPixels(), 10 * 10 * 16);
489 auto image = SkImages::RasterFromBitmap(bitmap);
490
491 ASSERT_TRUE(image != nullptr);
492 EXPECT_EQ(SkISize::Make(10, 10), image->dimensions());
493 EXPECT_EQ(nullptr, image->colorSpace());
494
495 auto descriptor = fml::MakeRefCounted<ImageDescriptor>(
496 std::move(data), ImageDescriptor::CreateImageInfo(image->imageInfo()),
497 10 * 16);
498
499#if IMPELLER_SUPPORTS_RENDERING
500 std::shared_ptr<impeller::Capabilities> capabilities =
503 .Build();
504 std::shared_ptr<impeller::Allocator> allocator =
505 std::make_shared<impeller::TestImpellerAllocator>();
506 absl::StatusOr<ImageDecoderImpeller::DecompressResult> decompressed =
508 descriptor.get(), {.target_width = 100, .target_height = 100},
509 {100, 100},
510 /*supports_wide_gamut=*/true, capabilities, allocator);
511
512 ASSERT_TRUE(decompressed.ok());
513 EXPECT_EQ(decompressed->image_info.format,
515#endif // IMPELLER_SUPPORTS_RENDERING
516}
517
518TEST_F(ImageDecoderFixtureTest, ImpellerWideGamutDisplayP3Opaque) {
519 auto data = flutter::testing::OpenFixtureAsSkData("DisplayP3Logo.jpg");
520 auto image = SkImages::DeferredFromEncodedData(data);
521 ASSERT_TRUE(image != nullptr);
522 ASSERT_EQ(SkISize::Make(100, 100), image->dimensions());
523
524 ImageGeneratorRegistry registry;
525 std::shared_ptr<ImageGenerator> generator =
527 ASSERT_TRUE(generator);
528
529 auto descriptor = fml::MakeRefCounted<ImageDescriptor>(std::move(data),
530 std::move(generator));
531
532#if IMPELLER_SUPPORTS_RENDERING
533 std::shared_ptr<impeller::Capabilities> capabilities =
536 .Build();
537 std::shared_ptr<impeller::Allocator> allocator =
538 std::make_shared<impeller::TestImpellerAllocator>();
539 absl::StatusOr<ImageDecoderImpeller::DecompressResult> wide_result =
541 descriptor.get(), {.target_width = 100, .target_height = 100},
542 {100, 100},
543 /*supports_wide_gamut=*/true, capabilities, allocator);
544
545 ASSERT_TRUE(wide_result.ok());
546 ASSERT_EQ(wide_result->image_info.format,
548
549 const uint32_t* pixel_ptr = reinterpret_cast<const uint32_t*>(
550 wide_result->device_buffer->OnGetContents());
551 bool found_deep_red = false;
552 for (int i = 0; i < wide_result->image_info.size.width *
553 wide_result->image_info.size.height;
554 ++i) {
555 uint32_t pixel = *pixel_ptr++;
556 float blue = DecodeBGR10((pixel >> 0) & 0x3ff);
557 float green = DecodeBGR10((pixel >> 10) & 0x3ff);
558 float red = DecodeBGR10((pixel >> 20) & 0x3ff);
559 if (fabsf(red - 1.0931f) < 0.01f && fabsf(green - -0.2268f) < 0.01f &&
560 fabsf(blue - -0.1501f) < 0.01f) {
561 found_deep_red = true;
562 break;
563 }
564 }
565 ASSERT_TRUE(found_deep_red);
566
567 absl::StatusOr<ImageDecoderImpeller::DecompressResult> narrow_result =
569 descriptor.get(), {.target_width = 100, .target_height = 100},
570 {100, 100},
571 /*supports_wide_gamut=*/false, capabilities, allocator);
572
573 ASSERT_TRUE(narrow_result.ok());
574 ASSERT_EQ(narrow_result->image_info.format,
576#endif // IMPELLER_SUPPORTS_RENDERING
577}
578
579TEST_F(ImageDecoderFixtureTest, ImpellerNonWideGamut) {
580 auto data = flutter::testing::OpenFixtureAsSkData("Horizontal.jpg");
581 auto image = SkImages::DeferredFromEncodedData(data);
582 ASSERT_TRUE(image != nullptr);
583 ASSERT_EQ(SkISize::Make(600, 200), image->dimensions());
584
585 ImageGeneratorRegistry registry;
586 std::shared_ptr<ImageGenerator> generator =
588 ASSERT_TRUE(generator);
589
590 auto descriptor = fml::MakeRefCounted<ImageDescriptor>(std::move(data),
591 std::move(generator));
592
593#if IMPELLER_SUPPORTS_RENDERING
594 std::shared_ptr<impeller::Capabilities> capabilities =
597 .Build();
598 std::shared_ptr<impeller::Allocator> allocator =
599 std::make_shared<impeller::TestImpellerAllocator>();
600 absl::StatusOr<ImageDecoderImpeller::DecompressResult> result =
602 descriptor.get(), {.target_width = 600, .target_height = 200},
603 {600, 200},
604 /*supports_wide_gamut=*/true, capabilities, allocator);
605
606 ASSERT_TRUE(result.ok());
607 ASSERT_EQ(result->image_info.format,
609#endif // IMPELLER_SUPPORTS_RENDERING
610}
611
612TEST_F(ImageDecoderFixtureTest, ExifDataIsRespectedOnDecode) {
614 TaskRunners runners(GetCurrentTestName(), // label
615 CreateNewThread("platform"), // platform
616 CreateNewThread("raster"), // raster
617 CreateNewThread("ui"), // ui
618 CreateNewThread("io") // io
619 );
620
622
623 std::unique_ptr<IOManager> io_manager;
624
625 auto release_io_manager = [&]() {
626 io_manager.reset();
627 latch.Signal();
628 };
629
630 SkISize decoded_size = SkISize::MakeEmpty();
631 auto decode_image = [&]() {
632 Settings settings;
633 std::unique_ptr<ImageDecoder> image_decoder = ImageDecoder::Make(
634 settings, runners, loop->GetTaskRunner(),
635 io_manager->GetWeakIOManager(), std::make_shared<fml::SyncSwitch>());
636
637 auto data = flutter::testing::OpenFixtureAsSkData("Horizontal.jpg");
638
639 ASSERT_TRUE(data);
640 ASSERT_GE(data->size(), 0u);
641
642 ImageGeneratorRegistry registry;
643 std::shared_ptr<ImageGenerator> generator =
645 ASSERT_TRUE(generator);
646
647 auto descriptor = fml::MakeRefCounted<ImageDescriptor>(
648 std::move(data), std::move(generator));
649
650 ImageDecoder::ImageResult callback = [&](const sk_sp<DlImage>& image,
651 const std::string& decode_error) {
652 ASSERT_TRUE(runners.GetUITaskRunner()->RunsTasksOnCurrentThread());
653 ASSERT_TRUE(image && image->skia_image());
654 decoded_size = image->skia_image()->dimensions();
655 runners.GetIOTaskRunner()->PostTask(release_io_manager);
656 };
657 image_decoder->Decode(
658 descriptor,
659 {.target_width = static_cast<uint32_t>(descriptor->width()),
660 .target_height = static_cast<uint32_t>(descriptor->height())},
661 callback);
662 };
663
664 auto set_up_io_manager_and_decode = [&]() {
665 io_manager = std::make_unique<TestIOManager>(runners.GetIOTaskRunner());
666 runners.GetUITaskRunner()->PostTask(decode_image);
667 };
668
669 runners.GetIOTaskRunner()->PostTask(set_up_io_manager_and_decode);
670
671 latch.Wait();
672
673 ASSERT_EQ(decoded_size.width(), 600);
674 ASSERT_EQ(decoded_size.height(), 200);
675}
676
677TEST_F(ImageDecoderFixtureTest, CanDecodeWithoutAGPUContext) {
679 TaskRunners runners(GetCurrentTestName(), // label
680 CreateNewThread("platform"), // platform
681 CreateNewThread("raster"), // raster
682 CreateNewThread("ui"), // ui
683 CreateNewThread("io") // io
684 );
685
687
688 std::unique_ptr<IOManager> io_manager;
689
690 auto release_io_manager = [&]() {
691 io_manager.reset();
692 latch.Signal();
693 };
694
695 auto decode_image = [&]() {
696 Settings settings;
697 std::unique_ptr<ImageDecoder> image_decoder = ImageDecoder::Make(
698 settings, runners, loop->GetTaskRunner(),
699 io_manager->GetWeakIOManager(), std::make_shared<fml::SyncSwitch>());
700
701 auto data = flutter::testing::OpenFixtureAsSkData("DashInNooglerHat.jpg");
702
703 ASSERT_TRUE(data);
704 ASSERT_GE(data->size(), 0u);
705
706 ImageGeneratorRegistry registry;
707 std::shared_ptr<ImageGenerator> generator =
709 ASSERT_TRUE(generator);
710
711 auto descriptor = fml::MakeRefCounted<ImageDescriptor>(
712 std::move(data), std::move(generator));
713
714 ImageDecoder::ImageResult callback = [&](const sk_sp<DlImage>& image,
715 const std::string& decode_error) {
716 ASSERT_TRUE(runners.GetUITaskRunner()->RunsTasksOnCurrentThread());
717 ASSERT_TRUE(image && image->skia_image());
718 runners.GetIOTaskRunner()->PostTask(release_io_manager);
719 };
720 image_decoder->Decode(
721 descriptor,
722 {.target_width = static_cast<uint32_t>(descriptor->width()),
723 .target_height = static_cast<uint32_t>(descriptor->height())},
724 callback);
725 };
726
727 auto set_up_io_manager_and_decode = [&]() {
728 io_manager =
729 std::make_unique<TestIOManager>(runners.GetIOTaskRunner(), false);
730 runners.GetUITaskRunner()->PostTask(decode_image);
731 };
732
733 runners.GetIOTaskRunner()->PostTask(set_up_io_manager_and_decode);
734
735 latch.Wait();
736}
737
738TEST_F(ImageDecoderFixtureTest, CanDecodeWithResizes) {
739 const auto image_dimensions =
740 SkImages::DeferredFromEncodedData(
741 flutter::testing::OpenFixtureAsSkData("DashInNooglerHat.jpg"))
742 ->dimensions();
743
744 ASSERT_FALSE(image_dimensions.isEmpty());
745
746 ASSERT_NE(image_dimensions.width(), image_dimensions.height());
747
749 TaskRunners runners(GetCurrentTestName(), // label
750 CreateNewThread("platform"), // platform
751 CreateNewThread("raster"), // raster
752 CreateNewThread("ui"), // ui
753 CreateNewThread("io") // io
754 );
755
757 std::unique_ptr<IOManager> io_manager;
758 std::unique_ptr<ImageDecoder> image_decoder;
759
760 // Setup the IO manager.
761 PostTaskSync(runners.GetIOTaskRunner(), [&]() {
762 io_manager = std::make_unique<TestIOManager>(runners.GetIOTaskRunner());
763 });
764
765 // Setup the image decoder.
766 PostTaskSync(runners.GetUITaskRunner(), [&]() {
767 Settings settings;
768 image_decoder = ImageDecoder::Make(settings, runners, loop->GetTaskRunner(),
769 io_manager->GetWeakIOManager(),
770 std::make_shared<fml::SyncSwitch>());
771 });
772
773 // Setup a generic decoding utility that gives us the final decoded size.
774 auto decoded_size = [&](uint32_t target_width,
775 uint32_t target_height) -> SkISize {
776 SkISize final_size = SkISize::MakeEmpty();
777 runners.GetUITaskRunner()->PostTask([&]() {
778 auto data = flutter::testing::OpenFixtureAsSkData("DashInNooglerHat.jpg");
779
780 ASSERT_TRUE(data);
781 ASSERT_GE(data->size(), 0u);
782
783 ImageGeneratorRegistry registry;
784 std::shared_ptr<ImageGenerator> generator =
786 ASSERT_TRUE(generator);
787
788 auto descriptor = fml::MakeRefCounted<ImageDescriptor>(
789 std::move(data), std::move(generator));
790
792 [&](const sk_sp<DlImage>& image, const std::string& decode_error) {
793 ASSERT_TRUE(runners.GetUITaskRunner()->RunsTasksOnCurrentThread());
794 ASSERT_TRUE(image && image->skia_image());
795 final_size = image->skia_image()->dimensions();
796 latch.Signal();
797 };
798 image_decoder->Decode(
799 descriptor,
800 {.target_width = target_width, .target_height = target_height},
801 callback);
802 });
803 latch.Wait();
804 return final_size;
805 };
806
807 ASSERT_EQ(SkISize::Make(3024, 4032), image_dimensions);
808 ASSERT_EQ(decoded_size(3024, 4032), image_dimensions);
809 ASSERT_EQ(decoded_size(100, 100), SkISize::Make(100, 100));
810
811 // Destroy the IO manager
812 PostTaskSync(runners.GetIOTaskRunner(), [&]() { io_manager.reset(); });
813
814 // Destroy the image decoder
815 PostTaskSync(runners.GetUITaskRunner(), [&]() { image_decoder.reset(); });
816}
817
818// Verifies https://skia-review.googlesource.com/c/skia/+/259161 is present in
819// Flutter.
820TEST(ImageDecoderTest,
821 VerifyCodecRepeatCountsForGifAndWebPAreConsistentWithLoopCounts) {
822 auto gif_mapping = flutter::testing::OpenFixtureAsSkData("hello_loop_2.gif");
823 auto webp_mapping =
824 flutter::testing::OpenFixtureAsSkData("hello_loop_2.webp");
825
826 ASSERT_TRUE(gif_mapping);
827 ASSERT_TRUE(webp_mapping);
828
829 ImageGeneratorRegistry registry;
830
831 auto gif_generator = registry.CreateCompatibleGenerator(gif_mapping);
832 auto webp_generator = registry.CreateCompatibleGenerator(webp_mapping);
833
834 ASSERT_TRUE(gif_generator);
835 ASSERT_TRUE(webp_generator);
836
837 // Both fixtures have a loop count of 2.
838 ASSERT_EQ(gif_generator->GetPlayCount(), static_cast<unsigned int>(2));
839 ASSERT_EQ(webp_generator->GetPlayCount(), static_cast<unsigned int>(2));
840}
841
842TEST(ImageDecoderTest, VerifySimpleDecoding) {
843 auto data = flutter::testing::OpenFixtureAsSkData("Horizontal.jpg");
844 auto image = SkImages::DeferredFromEncodedData(data);
845 ASSERT_TRUE(image != nullptr);
846 EXPECT_EQ(600, image->width());
847 EXPECT_EQ(200, image->height());
848
849 ImageGeneratorRegistry registry;
850 std::shared_ptr<ImageGenerator> generator =
852 ASSERT_TRUE(generator);
853
854 auto descriptor = fml::MakeRefCounted<ImageDescriptor>(std::move(data),
855 std::move(generator));
856 auto compressed_image = ImageDecoderSkia::ImageFromCompressedData(
857 descriptor.get(), 6, 2, fml::tracing::TraceFlow(""));
858 EXPECT_EQ(compressed_image->width(), 6);
859 EXPECT_EQ(compressed_image->height(), 2);
860 EXPECT_EQ(compressed_image->alphaType(), kOpaque_SkAlphaType);
861
862#if IMPELLER_SUPPORTS_RENDERING
863 std::shared_ptr<impeller::Capabilities> capabilities =
866 .Build();
867 std::shared_ptr<impeller::Capabilities> capabilities_no_blit =
870 .Build();
871 // Bitmap sizes reflect the original image size as resizing is done on the
872 // GPU if the src size is smaller than the max texture size.
873 std::shared_ptr<impeller::Allocator> allocator =
874 std::make_shared<impeller::TestImpellerAllocator>();
876 descriptor.get(), {.target_width = 6, .target_height = 2}, {1000, 1000},
877 /*supports_wide_gamut=*/false, capabilities, allocator);
878 ASSERT_TRUE(result_1.ok());
879 EXPECT_EQ(result_1->image_info.size.width, 75);
880 EXPECT_EQ(result_1->image_info.size.height, 25);
881
882 // Bitmap sizes reflect the scaled size if the source size is larger than
883 // max texture size even if destination size isn't max texture size.
885 descriptor.get(), {.target_width = 6, .target_height = 2}, {10, 10},
886 /*supports_wide_gamut=*/false, capabilities, allocator);
887 ASSERT_TRUE(result_2.ok());
888 EXPECT_EQ(result_2->image_info.size.width, 6);
889 EXPECT_EQ(result_2->image_info.size.height, 2);
890
891 // If the destination size is larger than the max texture size the image
892 // is scaled down.
894 descriptor.get(), {.target_width = 60, .target_height = 20}, {10, 10},
895 /*supports_wide_gamut=*/false, capabilities, allocator);
896 ASSERT_TRUE(result_3.ok());
897 EXPECT_EQ(result_3->image_info.size.width, 10);
898 EXPECT_EQ(result_3->image_info.size.height, 10);
899
900 // CPU resize is forced.
902 descriptor.get(), {.target_width = 6, .target_height = 2}, {1000, 1000},
903 /*supports_wide_gamut=*/false, capabilities_no_blit, allocator);
904 ASSERT_TRUE(result_4.ok());
905 EXPECT_EQ(result_4->image_info.size.width, 6);
906 EXPECT_EQ(result_4->image_info.size.height, 2);
907#endif // IMPELLER_SUPPORTS_RENDERING
908}
909
910TEST(ImageDecoderTest, ImagesWithTransparencyArePremulAlpha) {
911 auto data = flutter::testing::OpenFixtureAsSkData("heart_end.png");
912 ASSERT_TRUE(data);
913 ImageGeneratorRegistry registry;
914 std::shared_ptr<ImageGenerator> generator =
916 ASSERT_TRUE(generator);
917
918 auto descriptor = fml::MakeRefCounted<ImageDescriptor>(std::move(data),
919 std::move(generator));
920 auto compressed_image = ImageDecoderSkia::ImageFromCompressedData(
921 descriptor.get(), 250, 250, fml::tracing::TraceFlow(""));
922 ASSERT_TRUE(compressed_image);
923 ASSERT_EQ(compressed_image->width(), 250);
924 ASSERT_EQ(compressed_image->height(), 250);
925 ASSERT_EQ(compressed_image->alphaType(), kPremul_SkAlphaType);
926}
927
928TEST(ImageDecoderTest, VerifySubpixelDecodingPreservesExifOrientation) {
929 auto data = flutter::testing::OpenFixtureAsSkData("Horizontal.jpg");
930
931 ImageGeneratorRegistry registry;
932 std::shared_ptr<ImageGenerator> generator =
934 ASSERT_TRUE(generator);
935 auto descriptor =
936 fml::MakeRefCounted<ImageDescriptor>(data, std::move(generator));
937
938 // If Exif metadata is ignored, the height and width will be swapped because
939 // "Rotate 90 CW" is what is encoded there.
940 ASSERT_EQ(600, descriptor->width());
941 ASSERT_EQ(200, descriptor->height());
942
943 auto image = SkImages::DeferredFromEncodedData(data);
944 ASSERT_TRUE(image != nullptr);
945 ASSERT_EQ(600, image->width());
946 ASSERT_EQ(200, image->height());
947
948 auto decode = [descriptor](uint32_t target_width, uint32_t target_height) {
950 descriptor.get(), target_width, target_height,
952 };
953
954 auto expected_data = flutter::testing::OpenFixtureAsSkData("Horizontal.png");
955 ASSERT_TRUE(expected_data != nullptr);
956 ASSERT_FALSE(expected_data->isEmpty());
957
958 auto assert_image = [&](const auto& decoded_image,
959 const std::string& decode_error) {
960 ASSERT_EQ(decoded_image->dimensions(), SkISize::Make(300, 100));
961 sk_sp<SkData> encoded =
962 SkPngEncoder::Encode(nullptr, decoded_image.get(), {});
963 ASSERT_TRUE(encoded->equals(expected_data.get()));
964 };
965
966 assert_image(decode(300, 100), {});
967}
968
970 MultiFrameCodecCanBeCollectedBeforeIOTasksFinish) {
971 // This test verifies that the MultiFrameCodec safely shares state between
972 // tasks on the IO and UI runners, and does not allow unsafe memory access if
973 // the UI object is collected while the IO thread still has pending decode
974 // work. This could happen in a real application if the engine is collected
975 // while a multi-frame image is decoding. To exercise this, the test:
976 // - Starts a Dart VM
977 // - Latches the IO task runner
978 // - Create a MultiFrameCodec for an animated gif pointed to a callback
979 // in the Dart fixture
980 // - Calls getNextFrame on the UI task runner
981 // - Collects the MultiFrameCodec object before unlatching the IO task
982 // runner.
983 // - Unlatches the IO task runner
984 auto settings = CreateSettingsForFixture();
985 auto vm_ref = DartVMRef::Create(settings);
986 auto vm_data = vm_ref.GetVMData();
987
988 auto gif_mapping = flutter::testing::OpenFixtureAsSkData("hello_loop_2.gif");
989
990 ASSERT_TRUE(gif_mapping);
991
992 ImageGeneratorRegistry registry;
993 std::shared_ptr<ImageGenerator> gif_generator =
994 registry.CreateCompatibleGenerator(gif_mapping);
995 ASSERT_TRUE(gif_generator);
996
997 TaskRunners runners(GetCurrentTestName(), // label
998 CreateNewThread("platform"), // platform
999 CreateNewThread("raster"), // raster
1000 CreateNewThread("ui"), // ui
1001 CreateNewThread("io") // io
1002 );
1003
1005 std::unique_ptr<TestIOManager> io_manager;
1006
1007 // Setup the IO manager.
1008 PostTaskSync(runners.GetIOTaskRunner(), [&]() {
1009 io_manager = std::make_unique<TestIOManager>(runners.GetIOTaskRunner());
1010 });
1011
1012 auto isolate = RunDartCodeInIsolate(vm_ref, settings, runners, "main", {},
1014 io_manager->GetWeakIOManager());
1015
1016 // Latch the IO task runner.
1017 runners.GetIOTaskRunner()->PostTask([&]() { io_latch.Wait(); });
1018
1019 PostTaskSync(runners.GetUITaskRunner(), [&]() {
1020 fml::AutoResetWaitableEvent isolate_latch;
1021 fml::RefPtr<MultiFrameCodec> codec;
1022 EXPECT_TRUE(isolate->RunInIsolateScope([&]() -> bool {
1023 Dart_Handle library = Dart_RootLibrary();
1024 if (Dart_IsError(library)) {
1025 isolate_latch.Signal();
1026 return false;
1027 }
1028 Dart_Handle closure =
1029 Dart_GetField(library, Dart_NewStringFromCString("frameCallback"));
1030 if (Dart_IsError(closure) || !Dart_IsClosure(closure)) {
1031 isolate_latch.Signal();
1032 return false;
1033 }
1034
1035 codec = fml::MakeRefCounted<MultiFrameCodec>(std::move(gif_generator));
1036 codec->getNextFrame(closure);
1037 codec = nullptr;
1038 isolate_latch.Signal();
1039 return true;
1040 }));
1041 isolate_latch.Wait();
1042
1043 EXPECT_FALSE(codec);
1044
1045 io_latch.Signal();
1046 });
1047
1048 // Destroy the IO manager
1049 PostTaskSync(runners.GetIOTaskRunner(), [&]() { io_manager.reset(); });
1050}
1051
1053 auto context = std::make_shared<impeller::TestImpellerContext>();
1054 auto allocator = ImpellerAllocator(context->GetResourceAllocator());
1055
1056 EXPECT_FALSE(allocator.allocPixelRef(nullptr));
1057}
1058
1059} // namespace testing
1060} // namespace flutter
1061
1062// NOLINTEND(clang-analyzer-cplusplus.NewDeleteLeaks)
1063// NOLINTEND(clang-analyzer-core.StackAddressEscape)
static DartVMRef Create(const Settings &settings, fml::RefPtr< const DartSnapshot > vm_snapshot=nullptr, fml::RefPtr< const DartSnapshot > isolate_snapshot=nullptr)
std::function< void(sk_sp< DlImage >, std::string)> ImageResult
static std::unique_ptr< ImageDecoder > Make(const Settings &settings, const TaskRunners &runners, std::shared_ptr< fml::ConcurrentTaskRunner > concurrent_task_runner, const fml::WeakPtr< IOManager > &io_manager, const std::shared_ptr< fml::SyncSwitch > &gpu_disabled_switch)
static absl::StatusOr< DecompressResult > DecompressTexture(ImageDescriptor *descriptor, const ImageDecoder::Options &options, impeller::ISize max_texture_size, bool supports_wide_gamut, const std::shared_ptr< const impeller::Capabilities > &capabilities, const std::shared_ptr< impeller::Allocator > &allocator)
static void UploadTextureToPrivate(ImageResult result, const std::shared_ptr< impeller::Context > &context, const std::shared_ptr< impeller::DeviceBuffer > &buffer, const ImageInfo &image_info, const std::optional< SkImageInfo > &resize_info, const std::shared_ptr< const fml::SyncSwitch > &gpu_disabled_switch)
Create a device private texture from the provided host buffer.
static std::pair< sk_sp< DlImage >, std::string > UploadTextureToStorage(const std::shared_ptr< impeller::Context > &context, std::shared_ptr< SkBitmap > bitmap)
Create a texture from the provided bitmap.
static sk_sp< SkImage > ImageFromCompressedData(ImageDescriptor *descriptor, uint32_t target_width, uint32_t target_height, const fml::tracing::TraceFlow &flow)
static ImageInfo CreateImageInfo(const SkImageInfo &sk_image_info)
The minimal interface necessary for defining a decoder that can be used for both single and multi-fra...
Keeps a priority-ordered registry of image generator builders to be used when decoding images....
std::shared_ptr< ImageGenerator > CreateCompatibleGenerator(const sk_sp< SkData > &buffer)
Walks the list of image generator builders in descending priority order until a compatible ImageGener...
fml::RefPtr< fml::TaskRunner > GetUITaskRunner() const
fml::RefPtr< fml::TaskRunner > GetIOTaskRunner() const
fml::RefPtr< flutter::SkiaUnrefQueue > GetSkiaUnrefQueue() const override
std::shared_ptr< const fml::SyncSwitch > GetIsGpuDisabledSyncSwitch() override
fml::WeakPtr< IOManager > GetWeakIOManager() const override
std::shared_ptr< impeller::Context > GetImpellerContext() const override
Retrieve the impeller::Context.
TestIOManager(const fml::RefPtr< fml::TaskRunner > &task_runner, bool has_gpu_context=true)
fml::WeakPtr< GrDirectContext > GetResourceContext() const override
An Image generator that pretends it can't recognize the data it was given.
const SkImageInfo & GetInfo()
Returns basic information about the contents of the encoded image. This information can almost always...
unsigned int GetPlayCount() const
The number of times an animated image should play through before playback stops.
const ImageGenerator::FrameInfo GetFrameInfo(unsigned int frame_index)
Get information about a single frame in the context of a multi-frame image, useful for animation and ...
bool GetPixels(const SkImageInfo &info, void *pixels, size_t row_bytes, unsigned int frame_index, std::optional< unsigned int > prior_frame)
Decode the image into a given buffer. This method is currently always used for sub-pixel image decodi...
SkISize GetScaledDimensions(float scale)
Given a scale value, find the closest image size that can be used for efficiently decoding the image....
unsigned int GetFrameCount() const
Get the number of frames that the encoded image stores. This method is always expected to be called b...
static std::shared_ptr< ConcurrentMessageLoop > Create(size_t worker_count=std::thread::hardware_concurrency())
static void RunNowOrPostTask(const fml::RefPtr< fml::TaskRunner > &runner, const fml::closure &task)
virtual void PostTask(const fml::closure &task) override
virtual bool RunsTasksOnCurrentThread()
CapabilitiesBuilder & SetSupportsTextureToTextureBlits(bool value)
std::unique_ptr< Capabilities > Build()
To do anything rendering related with Impeller, you need a context.
Definition context.h:65
std::shared_ptr< CommandQueue > GetCommandQueue() const override
Return the graphics queue for submitting command buffers.
std::shared_ptr< ShaderLibrary > GetShaderLibrary() const override
Returns the library of shaders used to specify the programmable stages of a pipeline.
RuntimeStageBackend GetRuntimeStageBackend() const override
Retrieve the runtime stage for this context type.
std::shared_ptr< PipelineLibrary > GetPipelineLibrary() const override
Returns the library of pipelines used by render or compute commands.
void Shutdown() override
Force all pending asynchronous work to finish. This is achieved by deleting all owned concurrent mess...
std::shared_ptr< Allocator > GetResourceAllocator() const override
Returns the allocator used to create textures and buffers on the device.
bool IsValid() const override
Determines if a context is valid. If the caller ever receives an invalid context, they must discard i...
const std::shared_ptr< const Capabilities > & GetCapabilities() const override
Get the capabilities of Impeller context. All optionally supported feature of the platform,...
std::string DescribeGpuModel() const override
BackendType GetBackendType() const override
Get the graphics backend of an Impeller context.
std::shared_ptr< CommandBuffer > CreateCommandBuffer() const override
Create a new command buffer. Command buffers can be used to encode graphics, blit,...
void StoreTaskForGPU(const std::function< void()> &task, const std::function< void()> &failure) override
std::shared_ptr< SamplerLibrary > GetSamplerLibrary() const override
Returns the library of combined image samplers used in shaders.
FlutterVulkanImage * image
VkQueue queue
Definition main.cc:71
G_BEGIN_DECLS GBytes * message
FlutterDesktopBinaryReply callback
#define FML_CHECK(condition)
Definition logging.h:104
#define FML_UNREACHABLE()
Definition logging.h:128
#define FML_DISALLOW_COPY_AND_ASSIGN(TypeName)
Definition macros.h:27
std::shared_ptr< SkBitmap > bitmap
std::shared_ptr< ImpellerAllocator > allocator
std::string GetCurrentTestName()
Gets the name of the currently running test. This is useful in generating logs or assets based on tes...
Definition testing.cc:14
TEST_F(DisplayListTest, Defaults)
void PostTaskSync(const fml::RefPtr< fml::TaskRunner > &task_runner, const std::function< void()> &function)
std::string GetDefaultKernelFilePath()
Returns the default path to kernel_blob.bin. This file is within the directory returned by GetFixture...
Definition testing.cc:18
std::unique_ptr< AutoIsolateShutdown > RunDartCodeInIsolate(DartVMRef &vm_ref, const Settings &settings, const TaskRunners &task_runners, std::string entrypoint, const std::vector< std::string > &args, const std::string &kernel_file_path, fml::WeakPtr< IOManager > io_manager, std::unique_ptr< PlatformConfiguration > platform_configuration)
sk_sp< SkData > OpenFixtureAsSkData(const std::string &fixture_name)
Opens a fixture of the given file name and returns a Skia SkData holding its contents.
Definition testing.cc:63
TEST(NativeAssetsManagerTest, NoAvailableAssets)
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 manager
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
TEST_F(EngineAnimatorTest, AnimatorAcceptsMultipleRenders)
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 disable vm Disable the Dart VM Service The Dart VM Service is never available in release mode Bind to the IPv6 localhost address for the Dart VM Service Ignored if vm service host is set profile Make the profiler discard new samples once the profiler sample buffer is full When this flag is not the profiler sample buffer is used as a ring buffer
Definition switch_defs.h:98
ISize64 ISize
Definition size.h:162
Definition ref_ptr.h:261
Info about a single frame in the context of a multi-frame image, useful for animation and blending.