Skip to content

Commit

Permalink
Move Picture.toImage rasterization to the GPU thread (flutter#7442)
Browse files Browse the repository at this point in the history
Reuses the implementation that was previously done for Scene.toImage
(see flutter/engine@20c805c)

This introduces a breaking API change:
Picture.toImage is now asynchronous and returns a Future<Image>

Fixes flutter#23621
  • Loading branch information
jason-simmons committed Jan 14, 2019
1 parent fea645b commit 82097a4
Show file tree
Hide file tree
Showing 4 changed files with 105 additions and 80 deletions.
74 changes: 2 additions & 72 deletions lib/ui/compositing/scene.cc
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,15 @@

#include "flutter/lib/ui/compositing/scene.h"

#include "flutter/fml/make_copyable.h"
#include "flutter/fml/trace_event.h"
#include "flutter/lib/ui/painting/image.h"
#include "flutter/lib/ui/painting/picture.h"
#include "third_party/skia/include/core/SkImageInfo.h"
#include "third_party/skia/include/core/SkSurface.h"
#include "third_party/tonic/converter/dart_converter.h"
#include "third_party/tonic/dart_args.h"
#include "third_party/tonic/dart_binding_macros.h"
#include "third_party/tonic/dart_library_natives.h"
#include "third_party/tonic/dart_persistent_value.h"
#include "third_party/tonic/logging/dart_invoke.h"

namespace blink {

Expand Down Expand Up @@ -57,85 +55,17 @@ Dart_Handle Scene::toImage(uint32_t width,
uint32_t height,
Dart_Handle raw_image_callback) {
TRACE_EVENT0("flutter", "Scene::toImage");
if (Dart_IsNull(raw_image_callback) || !Dart_IsClosure(raw_image_callback)) {
return tonic::ToDart("Image callback was invalid");
}

if (!m_layerTree) {
return tonic::ToDart("Scene did not contain a layer tree.");
}

if (width == 0 || height == 0) {
return tonic::ToDart("Image dimensions for scene were invalid.");
}

auto* dart_state = UIDartState::Current();
auto image_callback = std::make_unique<tonic::DartPersistentValue>(
dart_state, raw_image_callback);
auto unref_queue = dart_state->GetSkiaUnrefQueue();
auto ui_task_runner = dart_state->GetTaskRunners().GetUITaskRunner();
auto gpu_task_runner = dart_state->GetTaskRunners().GetGPUTaskRunner();
auto snapshot_delegate = dart_state->GetSnapshotDelegate();

// We can't create an image on this task runner because we don't have a
// graphics context. Even if we did, it would be slow anyway. Also, this
// thread owns the sole reference to the layer tree. So we flatten the layer
// tree into a picture and use that as the thread transport mechanism.

auto picture_bounds = SkISize::Make(width, height);
auto picture = m_layerTree->Flatten(SkRect::MakeWH(width, height));

if (!picture) {
// Already in Dart scope.
return tonic::ToDart("Could not flatten scene into a layer tree.");
}

auto ui_task = fml::MakeCopyable([ui_task_runner,
image_callback = std::move(image_callback),
unref_queue](
sk_sp<SkImage> raster_image) mutable {
// Send the raster image back to the UI thread for submission to the
// framework.
ui_task_runner->PostTask(fml::MakeCopyable([raster_image,
image_callback =
std::move(image_callback),
unref_queue]() mutable {
auto dart_state = image_callback->dart_state().lock();
if (!dart_state) {
// The root isolate could have died in the meantime.
return;
}
tonic::DartState::Scope scope(dart_state);

if (!raster_image) {
tonic::DartInvoke(image_callback->Get(), {Dart_Null()});
return;
}

auto dart_image = CanvasImage::Create();
dart_image->set_image({std::move(raster_image), std::move(unref_queue)});
auto* raw_dart_image = tonic::ToDart(std::move(dart_image));

// All done!
tonic::DartInvoke(image_callback->Get(), {raw_dart_image});
}));
});

auto gpu_task = fml::MakeCopyable([gpu_task_runner, picture, picture_bounds,
snapshot_delegate, ui_task]() {
gpu_task_runner->PostTask([snapshot_delegate, picture, picture_bounds,
ui_task]() {
// Snapshot the picture on the GPU thread. This thread has access to the
// GPU contexts that may contain the sole references to texture backed
// images in the picture.
ui_task(snapshot_delegate->MakeRasterSnapshot(picture, picture_bounds));
});
});

// Kick things off on the GPU.
gpu_task();

return Dart_Null();
return Picture::RasterizeToImage(picture, width, height, raw_image_callback);
}

std::unique_ptr<flow::LayerTree> Scene::takeLayerTree() {
Expand Down
10 changes: 9 additions & 1 deletion lib/ui/painting.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3593,7 +3593,15 @@ class Picture extends NativeFieldWrapperClass2 {
///
/// Although the image is returned synchronously, the picture is actually
/// rasterized the first time the image is drawn and then cached.
Image toImage(int width, int height) native 'Picture_toImage';
Future<Image> toImage(int width, int height) {
if (width <= 0 || height <= 0)
throw new Exception('Invalid image dimensions.');
return _futurize(
(_Callback<Image> callback) => _toImage(width, height, callback)
);
}

String _toImage(int width, int height, _Callback<Image> callback) native 'Picture_toImage';

/// Release the resources used by this object. The object is no longer usable
/// after this method is called.
Expand Down
92 changes: 86 additions & 6 deletions lib/ui/painting/picture.cc
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,16 @@

#include "flutter/lib/ui/painting/picture.h"

#include "flutter/fml/make_copyable.h"
#include "flutter/lib/ui/painting/canvas.h"
#include "flutter/lib/ui/ui_dart_state.h"
#include "third_party/skia/include/core/SkImage.h"
#include "third_party/tonic/converter/dart_converter.h"
#include "third_party/tonic/dart_args.h"
#include "third_party/tonic/dart_binding_macros.h"
#include "third_party/tonic/dart_library_natives.h"
#include "third_party/tonic/dart_persistent_value.h"
#include "third_party/tonic/logging/dart_invoke.h"

namespace blink {

Expand All @@ -32,12 +35,14 @@ Picture::Picture(flow::SkiaGPUObject<SkPicture> picture)

Picture::~Picture() = default;

fml::RefPtr<CanvasImage> Picture::toImage(int width, int height) {
fml::RefPtr<CanvasImage> image = CanvasImage::Create();
image->set_image(UIDartState::CreateGPUObject(SkImage::MakeFromPicture(
picture_.get(), SkISize::Make(width, height), nullptr, nullptr,
SkImage::BitDepth::kU8, SkColorSpace::MakeSRGB())));
return image;
Dart_Handle Picture::toImage(uint32_t width,
uint32_t height,
Dart_Handle raw_image_callback) {
if (!picture_.get()) {
return tonic::ToDart("Picture is null");
}

return RasterizeToImage(picture_.get(), width, height, raw_image_callback);
}

void Picture::dispose() {
Expand All @@ -52,4 +57,79 @@ size_t Picture::GetAllocationSize() {
}
}

Dart_Handle Picture::RasterizeToImage(sk_sp<SkPicture> picture,
uint32_t width,
uint32_t height,
Dart_Handle raw_image_callback) {
if (Dart_IsNull(raw_image_callback) || !Dart_IsClosure(raw_image_callback)) {
return tonic::ToDart("Image callback was invalid");
}

if (width == 0 || height == 0) {
return tonic::ToDart("Image dimensions for scene were invalid.");
}

auto* dart_state = UIDartState::Current();
auto image_callback = std::make_unique<tonic::DartPersistentValue>(
dart_state, raw_image_callback);
auto unref_queue = dart_state->GetSkiaUnrefQueue();
auto ui_task_runner = dart_state->GetTaskRunners().GetUITaskRunner();
auto gpu_task_runner = dart_state->GetTaskRunners().GetGPUTaskRunner();
auto snapshot_delegate = dart_state->GetSnapshotDelegate();

// We can't create an image on this task runner because we don't have a
// graphics context. Even if we did, it would be slow anyway. Also, this
// thread owns the sole reference to the layer tree. So we flatten the layer
// tree into a picture and use that as the thread transport mechanism.

auto picture_bounds = SkISize::Make(width, height);

auto ui_task = fml::MakeCopyable([ui_task_runner,
image_callback = std::move(image_callback),
unref_queue](
sk_sp<SkImage> raster_image) mutable {
// Send the raster image back to the UI thread for submission to the
// framework.
ui_task_runner->PostTask(fml::MakeCopyable([raster_image,
image_callback =
std::move(image_callback),
unref_queue]() mutable {
auto dart_state = image_callback->dart_state().lock();
if (!dart_state) {
// The root isolate could have died in the meantime.
return;
}
tonic::DartState::Scope scope(dart_state);

if (!raster_image) {
tonic::DartInvoke(image_callback->Get(), {Dart_Null()});
return;
}

auto dart_image = CanvasImage::Create();
dart_image->set_image({std::move(raster_image), std::move(unref_queue)});
auto* raw_dart_image = tonic::ToDart(std::move(dart_image));

// All done!
tonic::DartInvoke(image_callback->Get(), {raw_dart_image});
}));
});

auto gpu_task = fml::MakeCopyable([gpu_task_runner, picture, picture_bounds,
snapshot_delegate, ui_task]() {
gpu_task_runner->PostTask([snapshot_delegate, picture, picture_bounds,
ui_task]() {
// Snapshot the picture on the GPU thread. This thread has access to the
// GPU contexts that may contain the sole references to texture backed
// images in the picture.
ui_task(snapshot_delegate->MakeRasterSnapshot(picture, picture_bounds));
});
});

// Kick things off on the GPU.
gpu_task();

return Dart_Null();
}

} // namespace blink
9 changes: 8 additions & 1 deletion lib/ui/painting/picture.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,21 @@ class Picture : public RefCountedDartWrappable<Picture> {

sk_sp<SkPicture> picture() const { return picture_.get(); }

fml::RefPtr<CanvasImage> toImage(int width, int height);
Dart_Handle toImage(uint32_t width,
uint32_t height,
Dart_Handle raw_image_callback);

void dispose();

size_t GetAllocationSize() override;

static void RegisterNatives(tonic::DartLibraryNatives* natives);

static Dart_Handle RasterizeToImage(sk_sp<SkPicture> picture,
uint32_t width,
uint32_t height,
Dart_Handle raw_image_callback);

private:
explicit Picture(flow::SkiaGPUObject<SkPicture> picture);

Expand Down

0 comments on commit 82097a4

Please sign in to comment.