blob: 6eb3c201d2944b983c91ff3c529b6e9119c12558 [file] [log] [blame]
// 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) {
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 = 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.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.";
}
}
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