|  | // Copyright 2013 The Flutter Authors. All rights reserved. | 
|  | // Use of this source code is governed by a BSD-style license that can be | 
|  | // found in the LICENSE file. | 
|  |  | 
|  | #include "flutter/lib/ui/painting/image_encoding_impeller.h" | 
|  |  | 
|  | #include "flutter/lib/ui/painting/image.h" | 
|  | #include "fml/status.h" | 
|  | #include "impeller/core/device_buffer.h" | 
|  | #include "impeller/core/formats.h" | 
|  | #include "impeller/renderer/command_buffer.h" | 
|  | #include "impeller/renderer/context.h" | 
|  | #include "third_party/skia/include/core/SkBitmap.h" | 
|  | #include "third_party/skia/include/core/SkImage.h" | 
|  |  | 
|  | namespace flutter { | 
|  | namespace { | 
|  |  | 
|  | std::optional<SkColorType> ToSkColorType(impeller::PixelFormat format) { | 
|  | switch (format) { | 
|  | case impeller::PixelFormat::kR8G8B8A8UNormInt: | 
|  | return SkColorType::kRGBA_8888_SkColorType; | 
|  | case impeller::PixelFormat::kR16G16B16A16Float: | 
|  | return SkColorType::kRGBA_F16_SkColorType; | 
|  | case impeller::PixelFormat::kB8G8R8A8UNormInt: | 
|  | return SkColorType::kBGRA_8888_SkColorType; | 
|  | case impeller::PixelFormat::kB10G10R10XR: | 
|  | return SkColorType::kBGR_101010x_XR_SkColorType; | 
|  | case impeller::PixelFormat::kB10G10R10A10XR: | 
|  | return SkColorType::kBGRA_10101010_XR_SkColorType; | 
|  | default: | 
|  | return std::nullopt; | 
|  | } | 
|  | } | 
|  |  | 
|  | sk_sp<SkImage> ConvertBufferToSkImage( | 
|  | const std::shared_ptr<impeller::DeviceBuffer>& buffer, | 
|  | SkColorType color_type, | 
|  | SkISize dimensions) { | 
|  | SkImageInfo image_info = SkImageInfo::Make(dimensions, color_type, | 
|  | SkAlphaType::kPremul_SkAlphaType); | 
|  | SkBitmap bitmap; | 
|  | auto func = [](void* addr, void* context) { | 
|  | auto buffer = | 
|  | static_cast<std::shared_ptr<impeller::DeviceBuffer>*>(context); | 
|  | buffer->reset(); | 
|  | delete buffer; | 
|  | }; | 
|  | auto bytes_per_pixel = image_info.bytesPerPixel(); | 
|  | bitmap.installPixels(image_info, buffer->OnGetContents(), | 
|  | dimensions.width() * bytes_per_pixel, func, | 
|  | new std::shared_ptr<impeller::DeviceBuffer>(buffer)); | 
|  | bitmap.setImmutable(); | 
|  |  | 
|  | sk_sp<SkImage> raster_image = SkImages::RasterFromBitmap(bitmap); | 
|  | return raster_image; | 
|  | } | 
|  |  | 
|  | [[nodiscard]] fml::Status DoConvertImageToRasterImpeller( | 
|  | const sk_sp<DlImage>& dl_image, | 
|  | const std::function<void(fml::StatusOr<sk_sp<SkImage>>)>& encode_task, | 
|  | const std::shared_ptr<const fml::SyncSwitch>& is_gpu_disabled_sync_switch, | 
|  | const std::shared_ptr<impeller::Context>& impeller_context) { | 
|  | fml::Status result; | 
|  | is_gpu_disabled_sync_switch->Execute( | 
|  | fml::SyncSwitch::Handlers() | 
|  | .SetIfTrue([&result] { | 
|  | result = | 
|  | fml::Status(fml::StatusCode::kUnavailable, "GPU unavailable."); | 
|  | }) | 
|  | .SetIfFalse([&dl_image, &encode_task, &impeller_context] { | 
|  | ImageEncodingImpeller::ConvertDlImageToSkImage( | 
|  | dl_image, encode_task, impeller_context); | 
|  | })); | 
|  | return result; | 
|  | } | 
|  |  | 
|  | /// Same as `DoConvertImageToRasterImpeller` but it will attempt to retry the | 
|  | /// operation if `DoConvertImageToRasterImpeller` returns kUnavailable when the | 
|  | /// GPU becomes available again. | 
|  | void DoConvertImageToRasterImpellerWithRetry( | 
|  | const sk_sp<DlImage>& dl_image, | 
|  | std::function<void(fml::StatusOr<sk_sp<SkImage>>)>&& encode_task, | 
|  | const std::shared_ptr<const fml::SyncSwitch>& is_gpu_disabled_sync_switch, | 
|  | const std::shared_ptr<impeller::Context>& impeller_context, | 
|  | const fml::RefPtr<fml::TaskRunner>& retry_runner) { | 
|  | fml::Status status = DoConvertImageToRasterImpeller( | 
|  | dl_image, encode_task, is_gpu_disabled_sync_switch, impeller_context); | 
|  | if (!status.ok()) { | 
|  | // If the conversion failed because of the GPU is unavailable, store the | 
|  | // task on the Context so it can be executed when the GPU becomes available. | 
|  | if (status.code() == fml::StatusCode::kUnavailable) { | 
|  | impeller_context->StoreTaskForGPU( | 
|  | [dl_image, encode_task, is_gpu_disabled_sync_switch, impeller_context, | 
|  | retry_runner]() mutable { | 
|  | auto retry_task = [dl_image, encode_task = std::move(encode_task), | 
|  | is_gpu_disabled_sync_switch, impeller_context] { | 
|  | fml::Status retry_status = DoConvertImageToRasterImpeller( | 
|  | dl_image, encode_task, is_gpu_disabled_sync_switch, | 
|  | impeller_context); | 
|  | if (!retry_status.ok()) { | 
|  | // The retry failed for some reason, maybe the GPU became | 
|  | // unavailable again. Don't retry again, just fail in this case. | 
|  | encode_task(retry_status); | 
|  | } | 
|  | }; | 
|  | // If a `retry_runner` is specified, post the retry to it, otherwise | 
|  | // execute it directly. | 
|  | if (retry_runner) { | 
|  | retry_runner->PostTask(retry_task); | 
|  | } else { | 
|  | retry_task(); | 
|  | } | 
|  | }, | 
|  | [encode_task]() { | 
|  | encode_task( | 
|  | fml::Status(fml::StatusCode::kUnavailable, "GPU unavailable.")); | 
|  | }); | 
|  | } else { | 
|  | // Pass on errors that are not `kUnavailable`. | 
|  | encode_task(status); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | }  // namespace | 
|  |  | 
|  | void ImageEncodingImpeller::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) { | 
|  | auto texture = dl_image->impeller_texture(); | 
|  |  | 
|  | if (impeller_context == nullptr) { | 
|  | encode_task(fml::Status(fml::StatusCode::kFailedPrecondition, | 
|  | "Impeller context was null.")); | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (texture == nullptr) { | 
|  | encode_task( | 
|  | fml::Status(fml::StatusCode::kFailedPrecondition, "Image was null.")); | 
|  | return; | 
|  | } | 
|  |  | 
|  | auto dimensions = dl_image->dimensions(); | 
|  | auto color_type = ToSkColorType(texture->GetTextureDescriptor().format); | 
|  |  | 
|  | if (dimensions.isEmpty()) { | 
|  | encode_task(fml::Status(fml::StatusCode::kFailedPrecondition, | 
|  | "Image dimensions were empty.")); | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (!color_type.has_value()) { | 
|  | encode_task(fml::Status(fml::StatusCode::kUnimplemented, | 
|  | "Failed to get color type from pixel format.")); | 
|  | return; | 
|  | } | 
|  |  | 
|  | impeller::DeviceBufferDescriptor buffer_desc; | 
|  | buffer_desc.storage_mode = impeller::StorageMode::kHostVisible; | 
|  | buffer_desc.readback = true;  // set to false for testing. | 
|  | buffer_desc.size = | 
|  | texture->GetTextureDescriptor().GetByteSizeOfBaseMipLevel(); | 
|  | auto buffer = | 
|  | impeller_context->GetResourceAllocator()->CreateBuffer(buffer_desc); | 
|  | auto command_buffer = impeller_context->CreateCommandBuffer(); | 
|  | command_buffer->SetLabel("BlitTextureToBuffer Command Buffer"); | 
|  | auto pass = command_buffer->CreateBlitPass(); | 
|  | pass->SetLabel("BlitTextureToBuffer Blit Pass"); | 
|  | pass->AddCopy(texture, buffer); | 
|  | pass->EncodeCommands(impeller_context->GetResourceAllocator()); | 
|  | auto completion = [buffer, color_type = color_type.value(), dimensions, | 
|  | encode_task = std::move(encode_task)]( | 
|  | impeller::CommandBuffer::Status status) { | 
|  | if (status != impeller::CommandBuffer::Status::kCompleted) { | 
|  | encode_task(fml::Status(fml::StatusCode::kUnknown, "")); | 
|  | return; | 
|  | } | 
|  | buffer->Invalidate(); | 
|  | auto sk_image = ConvertBufferToSkImage(buffer, color_type, dimensions); | 
|  | encode_task(sk_image); | 
|  | }; | 
|  |  | 
|  | if (!impeller_context->GetCommandQueue() | 
|  | ->Submit({command_buffer}, completion) | 
|  | .ok()) { | 
|  | FML_LOG(ERROR) << "Failed to submit commands."; | 
|  | } | 
|  |  | 
|  | impeller_context->DisposeThreadLocalCachedResources(); | 
|  | } | 
|  |  | 
|  | void ImageEncodingImpeller::ConvertImageToRaster( | 
|  | const sk_sp<DlImage>& dl_image, | 
|  | std::function<void(fml::StatusOr<sk_sp<SkImage>>)> encode_task, | 
|  | const fml::RefPtr<fml::TaskRunner>& raster_task_runner, | 
|  | const fml::RefPtr<fml::TaskRunner>& io_task_runner, | 
|  | const std::shared_ptr<const fml::SyncSwitch>& is_gpu_disabled_sync_switch, | 
|  | const std::shared_ptr<impeller::Context>& impeller_context) { | 
|  | auto original_encode_task = std::move(encode_task); | 
|  | encode_task = [original_encode_task = std::move(original_encode_task), | 
|  | io_task_runner](fml::StatusOr<sk_sp<SkImage>> image) mutable { | 
|  | fml::TaskRunner::RunNowOrPostTask( | 
|  | io_task_runner, | 
|  | [original_encode_task = std::move(original_encode_task), | 
|  | image = std::move(image)]() { original_encode_task(image); }); | 
|  | }; | 
|  |  | 
|  | if (dl_image->owning_context() != DlImage::OwningContext::kRaster) { | 
|  | DoConvertImageToRasterImpellerWithRetry(dl_image, std::move(encode_task), | 
|  | is_gpu_disabled_sync_switch, | 
|  | impeller_context, | 
|  | /*retry_runner=*/nullptr); | 
|  | return; | 
|  | } | 
|  |  | 
|  | raster_task_runner->PostTask([dl_image, encode_task = std::move(encode_task), | 
|  | io_task_runner, is_gpu_disabled_sync_switch, | 
|  | impeller_context, | 
|  | raster_task_runner]() mutable { | 
|  | DoConvertImageToRasterImpellerWithRetry( | 
|  | dl_image, std::move(encode_task), is_gpu_disabled_sync_switch, | 
|  | impeller_context, raster_task_runner); | 
|  | }); | 
|  | } | 
|  |  | 
|  | int ImageEncodingImpeller::GetColorSpace( | 
|  | const std::shared_ptr<impeller::Texture>& texture) { | 
|  | const impeller::TextureDescriptor& desc = texture->GetTextureDescriptor(); | 
|  | switch (desc.format) { | 
|  | case impeller::PixelFormat::kB10G10R10XR:  // intentional_fallthrough | 
|  | case impeller::PixelFormat::kR16G16B16A16Float: | 
|  | return ColorSpace::kExtendedSRGB; | 
|  | default: | 
|  | return ColorSpace::kSRGB; | 
|  | } | 
|  | } | 
|  |  | 
|  | }  // namespace flutter |