Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit 882703b

Browse files
authored
[Impeller] Avoid encoding metal commands while the GPU is unavailable when decoding images. (#42349)
Fixes flutter/flutter#126878 This disables device private upload on iOS when backgrounded, and disables mipmap generation when backgrounded. We don't have a good way to test the core problem in this repo because it only reproduces on physical iOS hardware - simulators don't really care if you do this stuff in the background. I'll write a devicelab test in the framework to capture this. In the mean time it can be reviewed. We could consider making the IOManager a shared_ptr instead of having an fml::WeakPtr and that'd clean up some of the extra arguments to engine construction - or we could consider vending the sync switch from impeller::Context unconditionally, but it's pretty iOS specific...
1 parent 858d975 commit 882703b

File tree

12 files changed

+222
-107
lines changed

12 files changed

+222
-107
lines changed

impeller/renderer/backend/metal/render_pass_mtl.mm

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -385,10 +385,13 @@ static bool Bind(PassBindingsCache& pass,
385385
}
386386

387387
if (texture.NeedsMipmapGeneration()) {
388+
// TODO(127697): generate mips when the GPU is available on iOS.
389+
#if !FML_OS_IOS
388390
VALIDATION_LOG
389391
<< "Texture at binding index " << bind_index
390392
<< " has a mip count > 1, but the mipmap has not been generated.";
391393
return false;
394+
#endif // !FML_OS_IOS
392395
}
393396

394397
return pass.SetTexture(stage, bind_index,

lib/ui/painting/image_decoder.cc

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,16 @@ std::unique_ptr<ImageDecoder> ImageDecoder::Make(
1616
const Settings& settings,
1717
const TaskRunners& runners,
1818
std::shared_ptr<fml::ConcurrentTaskRunner> concurrent_task_runner,
19-
fml::WeakPtr<IOManager> io_manager) {
19+
fml::WeakPtr<IOManager> io_manager,
20+
const std::shared_ptr<fml::SyncSwitch>& gpu_disabled_switch) {
2021
#if IMPELLER_SUPPORTS_RENDERING
2122
if (settings.enable_impeller) {
2223
return std::make_unique<ImageDecoderImpeller>(
2324
runners, //
2425
std::move(concurrent_task_runner), //
2526
std::move(io_manager), //
26-
settings.enable_wide_gamut);
27+
settings.enable_wide_gamut, //
28+
gpu_disabled_switch);
2729
}
2830
#endif // IMPELLER_SUPPORTS_RENDERING
2931
return std::make_unique<ImageDecoderSkia>(

lib/ui/painting/image_decoder.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ class ImageDecoder {
2727
const Settings& settings,
2828
const TaskRunners& runners,
2929
std::shared_ptr<fml::ConcurrentTaskRunner> concurrent_task_runner,
30-
fml::WeakPtr<IOManager> io_manager);
30+
fml::WeakPtr<IOManager> io_manager,
31+
const std::shared_ptr<fml::SyncSwitch>& gpu_disabled_switch);
3132

3233
virtual ~ImageDecoder();
3334

lib/ui/painting/image_decoder_impeller.cc

Lines changed: 85 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -80,9 +80,11 @@ ImageDecoderImpeller::ImageDecoderImpeller(
8080
const TaskRunners& runners,
8181
std::shared_ptr<fml::ConcurrentTaskRunner> concurrent_task_runner,
8282
const fml::WeakPtr<IOManager>& io_manager,
83-
bool supports_wide_gamut)
83+
bool supports_wide_gamut,
84+
const std::shared_ptr<fml::SyncSwitch>& gpu_disabled_switch)
8485
: ImageDecoder(runners, std::move(concurrent_task_runner), io_manager),
85-
supports_wide_gamut_(supports_wide_gamut) {
86+
supports_wide_gamut_(supports_wide_gamut),
87+
gpu_disabled_switch_(gpu_disabled_switch) {
8688
std::promise<std::shared_ptr<impeller::Context>> context_promise;
8789
context_ = context_promise.get_future();
8890
runners_.GetIOTaskRunner()->PostTask(fml::MakeCopyable(
@@ -246,18 +248,11 @@ DecompressResult ImageDecoderImpeller::DecompressTexture(
246248
.image_info = scaled_bitmap->info()};
247249
}
248250

249-
std::pair<sk_sp<DlImage>, std::string>
250-
ImageDecoderImpeller::UploadTextureToPrivate(
251+
/// Only call this method if the GPU is available.
252+
static std::pair<sk_sp<DlImage>, std::string> UnsafeUploadTextureToPrivate(
251253
const std::shared_ptr<impeller::Context>& context,
252254
const std::shared_ptr<impeller::DeviceBuffer>& buffer,
253255
const SkImageInfo& image_info) {
254-
TRACE_EVENT0("impeller", __FUNCTION__);
255-
if (!context) {
256-
return std::make_pair(nullptr, "No Impeller context is available");
257-
}
258-
if (!buffer) {
259-
return std::make_pair(nullptr, "No Impeller device buffer is available");
260-
}
261256
const auto pixel_format =
262257
impeller::skia_conversions::ToPixelFormat(image_info.colorType());
263258
if (!pixel_format) {
@@ -318,10 +313,40 @@ ImageDecoderImpeller::UploadTextureToPrivate(
318313
impeller::DlImageImpeller::Make(std::move(dest_texture)), std::string());
319314
}
320315

316+
std::pair<sk_sp<DlImage>, std::string>
317+
ImageDecoderImpeller::UploadTextureToPrivate(
318+
const std::shared_ptr<impeller::Context>& context,
319+
const std::shared_ptr<impeller::DeviceBuffer>& buffer,
320+
const SkImageInfo& image_info,
321+
std::shared_ptr<SkBitmap> bitmap,
322+
const std::shared_ptr<fml::SyncSwitch>& gpu_disabled_switch) {
323+
TRACE_EVENT0("impeller", __FUNCTION__);
324+
if (!context) {
325+
return std::make_pair(nullptr, "No Impeller context is available");
326+
}
327+
if (!buffer) {
328+
return std::make_pair(nullptr, "No Impeller device buffer is available");
329+
}
330+
331+
std::pair<sk_sp<DlImage>, std::string> result;
332+
gpu_disabled_switch->Execute(
333+
fml::SyncSwitch::Handlers()
334+
.SetIfFalse([&result, context, buffer, image_info] {
335+
result = UnsafeUploadTextureToPrivate(context, buffer, image_info);
336+
})
337+
.SetIfTrue([&result, context, bitmap, gpu_disabled_switch] {
338+
// create_mips is false because we already know the GPU is disabled.
339+
result = UploadTextureToShared(context, bitmap, gpu_disabled_switch,
340+
/*create_mips=*/false);
341+
}));
342+
return result;
343+
}
344+
321345
std::pair<sk_sp<DlImage>, std::string>
322346
ImageDecoderImpeller::UploadTextureToShared(
323347
const std::shared_ptr<impeller::Context>& context,
324348
std::shared_ptr<SkBitmap> bitmap,
349+
const std::shared_ptr<fml::SyncSwitch>& gpu_disabled_switch,
325350
bool create_mips) {
326351
TRACE_EVENT0("impeller", __FUNCTION__);
327352
if (!context) {
@@ -370,32 +395,40 @@ ImageDecoderImpeller::UploadTextureToShared(
370395
texture->SetLabel(impeller::SPrintF("ui.Image(%p)", texture.get()).c_str());
371396

372397
if (texture_descriptor.mip_count > 1u && create_mips) {
373-
auto command_buffer = context->CreateCommandBuffer();
374-
if (!command_buffer) {
375-
std::string decode_error(
376-
"Could not create command buffer for mipmap generation.");
377-
FML_DLOG(ERROR) << decode_error;
378-
return std::make_pair(nullptr, decode_error);
398+
std::optional<std::string> decode_error;
399+
400+
// The only platform that needs mipmapping unconditionally is GL.
401+
// GL based platforms never disable GPU access.
402+
// This is only really needed for iOS.
403+
gpu_disabled_switch->Execute(fml::SyncSwitch::Handlers().SetIfFalse(
404+
[context, &texture, &decode_error] {
405+
auto command_buffer = context->CreateCommandBuffer();
406+
if (!command_buffer) {
407+
decode_error =
408+
"Could not create command buffer for mipmap generation.";
409+
return;
410+
}
411+
command_buffer->SetLabel("Mipmap Command Buffer");
412+
413+
auto blit_pass = command_buffer->CreateBlitPass();
414+
if (!blit_pass) {
415+
decode_error = "Could not create blit pass for mipmap generation.";
416+
return;
417+
}
418+
blit_pass->SetLabel("Mipmap Blit Pass");
419+
blit_pass->GenerateMipmap(texture);
420+
421+
blit_pass->EncodeCommands(context->GetResourceAllocator());
422+
if (!command_buffer->SubmitCommands()) {
423+
decode_error = "Failed to submit blit pass command buffer.";
424+
return;
425+
}
426+
command_buffer->WaitUntilScheduled();
427+
}));
428+
if (decode_error.has_value()) {
429+
FML_DLOG(ERROR) << decode_error.value();
430+
return std::make_pair(nullptr, decode_error.value());
379431
}
380-
command_buffer->SetLabel("Mipmap Command Buffer");
381-
382-
auto blit_pass = command_buffer->CreateBlitPass();
383-
if (!blit_pass) {
384-
std::string decode_error(
385-
"Could not create blit pass for mipmap generation.");
386-
FML_DLOG(ERROR) << decode_error;
387-
return std::make_pair(nullptr, decode_error);
388-
}
389-
blit_pass->SetLabel("Mipmap Blit Pass");
390-
blit_pass->GenerateMipmap(texture);
391-
392-
blit_pass->EncodeCommands(context->GetResourceAllocator());
393-
if (!command_buffer->SubmitCommands()) {
394-
std::string decode_error("Failed to submit blit pass command buffer.");
395-
FML_DLOG(ERROR) << decode_error;
396-
return std::make_pair(nullptr, decode_error);
397-
}
398-
command_buffer->WaitUntilScheduled();
399432
}
400433

401434
return std::make_pair(impeller::DlImageImpeller::Make(std::move(texture)),
@@ -429,8 +462,8 @@ void ImageDecoderImpeller::Decode(fml::RefPtr<ImageDescriptor> descriptor,
429462
target_size = SkISize::Make(target_width, target_height), //
430463
io_runner = runners_.GetIOTaskRunner(), //
431464
result,
432-
supports_wide_gamut = supports_wide_gamut_ //
433-
]() {
465+
supports_wide_gamut = supports_wide_gamut_, //
466+
gpu_disabled_switch = gpu_disabled_switch_]() {
434467
if (!context) {
435468
result(nullptr, "No Impeller context is available");
436469
return;
@@ -446,24 +479,28 @@ void ImageDecoderImpeller::Decode(fml::RefPtr<ImageDescriptor> descriptor,
446479
result(nullptr, bitmap_result.decode_error);
447480
return;
448481
}
449-
auto upload_texture_and_invoke_result = [result, context,
450-
bitmap_result]() {
451-
// TODO(jonahwilliams): remove ifdef once blit from buffer to
452-
// texture is implemented on other platforms.
482+
auto upload_texture_and_invoke_result = [result, context, bitmap_result,
483+
gpu_disabled_switch]() {
484+
// TODO(jonahwilliams): remove ifdef once blit from buffer
485+
// to texture is implemented on other platforms.
453486
sk_sp<DlImage> image;
454487
std::string decode_error;
488+
455489
#ifdef FML_OS_IOS
456490
std::tie(image, decode_error) = UploadTextureToPrivate(
457-
context, bitmap_result.device_buffer, bitmap_result.image_info);
491+
context, bitmap_result.device_buffer, bitmap_result.image_info,
492+
bitmap_result.sk_bitmap, gpu_disabled_switch);
458493
#else
459494
std::tie(image, decode_error) =
460-
UploadTextureToShared(context, bitmap_result.sk_bitmap);
461-
#endif
495+
UploadTextureToShared(context, bitmap_result.sk_bitmap,
496+
gpu_disabled_switch, /*create_mips=*/true);
497+
#endif // FML_OS_IOS
462498
result(image, decode_error);
463499
};
464-
// TODO(jonahwilliams): https://github.com/flutter/flutter/issues/123058
465-
// Technically we don't need to post tasks to the io runner, but without
466-
// this forced serialization we can end up overloading the GPU and/or
500+
// TODO(jonahwilliams):
501+
// https://github.com/flutter/flutter/issues/123058 Technically we
502+
// don't need to post tasks to the io runner, but without this
503+
// forced serialization we can end up overloading the GPU and/or
467504
// competing with raster workloads.
468505
io_runner->PostTask(upload_texture_and_invoke_result);
469506
});

lib/ui/painting/image_decoder_impeller.h

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,8 @@ class ImageDecoderImpeller final : public ImageDecoder {
5050
const TaskRunners& runners,
5151
std::shared_ptr<fml::ConcurrentTaskRunner> concurrent_task_runner,
5252
const fml::WeakPtr<IOManager>& io_manager,
53-
bool supports_wide_gamut);
53+
bool supports_wide_gamut,
54+
const std::shared_ptr<fml::SyncSwitch>& gpu_disabled_switch);
5455

5556
~ImageDecoderImpeller() override;
5657

@@ -72,27 +73,35 @@ class ImageDecoderImpeller final : public ImageDecoder {
7273
/// @param context The Impeller graphics context.
7374
/// @param buffer A host buffer containing the image to be uploaded.
7475
/// @param image_info Format information about the particular image.
76+
/// @param bitmap A bitmap containg the image to be uploaded.
77+
/// @param gpu_disabled_switch Whether the GPU is available command encoding.
7578
/// @return A DlImage.
7679
static std::pair<sk_sp<DlImage>, std::string> UploadTextureToPrivate(
7780
const std::shared_ptr<impeller::Context>& context,
7881
const std::shared_ptr<impeller::DeviceBuffer>& buffer,
79-
const SkImageInfo& image_info);
82+
const SkImageInfo& image_info,
83+
std::shared_ptr<SkBitmap> bitmap,
84+
const std::shared_ptr<fml::SyncSwitch>& gpu_disabled_switch);
8085

8186
/// @brief Create a host visible texture from the provided bitmap.
8287
/// @param context The Impeller graphics context.
8388
/// @param bitmap A bitmap containg the image to be uploaded.
8489
/// @param create_mips Whether mipmaps should be generated for the given
8590
/// image.
91+
/// @param gpu_disabled_switch Whether the GPU is available for mipmap
92+
/// creation.
8693
/// @return A DlImage.
8794
static std::pair<sk_sp<DlImage>, std::string> UploadTextureToShared(
8895
const std::shared_ptr<impeller::Context>& context,
8996
std::shared_ptr<SkBitmap> bitmap,
97+
const std::shared_ptr<fml::SyncSwitch>& gpu_disabled_switch,
9098
bool create_mips = true);
9199

92100
private:
93101
using FutureContext = std::shared_future<std::shared_ptr<impeller::Context>>;
94102
FutureContext context_;
95103
const bool supports_wide_gamut_;
104+
std::shared_ptr<fml::SyncSwitch> gpu_disabled_switch_;
96105

97106
FML_DISALLOW_COPY_AND_ASSIGN(ImageDecoderImpeller);
98107
};

lib/ui/painting/image_decoder_unittests.cc

Lines changed: 46 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -138,9 +138,12 @@ class TestImpellerContext : public impeller::Context {
138138
}
139139

140140
std::shared_ptr<CommandBuffer> CreateCommandBuffer() const override {
141+
command_buffer_count_ += 1;
141142
return nullptr;
142143
}
143144

145+
mutable size_t command_buffer_count_ = 0;
146+
144147
private:
145148
std::shared_ptr<const Capabilities> capabilities_;
146149
};
@@ -280,7 +283,8 @@ TEST_F(ImageDecoderFixtureTest, CanCreateImageDecoder) {
280283
TestIOManager manager(runners.GetIOTaskRunner());
281284
Settings settings;
282285
auto decoder = ImageDecoder::Make(settings, runners, loop->GetTaskRunner(),
283-
manager.GetWeakIOManager());
286+
manager.GetWeakIOManager(),
287+
std::make_shared<fml::SyncSwitch>());
284288
ASSERT_NE(decoder, nullptr);
285289
});
286290
}
@@ -331,7 +335,8 @@ TEST_F(ImageDecoderFixtureTest, InvalidImageResultsError) {
331335
TestIOManager manager(runners.GetIOTaskRunner());
332336
Settings settings;
333337
auto decoder = ImageDecoder::Make(settings, runners, loop->GetTaskRunner(),
334-
manager.GetWeakIOManager());
338+
manager.GetWeakIOManager(),
339+
std::make_shared<fml::SyncSwitch>());
335340

336341
auto data = OpenFixtureAsSkData("ThisDoesNotExist.jpg");
337342
ASSERT_FALSE(data);
@@ -370,9 +375,9 @@ TEST_F(ImageDecoderFixtureTest, ValidImageResultsInSuccess) {
370375
};
371376
auto decode_image = [&]() {
372377
Settings settings;
373-
std::unique_ptr<ImageDecoder> image_decoder =
374-
ImageDecoder::Make(settings, runners, loop->GetTaskRunner(),
375-
io_manager->GetWeakIOManager());
378+
std::unique_ptr<ImageDecoder> image_decoder = ImageDecoder::Make(
379+
settings, runners, loop->GetTaskRunner(),
380+
io_manager->GetWeakIOManager(), std::make_shared<fml::SyncSwitch>());
376381

377382
auto data = OpenFixtureAsSkData("DashInNooglerHat.jpg");
378383

@@ -426,6 +431,34 @@ float HalfToFloat(uint16_t half) {
426431
}
427432
} // namespace
428433

434+
TEST_F(ImageDecoderFixtureTest, ImpellerUploadToSharedNoGpu) {
435+
#if !IMPELLER_SUPPORTS_RENDERING
436+
GTEST_SKIP() << "Impeller only test.";
437+
#endif // IMPELLER_SUPPORTS_RENDERING
438+
439+
auto no_gpu_access_context =
440+
std::make_shared<impeller::TestImpellerContext>();
441+
auto gpu_disabled_switch = std::make_shared<fml::SyncSwitch>(true);
442+
443+
auto info = SkImageInfo::Make(10, 10, SkColorType::kRGBA_8888_SkColorType,
444+
SkAlphaType::kPremul_SkAlphaType);
445+
auto bitmap = std::make_shared<SkBitmap>();
446+
bitmap->allocPixels(info, 10 * 4);
447+
impeller::DeviceBufferDescriptor desc;
448+
desc.size = bitmap->computeByteSize();
449+
auto buffer = std::make_shared<impeller::TestImpellerDeviceBuffer>(desc);
450+
451+
auto result = ImageDecoderImpeller::UploadTextureToPrivate(
452+
no_gpu_access_context, buffer, info, bitmap, gpu_disabled_switch);
453+
ASSERT_EQ(no_gpu_access_context->command_buffer_count_, 0ul);
454+
ASSERT_EQ(result.second, "");
455+
456+
result = ImageDecoderImpeller::UploadTextureToShared(
457+
no_gpu_access_context, bitmap, gpu_disabled_switch, true);
458+
ASSERT_EQ(no_gpu_access_context->command_buffer_count_, 0ul);
459+
ASSERT_EQ(result.second, "");
460+
}
461+
429462
TEST_F(ImageDecoderFixtureTest, ImpellerNullColorspace) {
430463
auto info = SkImageInfo::Make(10, 10, SkColorType::kRGBA_8888_SkColorType,
431464
SkAlphaType::kPremul_SkAlphaType);
@@ -642,9 +675,9 @@ TEST_F(ImageDecoderFixtureTest, ExifDataIsRespectedOnDecode) {
642675
SkISize decoded_size = SkISize::MakeEmpty();
643676
auto decode_image = [&]() {
644677
Settings settings;
645-
std::unique_ptr<ImageDecoder> image_decoder =
646-
ImageDecoder::Make(settings, runners, loop->GetTaskRunner(),
647-
io_manager->GetWeakIOManager());
678+
std::unique_ptr<ImageDecoder> image_decoder = ImageDecoder::Make(
679+
settings, runners, loop->GetTaskRunner(),
680+
io_manager->GetWeakIOManager(), std::make_shared<fml::SyncSwitch>());
648681

649682
auto data = OpenFixtureAsSkData("Horizontal.jpg");
650683

@@ -703,9 +736,9 @@ TEST_F(ImageDecoderFixtureTest, CanDecodeWithoutAGPUContext) {
703736

704737
auto decode_image = [&]() {
705738
Settings settings;
706-
std::unique_ptr<ImageDecoder> image_decoder =
707-
ImageDecoder::Make(settings, runners, loop->GetTaskRunner(),
708-
io_manager->GetWeakIOManager());
739+
std::unique_ptr<ImageDecoder> image_decoder = ImageDecoder::Make(
740+
settings, runners, loop->GetTaskRunner(),
741+
io_manager->GetWeakIOManager(), std::make_shared<fml::SyncSwitch>());
709742

710743
auto data = OpenFixtureAsSkData("DashInNooglerHat.jpg");
711744

@@ -771,7 +804,8 @@ TEST_F(ImageDecoderFixtureTest, CanDecodeWithResizes) {
771804
PostTaskSync(runners.GetUITaskRunner(), [&]() {
772805
Settings settings;
773806
image_decoder = ImageDecoder::Make(settings, runners, loop->GetTaskRunner(),
774-
io_manager->GetWeakIOManager());
807+
io_manager->GetWeakIOManager(),
808+
std::make_shared<fml::SyncSwitch>());
775809
});
776810

777811
// Setup a generic decoding utility that gives us the final decoded size.

0 commit comments

Comments
 (0)