From 8973c733a5e39a9fb89277f43586e8aab4e68632 Mon Sep 17 00:00:00 2001 From: Chinmay Garde Date: Wed, 18 Apr 2018 12:42:16 -0700 Subject: [PATCH] Implement Scene::toImage for creating a raster image representation of a scene. (#5021) --- flow/layers/layer_tree.cc | 44 +++++++++++- flow/layers/layer_tree.h | 3 + lib/ui/compositing.dart | 13 ++++ lib/ui/compositing/scene.cc | 136 +++++++++++++++++++++++++++++++++++- lib/ui/compositing/scene.h | 4 ++ 5 files changed, 196 insertions(+), 4 deletions(-) diff --git a/flow/layers/layer_tree.cc b/flow/layers/layer_tree.cc index d9fb3741946..67ec443dbd9 100644 --- a/flow/layers/layer_tree.cc +++ b/flow/layers/layer_tree.cc @@ -6,6 +6,7 @@ #include "flutter/flow/layers/layer.h" #include "flutter/glue/trace_event.h" +#include "third_party/skia/include/core/SkPictureRecorder.h" namespace flow { @@ -60,6 +61,7 @@ void LayerTree::UpdateScene(SceneUpdateContext& context, #endif void LayerTree::Paint(CompositorContext::ScopedFrame& frame) const { + TRACE_EVENT0("flutter", "LayerTree::Paint"); Layer::PaintContext context = { *frame.canvas(), // frame.context().frame_time(), // @@ -67,10 +69,50 @@ void LayerTree::Paint(CompositorContext::ScopedFrame& frame) const { frame.context().texture_registry(), // checkerboard_offscreen_layers_ // }; - TRACE_EVENT0("flutter", "LayerTree::Paint"); if (root_layer_->needs_painting()) root_layer_->Paint(context); } +sk_sp LayerTree::Flatten(const SkRect& bounds) { + TRACE_EVENT0("flutter", "LayerTree::Flatten"); + + SkPictureRecorder recorder; + auto canvas = recorder.beginRecording(bounds); + + if (!canvas) { + return nullptr; + } + + Layer::PrerollContext preroll_context{ + nullptr, // raster_cache (don't consult the cache) + nullptr, // gr_context (used for the raster cache) + nullptr, // SkColorSpace* dst_color_space + SkRect::MakeEmpty(), // SkRect child_paint_bounds + }; + + const Stopwatch unused_stopwatch; + TextureRegistry unused_texture_registry; + + Layer::PaintContext paint_context = { + *canvas, // canvas + unused_stopwatch, // frame time (dont care) + unused_stopwatch, // engine time (dont care) + unused_texture_registry, // texture registry (not supported) + false // checkerboard offscreen layers + }; + + // Even if we don't have a root layer, we still need to create an empty + // picture. + if (root_layer_) { + root_layer_->Preroll(&preroll_context, SkMatrix::I()); + // The needs painting flag may be set after the preroll. So check it after. + if (root_layer_->needs_painting()) { + root_layer_->Paint(paint_context); + } + } + + return recorder.finishRecordingAsPicture(); +} + } // namespace flow diff --git a/flow/layers/layer_tree.h b/flow/layers/layer_tree.h index 25a8e072cfa..548c99f0ce6 100644 --- a/flow/layers/layer_tree.h +++ b/flow/layers/layer_tree.h @@ -13,6 +13,7 @@ #include "flutter/flow/layers/layer.h" #include "lib/fxl/macros.h" #include "lib/fxl/time/time_delta.h" +#include "third_party/skia/include/core/SkPicture.h" #include "third_party/skia/include/core/SkSize.h" namespace flow { @@ -33,6 +34,8 @@ class LayerTree { void Paint(CompositorContext::ScopedFrame& frame) const; + sk_sp Flatten(const SkRect& bounds); + Layer* root_layer() const { return root_layer_.get(); } void set_root_layer(std::unique_ptr root_layer) { diff --git a/lib/ui/compositing.dart b/lib/ui/compositing.dart index 97e6c59e53d..e0be1a66fda 100644 --- a/lib/ui/compositing.dart +++ b/lib/ui/compositing.dart @@ -17,6 +17,19 @@ class Scene extends NativeFieldWrapperClass2 { /// To create a Scene object, use a [SceneBuilder]. Scene._(); + + /// Creates a raster image representation of the current state of the scene. + /// This is a slow operation that is performed on a background thread. + Future toImage(int width, int height) { + if (width <= 0 || height <= 0) + throw new Exception('Invalid image dimensions.'); + return _futurize( + (_Callback callback) => _toImage(width, height, callback) + ); + } + + String _toImage(int width, int height, _Callback callback) native 'Scene_toImage'; + /// Releases the resources used by this scene. /// /// After calling this function, the scene is cannot be used further. diff --git a/lib/ui/compositing/scene.cc b/lib/ui/compositing/scene.cc index 513d89f102e..37514350b41 100644 --- a/lib/ui/compositing/scene.cc +++ b/lib/ui/compositing/scene.cc @@ -4,18 +4,26 @@ #include "flutter/lib/ui/compositing/scene.h" +#include "flutter/fml/trace_event.h" +#include "flutter/lib/ui/painting/image.h" +#include "lib/fxl/functional/make_copyable.h" #include "lib/tonic/converter/dart_converter.h" #include "lib/tonic/dart_args.h" #include "lib/tonic/dart_binding_macros.h" #include "lib/tonic/dart_library_natives.h" -#include "third_party/skia/include/core/SkCanvas.h" -#include "third_party/skia/include/core/SkPictureRecorder.h" +#include "lib/tonic/dart_persistent_value.h" +#include "lib/tonic/dart_wrappable.h" +#include "lib/tonic/logging/dart_invoke.h" +#include "third_party/skia/include/core/SkImageInfo.h" +#include "third_party/skia/include/core/SkSurface.h" namespace blink { IMPLEMENT_WRAPPERTYPEINFO(ui, Scene); -#define FOR_EACH_BINDING(V) V(Scene, dispose) +#define FOR_EACH_BINDING(V) \ + V(Scene, toImage) \ + V(Scene, dispose) DART_BIND_ALL(Scene, FOR_EACH_BINDING) @@ -46,6 +54,128 @@ void Scene::dispose() { ClearDartWrapper(); } +static sk_sp CreateSceneSnapshot(GrContext* context, + sk_sp picture, + const SkSize& size) { + TRACE_EVENT0("flutter", "CreateSceneSnapshot"); + auto image_info = + SkImageInfo::MakeN32Premul(SkISize::Make(size.width(), size.height())); + + sk_sp surface; + + if (context) { + surface = SkSurface::MakeRenderTarget(context, SkBudgeted::kNo, image_info); + } + + if (!surface) { + surface = SkSurface::MakeRaster(image_info); + } + + if (!surface) { + return nullptr; + } + + auto canvas = surface->getCanvas(); + + if (!canvas) { + return nullptr; + } + + if (picture) { + canvas->drawPicture(picture.get()); + } + + auto snapshot = surface->makeImageSnapshot(); + + if (!snapshot) { + return nullptr; + } + + return snapshot->makeRasterImage(); +} + +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( + dart_state, raw_image_callback); + + // 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 bounds_size = SkSize::Make(width, height); + auto picture = m_layerTree->Flatten(SkRect::MakeSize(bounds_size)); + if (!picture) { + // Already in Dart scope. + return tonic::ToDart("Could not flatten scene into a layer tree."); + } + + auto resource_context = dart_state->GetResourceContext(); + auto ui_task_runner = dart_state->GetTaskRunners().GetUITaskRunner(); + auto unref_queue = dart_state->GetSkiaUnrefQueue(); + + // The picture has been prepared on the UI thread. + dart_state->GetTaskRunners().GetIOTaskRunner()->PostTask( + fxl::MakeCopyable([picture = std::move(picture), // + bounds_size, // + resource_context = std::move(resource_context), // + ui_task_runner = std::move(ui_task_runner), // + image_callback = std::move(image_callback), // + unref_queue = std::move(unref_queue) // + ]() mutable { + // Snapshot the picture on the IO thread that contains an optional + // GrContext. + auto image = CreateSceneSnapshot(resource_context.get(), + std::move(picture), bounds_size); + + // Send the image back to the UI thread for submission back to the + // framework. + ui_task_runner->PostTask( + fxl::MakeCopyable([image = std::move(image), // + image_callback = std::move(image_callback), // + unref_queue = std::move(unref_queue) // + ]() mutable { + auto dart_state = image_callback->dart_state().get(); + if (!dart_state) { + // The root isolate could have died in the meantime. + return; + } + tonic::DartState::Scope scope(dart_state); + + if (!image) { + tonic::DartInvoke(image_callback->Get(), {Dart_Null()}); + return; + } + + auto dart_image = CanvasImage::Create(); + dart_image->set_image({std::move(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}); + })); + })); + + return Dart_Null(); +} + std::unique_ptr Scene::takeLayerTree() { return std::move(m_layerTree); } diff --git a/lib/ui/compositing/scene.h b/lib/ui/compositing/scene.h index 926fd9d83ba..92f4b5ae154 100644 --- a/lib/ui/compositing/scene.h +++ b/lib/ui/compositing/scene.h @@ -32,6 +32,10 @@ class Scene : public fxl::RefCountedThreadSafe, std::unique_ptr takeLayerTree(); + Dart_Handle toImage(uint32_t width, + uint32_t height, + Dart_Handle image_callback); + void dispose(); static void RegisterNatives(tonic::DartLibraryNatives* natives);