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

Commit 1095102

Browse files
[Impeller] Return image decoder error messages to the Dart API (#42175)
Fixes flutter/flutter#127061 See flutter/flutter#126768
1 parent ac29a74 commit 1095102

11 files changed

+178
-114
lines changed

lib/ui/fixtures/ui_test.dart

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -248,12 +248,12 @@ void createPath() {
248248
external void _validatePath(Path path);
249249

250250
@pragma('vm:entry-point')
251-
void frameCallback(Object? image, int durationMilliseconds) {
252-
validateFrameCallback(image, durationMilliseconds);
251+
void frameCallback(Object? image, int durationMilliseconds, String decodeError) {
252+
validateFrameCallback(image, durationMilliseconds, decodeError);
253253
}
254254

255255
@pragma('vm:external-name', 'ValidateFrameCallback')
256-
external void validateFrameCallback(Object? image, int durationMilliseconds);
256+
external void validateFrameCallback(Object? image, int durationMilliseconds, String decodeError);
257257

258258
@pragma('vm:entry-point')
259259
void platformMessagePortResponseTest() async {

lib/ui/painting.dart

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2125,9 +2125,12 @@ base class _NativeCodec extends NativeFieldWrapperClass1 implements Codec {
21252125
@override
21262126
Future<FrameInfo> getNextFrame() async {
21272127
final Completer<FrameInfo> completer = Completer<FrameInfo>.sync();
2128-
final String? error = _getNextFrame((_Image? image, int durationMilliseconds) {
2128+
final String? error = _getNextFrame((_Image? image, int durationMilliseconds, String decodeError) {
21292129
if (image == null) {
2130-
completer.completeError(Exception('Codec failed to produce an image, possibly due to invalid image data.'));
2130+
if (decodeError.isEmpty) {
2131+
decodeError = 'Codec failed to produce an image, possibly due to invalid image data.';
2132+
}
2133+
completer.completeError(Exception(decodeError));
21312134
} else {
21322135
completer.complete(FrameInfo._(
21332136
image: Image._(image, image.width, image.height),
@@ -2143,7 +2146,7 @@ base class _NativeCodec extends NativeFieldWrapperClass1 implements Codec {
21432146

21442147
/// Returns an error message on failure, null on success.
21452148
@Native<Handle Function(Pointer<Void>, Handle)>(symbol: 'Codec::getNextFrame')
2146-
external String? _getNextFrame(void Function(_Image?, int) callback);
2149+
external String? _getNextFrame(void Function(_Image?, int, String) callback);
21472150

21482151
@override
21492152
@Native<Void Function(Pointer<Void>)>(symbol: 'Codec::dispose')

lib/ui/painting/image_decoder.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ class ImageDecoder {
3131

3232
virtual ~ImageDecoder();
3333

34-
using ImageResult = std::function<void(sk_sp<DlImage>)>;
34+
using ImageResult = std::function<void(sk_sp<DlImage>, std::string)>;
3535

3636
// Takes an image descriptor and returns a handle to a texture resident on the
3737
// GPU. All image decompression and resizes are done on a worker thread

lib/ui/painting/image_decoder_impeller.cc

Lines changed: 99 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -107,16 +107,17 @@ static SkAlphaType ChooseCompatibleAlphaType(SkAlphaType type) {
107107
return type;
108108
}
109109

110-
std::optional<DecompressResult> ImageDecoderImpeller::DecompressTexture(
110+
DecompressResult ImageDecoderImpeller::DecompressTexture(
111111
ImageDescriptor* descriptor,
112112
SkISize target_size,
113113
impeller::ISize max_texture_size,
114114
bool supports_wide_gamut,
115115
const std::shared_ptr<impeller::Allocator>& allocator) {
116116
TRACE_EVENT0("impeller", __FUNCTION__);
117117
if (!descriptor) {
118-
FML_DLOG(ERROR) << "Invalid descriptor.";
119-
return std::nullopt;
118+
std::string decode_error("Invalid descriptor (should never happen)");
119+
FML_DLOG(ERROR) << decode_error;
120+
return DecompressResult{.decode_error = decode_error};
120121
}
121122

122123
target_size.set(std::min(static_cast<int32_t>(max_texture_size.width),
@@ -162,8 +163,11 @@ std::optional<DecompressResult> ImageDecoderImpeller::DecompressTexture(
162163
const auto pixel_format =
163164
impeller::skia_conversions::ToPixelFormat(image_info.colorType());
164165
if (!pixel_format.has_value()) {
165-
FML_DLOG(ERROR) << "Codec pixel format not supported by Impeller.";
166-
return std::nullopt;
166+
std::string decode_error(impeller::SPrintF(
167+
"Codec pixel format is not supported (SkColorType=%d)",
168+
image_info.colorType()));
169+
FML_DLOG(ERROR) << decode_error;
170+
return DecompressResult{.decode_error = decode_error};
167171
}
168172

169173
auto bitmap = std::make_shared<SkBitmap>();
@@ -172,14 +176,16 @@ std::optional<DecompressResult> ImageDecoderImpeller::DecompressTexture(
172176

173177
if (descriptor->is_compressed()) {
174178
if (!bitmap->tryAllocPixels(bitmap_allocator.get())) {
175-
FML_DLOG(ERROR)
176-
<< "Could not allocate intermediate for image decompression.";
177-
return std::nullopt;
179+
std::string decode_error(
180+
"Could not allocate intermediate for image decompression.");
181+
FML_DLOG(ERROR) << decode_error;
182+
return DecompressResult{.decode_error = decode_error};
178183
}
179184
// Decode the image into the image generator's closest supported size.
180185
if (!descriptor->get_pixels(bitmap->pixmap())) {
181-
FML_DLOG(ERROR) << "Could not decompress image.";
182-
return std::nullopt;
186+
std::string decode_error("Could not decompress image.");
187+
FML_DLOG(ERROR) << decode_error;
188+
return DecompressResult{.decode_error = decode_error};
183189
}
184190
} else {
185191
auto temp_bitmap = std::make_shared<SkBitmap>();
@@ -189,9 +195,10 @@ std::optional<DecompressResult> ImageDecoderImpeller::DecompressTexture(
189195
temp_bitmap->setPixelRef(pixel_ref, 0, 0);
190196

191197
if (!bitmap->tryAllocPixels(bitmap_allocator.get())) {
192-
FML_DLOG(ERROR)
193-
<< "Could not allocate intermediate for pixel conversion.";
194-
return std::nullopt;
198+
std::string decode_error(
199+
"Could not allocate intermediate for pixel conversion.");
200+
FML_DLOG(ERROR) << decode_error;
201+
return DecompressResult{.decode_error = decode_error};
195202
}
196203
temp_bitmap->readPixels(bitmap->pixmap());
197204
bitmap->setImmutable();
@@ -200,7 +207,7 @@ std::optional<DecompressResult> ImageDecoderImpeller::DecompressTexture(
200207
if (bitmap->dimensions() == target_size) {
201208
auto buffer = bitmap_allocator->GetDeviceBuffer();
202209
if (!buffer.has_value()) {
203-
return std::nullopt;
210+
return DecompressResult{.decode_error = "Unable to get device buffer"};
204211
}
205212
return DecompressResult{.device_buffer = buffer.value(),
206213
.sk_bitmap = bitmap,
@@ -218,9 +225,10 @@ std::optional<DecompressResult> ImageDecoderImpeller::DecompressTexture(
218225
auto scaled_allocator = std::make_shared<ImpellerAllocator>(allocator);
219226
scaled_bitmap->setInfo(scaled_image_info);
220227
if (!scaled_bitmap->tryAllocPixels(scaled_allocator.get())) {
221-
FML_LOG(ERROR)
222-
<< "Could not allocate scaled bitmap for image decompression.";
223-
return std::nullopt;
228+
std::string decode_error(
229+
"Could not allocate scaled bitmap for image decompression.");
230+
FML_DLOG(ERROR) << decode_error;
231+
return DecompressResult{.decode_error = decode_error};
224232
}
225233
if (!bitmap->pixmap().scalePixels(
226234
scaled_bitmap->pixmap(),
@@ -231,26 +239,32 @@ std::optional<DecompressResult> ImageDecoderImpeller::DecompressTexture(
231239

232240
auto buffer = scaled_allocator->GetDeviceBuffer();
233241
if (!buffer.has_value()) {
234-
return std::nullopt;
242+
return DecompressResult{.decode_error = "Unable to get device buffer"};
235243
}
236244
return DecompressResult{.device_buffer = buffer.value(),
237245
.sk_bitmap = scaled_bitmap,
238246
.image_info = scaled_bitmap->info()};
239247
}
240248

241-
sk_sp<DlImage> ImageDecoderImpeller::UploadTextureToPrivate(
249+
std::pair<sk_sp<DlImage>, std::string>
250+
ImageDecoderImpeller::UploadTextureToPrivate(
242251
const std::shared_ptr<impeller::Context>& context,
243252
const std::shared_ptr<impeller::DeviceBuffer>& buffer,
244253
const SkImageInfo& image_info) {
245254
TRACE_EVENT0("impeller", __FUNCTION__);
246-
if (!context || !buffer) {
247-
return nullptr;
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");
248260
}
249261
const auto pixel_format =
250262
impeller::skia_conversions::ToPixelFormat(image_info.colorType());
251263
if (!pixel_format) {
252-
FML_DLOG(ERROR) << "Pixel format unsupported by Impeller.";
253-
return nullptr;
264+
std::string decode_error(impeller::SPrintF(
265+
"Unsupported pixel format (SkColorType=%d)", image_info.colorType()));
266+
FML_DLOG(ERROR) << decode_error;
267+
return std::make_pair(nullptr, decode_error);
254268
}
255269

256270
impeller::TextureDescriptor texture_descriptor;
@@ -263,24 +277,29 @@ sk_sp<DlImage> ImageDecoderImpeller::UploadTextureToPrivate(
263277
auto dest_texture =
264278
context->GetResourceAllocator()->CreateTexture(texture_descriptor);
265279
if (!dest_texture) {
266-
FML_DLOG(ERROR) << "Could not create Impeller texture.";
267-
return nullptr;
280+
std::string decode_error("Could not create Impeller texture.");
281+
FML_DLOG(ERROR) << decode_error;
282+
return std::make_pair(nullptr, decode_error);
268283
}
269284

270285
dest_texture->SetLabel(
271286
impeller::SPrintF("ui.Image(%p)", dest_texture.get()).c_str());
272287

273288
auto command_buffer = context->CreateCommandBuffer();
274289
if (!command_buffer) {
275-
FML_DLOG(ERROR) << "Could not create command buffer for mipmap generation.";
276-
return nullptr;
290+
std::string decode_error(
291+
"Could not create command buffer for mipmap generation.");
292+
FML_DLOG(ERROR) << decode_error;
293+
return std::make_pair(nullptr, decode_error);
277294
}
278295
command_buffer->SetLabel("Mipmap Command Buffer");
279296

280297
auto blit_pass = command_buffer->CreateBlitPass();
281298
if (!blit_pass) {
282-
FML_DLOG(ERROR) << "Could not create blit pass for mipmap generation.";
283-
return nullptr;
299+
std::string decode_error(
300+
"Could not create blit pass for mipmap generation.");
301+
FML_DLOG(ERROR) << decode_error;
302+
return std::make_pair(nullptr, decode_error);
284303
}
285304
blit_pass->SetLabel("Mipmap Blit Pass");
286305
blit_pass->AddCopy(buffer->AsBufferView(), dest_texture);
@@ -290,27 +309,35 @@ sk_sp<DlImage> ImageDecoderImpeller::UploadTextureToPrivate(
290309

291310
blit_pass->EncodeCommands(context->GetResourceAllocator());
292311
if (!command_buffer->SubmitCommands()) {
293-
FML_DLOG(ERROR) << "Failed to submit blit pass command buffer.";
294-
return nullptr;
312+
std::string decode_error("Failed to submit blit pass command buffer.");
313+
FML_DLOG(ERROR) << decode_error;
314+
return std::make_pair(nullptr, decode_error);
295315
}
296316

297-
return impeller::DlImageImpeller::Make(std::move(dest_texture));
317+
return std::make_pair(
318+
impeller::DlImageImpeller::Make(std::move(dest_texture)), std::string());
298319
}
299320

300-
sk_sp<DlImage> ImageDecoderImpeller::UploadTextureToShared(
321+
std::pair<sk_sp<DlImage>, std::string>
322+
ImageDecoderImpeller::UploadTextureToShared(
301323
const std::shared_ptr<impeller::Context>& context,
302324
std::shared_ptr<SkBitmap> bitmap,
303325
bool create_mips) {
304326
TRACE_EVENT0("impeller", __FUNCTION__);
305-
if (!context || !bitmap) {
306-
return nullptr;
327+
if (!context) {
328+
return std::make_pair(nullptr, "No Impeller context is available");
329+
}
330+
if (!bitmap) {
331+
return std::make_pair(nullptr, "No texture bitmap is available");
307332
}
308333
const auto image_info = bitmap->info();
309334
const auto pixel_format =
310335
impeller::skia_conversions::ToPixelFormat(image_info.colorType());
311336
if (!pixel_format) {
312-
FML_DLOG(ERROR) << "Pixel format unsupported by Impeller.";
313-
return nullptr;
337+
std::string decode_error(impeller::SPrintF(
338+
"Unsupported pixel format (SkColorType=%d)", image_info.colorType()));
339+
FML_DLOG(ERROR) << decode_error;
340+
return std::make_pair(nullptr, decode_error);
314341
}
315342

316343
impeller::TextureDescriptor texture_descriptor;
@@ -323,8 +350,9 @@ sk_sp<DlImage> ImageDecoderImpeller::UploadTextureToShared(
323350
auto texture =
324351
context->GetResourceAllocator()->CreateTexture(texture_descriptor);
325352
if (!texture) {
326-
FML_DLOG(ERROR) << "Could not create Impeller texture.";
327-
return nullptr;
353+
std::string decode_error("Could not create Impeller texture.");
354+
FML_DLOG(ERROR) << decode_error;
355+
return std::make_pair(nullptr, decode_error);
328356
}
329357

330358
auto mapping = std::make_shared<fml::NonOwnedMapping>(
@@ -334,38 +362,44 @@ sk_sp<DlImage> ImageDecoderImpeller::UploadTextureToShared(
334362
);
335363

336364
if (!texture->SetContents(mapping)) {
337-
FML_DLOG(ERROR) << "Could not copy contents into Impeller texture.";
338-
return nullptr;
365+
std::string decode_error("Could not copy contents into Impeller texture.");
366+
FML_DLOG(ERROR) << decode_error;
367+
return std::make_pair(nullptr, decode_error);
339368
}
340369

341370
texture->SetLabel(impeller::SPrintF("ui.Image(%p)", texture.get()).c_str());
342371

343372
if (texture_descriptor.mip_count > 1u && create_mips) {
344373
auto command_buffer = context->CreateCommandBuffer();
345374
if (!command_buffer) {
346-
FML_DLOG(ERROR)
347-
<< "Could not create command buffer for mipmap generation.";
348-
return nullptr;
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);
349379
}
350380
command_buffer->SetLabel("Mipmap Command Buffer");
351381

352382
auto blit_pass = command_buffer->CreateBlitPass();
353383
if (!blit_pass) {
354-
FML_DLOG(ERROR) << "Could not create blit pass for mipmap generation.";
355-
return nullptr;
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);
356388
}
357389
blit_pass->SetLabel("Mipmap Blit Pass");
358390
blit_pass->GenerateMipmap(texture);
359391

360392
blit_pass->EncodeCommands(context->GetResourceAllocator());
361393
if (!command_buffer->SubmitCommands()) {
362-
FML_DLOG(ERROR) << "Failed to submit blit pass command buffer.";
363-
return nullptr;
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);
364397
}
365398
command_buffer->WaitUntilScheduled();
366399
}
367400

368-
return impeller::DlImageImpeller::Make(std::move(texture));
401+
return std::make_pair(impeller::DlImageImpeller::Make(std::move(texture)),
402+
std::string());
369403
}
370404

371405
// |ImageDecoder|
@@ -382,10 +416,10 @@ void ImageDecoderImpeller::Decode(fml::RefPtr<ImageDescriptor> descriptor,
382416
ImageResult result = [p_result, //
383417
raw_descriptor, //
384418
ui_runner = runners_.GetUITaskRunner() //
385-
](auto image) {
386-
ui_runner->PostTask([raw_descriptor, p_result, image]() {
419+
](auto image, auto decode_error) {
420+
ui_runner->PostTask([raw_descriptor, p_result, image, decode_error]() {
387421
raw_descriptor->Release();
388-
p_result(std::move(image));
422+
p_result(std::move(image), decode_error);
389423
});
390424
};
391425

@@ -398,7 +432,7 @@ void ImageDecoderImpeller::Decode(fml::RefPtr<ImageDescriptor> descriptor,
398432
supports_wide_gamut = supports_wide_gamut_ //
399433
]() {
400434
if (!context) {
401-
result(nullptr);
435+
result(nullptr, "No Impeller context is available");
402436
return;
403437
}
404438
auto max_size_supported =
@@ -408,21 +442,24 @@ void ImageDecoderImpeller::Decode(fml::RefPtr<ImageDescriptor> descriptor,
408442
auto bitmap_result = DecompressTexture(
409443
raw_descriptor, target_size, max_size_supported,
410444
supports_wide_gamut, context->GetResourceAllocator());
411-
if (!bitmap_result.has_value()) {
412-
result(nullptr);
445+
if (!bitmap_result.device_buffer) {
446+
result(nullptr, bitmap_result.decode_error);
413447
return;
414448
}
415449
auto upload_texture_and_invoke_result = [result, context,
416-
bitmap_result =
417-
bitmap_result.value()]() {
418-
// TODO(jonahwilliams): remove ifdef once blit from buffer to texture is
419-
// implemented on other platforms.
450+
bitmap_result]() {
451+
// TODO(jonahwilliams): remove ifdef once blit from buffer to
452+
// texture is implemented on other platforms.
453+
sk_sp<DlImage> image;
454+
std::string decode_error;
420455
#if (FML_OS_IOS && !TARGET_IPHONE_SIMULATOR)
421-
result(UploadTextureToPrivate(context, bitmap_result.device_buffer,
422-
bitmap_result.image_info));
456+
std::tie(image, decode_error) = UploadTextureToPrivate(
457+
context, bitmap_result.device_buffer, bitmap_result.image_info);
423458
#else
424-
result(UploadTextureToShared(context, bitmap_result.sk_bitmap));
459+
std::tie(image, decode_error) =
460+
UploadTextureToShared(context, bitmap_result.sk_bitmap);
425461
#endif
462+
result(image, decode_error);
426463
};
427464
// TODO(jonahwilliams): https://github.com/flutter/flutter/issues/123058
428465
// Technically we don't need to post tasks to the io runner, but without

0 commit comments

Comments
 (0)