| // 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 "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; |
| default: |
| return std::nullopt; |
| } |
| } |
| |
| sk_sp<SkImage> ConvertBufferToSkImage( |
| const std::shared_ptr<impeller::DeviceBuffer>& buffer, |
| SkColorType color_type, |
| SkISize dimensions) { |
| auto buffer_view = buffer->AsBufferView(); |
| |
| 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_view.contents, |
| 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 = std::move(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(); |
| } |
| }); |
| } 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.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; |
| } |
| auto sk_image = ConvertBufferToSkImage(buffer, color_type, dimensions); |
| encode_task(sk_image); |
| }; |
| |
| if (!command_buffer->SubmitCommands(completion)) { |
| FML_LOG(ERROR) << "Failed to submit commands."; |
| } |
| } |
| |
| 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 |