blob: c75ec5fef3074632a9d61609292fdf28a6045f38 [file] [log] [blame]
// Copyright 2017 The Chromium 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/codec.h"
#include "flutter/common/task_runners.h"
#include "flutter/glue/trace_event.h"
#include "flutter/lib/ui/painting/frame_info.h"
#include "lib/fxl/functional/make_copyable.h"
#include "lib/fxl/logging.h"
#include "lib/tonic/dart_binding_macros.h"
#include "lib/tonic/dart_library_natives.h"
#include "lib/tonic/dart_state.h"
#include "lib/tonic/logging/dart_invoke.h"
#include "lib/tonic/typed_data/uint8_list.h"
#include "third_party/skia/include/codec/SkCodec.h"
#include "third_party/skia/include/core/SkPixelRef.h"
#ifdef ERROR
#undef ERROR
#endif
using tonic::DartInvoke;
using tonic::DartPersistentValue;
using tonic::ToDart;
namespace blink {
namespace {
static constexpr const char* kInitCodecTraceTag = "InitCodec";
static constexpr const char* kCodecNextFrameTraceTag = "CodecNextFrame";
static void InvokeCodecCallback(fxl::RefPtr<Codec> codec,
std::unique_ptr<DartPersistentValue> callback,
size_t trace_id) {
tonic::DartState* dart_state = callback->dart_state().get();
if (!dart_state) {
TRACE_FLOW_END("flutter", kInitCodecTraceTag, trace_id);
return;
}
tonic::DartState::Scope scope(dart_state);
if (!codec) {
DartInvoke(callback->value(), {Dart_Null()});
} else {
DartInvoke(callback->value(), {ToDart(codec)});
}
TRACE_FLOW_END("flutter", kInitCodecTraceTag, trace_id);
}
static sk_sp<SkImage> DecodeImage(fml::WeakPtr<GrContext> context,
sk_sp<SkData> buffer,
size_t trace_id) {
TRACE_FLOW_STEP("flutter", kInitCodecTraceTag, trace_id);
TRACE_EVENT0("flutter", "DecodeImage");
if (buffer == nullptr || buffer->isEmpty()) {
return nullptr;
}
if (context) {
// This indicates that we do not want a "linear blending" decode.
sk_sp<SkColorSpace> dstColorSpace = nullptr;
return SkImage::MakeCrossContextFromEncoded(
context.get(), std::move(buffer), false, dstColorSpace.get(), true);
} else {
// Defer decoding until time of draw later on the GPU thread. Can happen
// when GL operations are currently forbidden such as in the background
// on iOS.
return SkImage::MakeFromEncoded(std::move(buffer));
}
}
fxl::RefPtr<Codec> InitCodec(fml::WeakPtr<GrContext> context,
sk_sp<SkData> buffer,
fxl::RefPtr<flow::SkiaUnrefQueue> unref_queue,
size_t trace_id) {
TRACE_FLOW_STEP("flutter", kInitCodecTraceTag, trace_id);
TRACE_EVENT0("blink", "InitCodec");
if (buffer == nullptr || buffer->isEmpty()) {
FXL_LOG(ERROR) << "InitCodec failed - buffer was empty ";
return nullptr;
}
std::unique_ptr<SkCodec> skCodec = SkCodec::MakeFromData(buffer);
if (!skCodec) {
FXL_LOG(ERROR) << "Failed decoding image. Data is either invalid, or it is "
"encoded using an unsupported format.";
return nullptr;
}
if (skCodec->getFrameCount() > 1) {
return fxl::MakeRefCounted<MultiFrameCodec>(std::move(skCodec));
}
auto skImage = DecodeImage(context, buffer, trace_id);
if (!skImage) {
FXL_LOG(ERROR) << "DecodeImage failed";
return nullptr;
}
auto image = CanvasImage::Create();
image->set_image({skImage, unref_queue});
auto frameInfo = fxl::MakeRefCounted<FrameInfo>(std::move(image), 0);
return fxl::MakeRefCounted<SingleFrameCodec>(std::move(frameInfo));
}
void InitCodecAndInvokeCodecCallback(
fxl::RefPtr<fxl::TaskRunner> ui_task_runner,
fml::WeakPtr<GrContext> context,
fxl::RefPtr<flow::SkiaUnrefQueue> unref_queue,
std::unique_ptr<DartPersistentValue> callback,
sk_sp<SkData> buffer,
size_t trace_id) {
auto codec =
InitCodec(context, std::move(buffer), std::move(unref_queue), trace_id);
ui_task_runner->PostTask(
fxl::MakeCopyable([callback = std::move(callback),
codec = std::move(codec), trace_id]() mutable {
InvokeCodecCallback(std::move(codec), std::move(callback), trace_id);
}));
}
void InstantiateImageCodec(Dart_NativeArguments args) {
static size_t trace_counter = 1;
const size_t trace_id = trace_counter++;
TRACE_FLOW_BEGIN("flutter", kInitCodecTraceTag, trace_id);
Dart_Handle exception = nullptr;
tonic::Uint8List list =
tonic::DartConverter<tonic::Uint8List>::FromArguments(args, 0, exception);
if (exception) {
TRACE_FLOW_END("flutter", kInitCodecTraceTag, trace_id);
Dart_SetReturnValue(args, exception);
return;
}
Dart_Handle callback_handle = Dart_GetNativeArgument(args, 1);
if (!Dart_IsClosure(callback_handle)) {
TRACE_FLOW_END("flutter", kInitCodecTraceTag, trace_id);
Dart_SetReturnValue(args, ToDart("Callback must be a function"));
return;
}
auto buffer = SkData::MakeWithCopy(list.data(), list.num_elements());
auto dart_state = UIDartState::Current();
const auto& task_runners = dart_state->GetTaskRunners();
task_runners.GetIOTaskRunner()->PostTask(fxl::MakeCopyable(
[callback = std::make_unique<DartPersistentValue>(
tonic::DartState::Current(), callback_handle),
buffer = std::move(buffer), trace_id,
ui_task_runner = task_runners.GetUITaskRunner(),
context = dart_state->GetResourceContext(),
queue = UIDartState::Current()->GetSkiaUnrefQueue()]() mutable {
InitCodecAndInvokeCodecCallback(std::move(ui_task_runner), context,
std::move(queue), std::move(callback),
std::move(buffer), trace_id);
}));
}
bool copy_to(SkBitmap* dst, SkColorType dstColorType, const SkBitmap& src) {
SkPixmap srcPM;
if (!src.peekPixels(&srcPM)) {
return false;
}
SkBitmap tmpDst;
SkImageInfo dstInfo = srcPM.info().makeColorType(dstColorType);
if (!tmpDst.setInfo(dstInfo)) {
return false;
}
if (!tmpDst.tryAllocPixels()) {
return false;
}
SkPixmap dstPM;
if (!tmpDst.peekPixels(&dstPM)) {
return false;
}
if (!srcPM.readPixels(dstPM)) {
return false;
}
dst->swap(tmpDst);
return true;
}
void InvokeNextFrameCallback(fxl::RefPtr<FrameInfo> frameInfo,
std::unique_ptr<DartPersistentValue> callback,
size_t trace_id) {
tonic::DartState* dart_state = callback->dart_state().get();
if (!dart_state) {
TRACE_FLOW_END("flutter", kCodecNextFrameTraceTag, trace_id);
return;
}
tonic::DartState::Scope scope(dart_state);
if (!frameInfo) {
DartInvoke(callback->value(), {Dart_Null()});
} else {
DartInvoke(callback->value(), {ToDart(frameInfo)});
}
TRACE_FLOW_END("flutter", kCodecNextFrameTraceTag, trace_id);
}
} // namespace
IMPLEMENT_WRAPPERTYPEINFO(ui, Codec);
#define FOR_EACH_BINDING(V) \
V(Codec, getNextFrame) \
V(Codec, frameCount) \
V(Codec, repetitionCount) \
V(Codec, dispose)
FOR_EACH_BINDING(DART_NATIVE_CALLBACK)
void Codec::dispose() {
ClearDartWrapper();
}
MultiFrameCodec::MultiFrameCodec(std::unique_ptr<SkCodec> codec)
: codec_(std::move(codec)) {
repetitionCount_ = codec_->getRepetitionCount();
frameInfos_ = codec_->getFrameInfo();
frameBitmaps_.resize(frameInfos_.size());
nextFrameIndex_ = 0;
}
sk_sp<SkImage> MultiFrameCodec::GetNextFrameImage(
fml::WeakPtr<GrContext> resourceContext) {
SkBitmap& bitmap = frameBitmaps_[nextFrameIndex_];
if (!bitmap.getPixels()) { // We haven't decoded this frame yet
const SkImageInfo info = codec_->getInfo().makeColorType(kN32_SkColorType);
bitmap.allocPixels(info);
SkCodec::Options options;
options.fFrameIndex = nextFrameIndex_;
const int requiredFrame = frameInfos_[nextFrameIndex_].fRequiredFrame;
if (requiredFrame != SkCodec::kNone) {
if (requiredFrame < 0 ||
static_cast<size_t>(requiredFrame) >= frameBitmaps_.size()) {
FXL_LOG(ERROR) << "Frame " << nextFrameIndex_ << " depends on frame "
<< requiredFrame << " which out of range (0,"
<< frameBitmaps_.size() << ").";
return NULL;
}
SkBitmap& requiredBitmap = frameBitmaps_[requiredFrame];
// For simplicity, do not try to cache old frames
if (requiredBitmap.getPixels() &&
copy_to(&bitmap, requiredBitmap.colorType(), requiredBitmap)) {
options.fPriorFrame = requiredFrame;
}
}
if (SkCodec::kSuccess != codec_->getPixels(info, bitmap.getPixels(),
bitmap.rowBytes(), &options)) {
FXL_LOG(ERROR) << "Could not getPixels for frame " << nextFrameIndex_;
return NULL;
}
}
if (resourceContext) {
SkPixmap pixmap(bitmap.info(), bitmap.pixelRef()->pixels(),
bitmap.pixelRef()->rowBytes());
// This indicates that we do not want a "linear blending" decode.
sk_sp<SkColorSpace> dstColorSpace = nullptr;
return SkImage::MakeCrossContextFromPixmap(resourceContext.get(), pixmap,
false, dstColorSpace.get());
} else {
// Defer decoding until time of draw later on the GPU thread. Can happen
// when GL operations are currently forbidden such as in the background
// on iOS.
return SkImage::MakeFromBitmap(bitmap);
}
}
void MultiFrameCodec::GetNextFrameAndInvokeCallback(
std::unique_ptr<DartPersistentValue> callback,
fxl::RefPtr<fxl::TaskRunner> ui_task_runner,
fml::WeakPtr<GrContext> resourceContext,
fxl::RefPtr<flow::SkiaUnrefQueue> unref_queue,
size_t trace_id) {
fxl::RefPtr<FrameInfo> frameInfo = NULL;
sk_sp<SkImage> skImage = GetNextFrameImage(resourceContext);
if (skImage) {
fxl::RefPtr<CanvasImage> image = CanvasImage::Create();
image->set_image({skImage, std::move(unref_queue)});
frameInfo = fxl::MakeRefCounted<FrameInfo>(
std::move(image), frameInfos_[nextFrameIndex_].fDuration);
}
nextFrameIndex_ = (nextFrameIndex_ + 1) % frameInfos_.size();
ui_task_runner->PostTask(fxl::MakeCopyable(
[callback = std::move(callback), frameInfo, trace_id]() mutable {
InvokeNextFrameCallback(frameInfo, std::move(callback), trace_id);
}));
TRACE_FLOW_END("flutter", kCodecNextFrameTraceTag, trace_id);
}
Dart_Handle MultiFrameCodec::getNextFrame(Dart_Handle callback_handle) {
static size_t trace_counter = 1;
const size_t trace_id = trace_counter++;
TRACE_FLOW_BEGIN("flutter", kCodecNextFrameTraceTag, trace_id);
if (!Dart_IsClosure(callback_handle)) {
TRACE_FLOW_END("flutter", kCodecNextFrameTraceTag, trace_id);
return ToDart("Callback must be a function");
}
auto dart_state = UIDartState::Current();
const auto& task_runners = dart_state->GetTaskRunners();
task_runners.GetIOTaskRunner()->PostTask(fxl::MakeCopyable(
[callback = std::make_unique<DartPersistentValue>(
tonic::DartState::Current(), callback_handle),
this, trace_id, ui_task_runner = task_runners.GetUITaskRunner(),
queue = UIDartState::Current()->GetSkiaUnrefQueue(),
context = dart_state->GetResourceContext()]() mutable {
GetNextFrameAndInvokeCallback(std::move(callback),
std::move(ui_task_runner), context,
std::move(queue), trace_id);
}));
return Dart_Null();
}
Dart_Handle SingleFrameCodec::getNextFrame(Dart_Handle callback_handle) {
if (!Dart_IsClosure(callback_handle)) {
return ToDart("Callback must be a function");
}
auto callback = std::make_unique<DartPersistentValue>(
tonic::DartState::Current(), callback_handle);
tonic::DartState* dart_state = callback->dart_state().get();
if (!dart_state) {
return ToDart("Invalid dart state");
}
tonic::DartState::Scope scope(dart_state);
DartInvoke(callback->value(), {ToDart(frame_)});
return Dart_Null();
}
void Codec::RegisterNatives(tonic::DartLibraryNatives* natives) {
natives->Register({
{"instantiateImageCodec", InstantiateImageCodec, 2, true},
});
natives->Register({FOR_EACH_BINDING(DART_REGISTER_NATIVE)});
}
} // namespace blink