From 6816a721e49d483c723fdc6c33a115a702939095 Mon Sep 17 00:00:00 2001 From: Forrest Reiling Date: Fri, 19 Jun 2020 02:47:47 +0000 Subject: [PATCH 1/3] [fuchsia] fix Rasterizer::ScreenshotLayerTreeAsPicture on Fuchsia --- shell/common/rasterizer.cc | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/shell/common/rasterizer.cc b/shell/common/rasterizer.cc index 9735854f41842..79ad22b5a9da5 100644 --- a/shell/common/rasterizer.cc +++ b/shell/common/rasterizer.cc @@ -462,11 +462,20 @@ static sk_sp ScreenshotLayerTreeAsPicture( SkMatrix root_surface_transformation; root_surface_transformation.reset(); +#if defined(LEGACY_FUCHSIA_EMBEDDER) + // TODO(arbreng: fxb/55805) Our ScopedFrame implementation doesnt do the + // right thing here so initialize the base class directly. This wont be + // needed after we move to using the embedder API on Fuchsia. + auto frame = std::make_unique( + compositor_context, nullptr, recorder.getRecordingCanvas(), nullptr, + root_surface_transformation, false, true, nullptr); +#else // TODO(amirh): figure out how to take a screenshot with embedded UIView. // https://github.com/flutter/flutter/issues/23435 auto frame = compositor_context.AcquireFrame( nullptr, recorder.getRecordingCanvas(), nullptr, root_surface_transformation, false, true, nullptr); +#endif // defined(LEGACY_FUCHSIA_EMBEDDER) frame->Raster(*tree, true); From f77120d752f88cc445e94b953d966fcf5614a2ab Mon Sep 17 00:00:00 2001 From: Forrest Reiling Date: Fri, 19 Jun 2020 03:02:40 +0000 Subject: [PATCH 2/3] [fuchsia] Add shell test for skp based shader warmup --- shell/common/BUILD.gn | 1 + shell/common/skp_shader_warmup_unittests.cc | 200 ++++++++++++++++++++ 2 files changed, 201 insertions(+) create mode 100644 shell/common/skp_shader_warmup_unittests.cc diff --git a/shell/common/BUILD.gn b/shell/common/BUILD.gn index e4d1c956658b9..b6c9c2afac03a 100644 --- a/shell/common/BUILD.gn +++ b/shell/common/BUILD.gn @@ -264,6 +264,7 @@ if (enable_unittests) { "persistent_cache_unittests.cc", "pipeline_unittests.cc", "shell_unittests.cc", + "skp_shader_warmup_unittests.cc", ] deps = [ diff --git a/shell/common/skp_shader_warmup_unittests.cc b/shell/common/skp_shader_warmup_unittests.cc new file mode 100644 index 0000000000000..2ce9419fc0294 --- /dev/null +++ b/shell/common/skp_shader_warmup_unittests.cc @@ -0,0 +1,200 @@ +// 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 + +#include "flutter/assets/directory_asset_bundle.h" +#include "flutter/flow/layers/container_layer.h" +#include "flutter/flow/layers/layer.h" +#include "flutter/flow/layers/physical_shape_layer.h" +#include "flutter/flow/layers/picture_layer.h" +#include "flutter/fml/command_line.h" +#include "flutter/fml/file.h" +#include "flutter/fml/log_settings.h" +#include "flutter/fml/unique_fd.h" +#include "flutter/shell/common/persistent_cache.h" +#include "flutter/shell/common/shell_test.h" +#include "flutter/shell/common/switches.h" +#include "flutter/shell/version/version.h" +#include "flutter/testing/testing.h" +#include "include/core/SkPicture.h" + +namespace flutter { +namespace testing { + +#if defined(OS_FUCHSIA) + +static void WaitForIO(Shell* shell) { + std::promise io_task_finished; + shell->GetTaskRunners().GetIOTaskRunner()->PostTask( + [&io_task_finished]() { io_task_finished.set_value(true); }); + io_task_finished.get_future().wait(); +} + +class SkpWarmupTest : public ShellTest { + public: + SkpWarmupTest() {} + + void TestWarmup(const LayerTreeBuilder& builder) { + // Create a temp dir to store the persistent cache + fml::ScopedTemporaryDirectory dir; + PersistentCache::SetCacheDirectoryPath(dir.path()); + PersistentCache::ResetCacheForProcess(); + + auto settings = CreateSettingsForFixture(); + settings.cache_sksl = true; + settings.dump_skp_on_shader_compilation = true; + + fml::AutoResetWaitableEvent firstFrameLatch; + settings.frame_rasterized_callback = + [&firstFrameLatch](const FrameTiming& t) { firstFrameLatch.Signal(); }; + + auto config = RunConfiguration::InferFromSettings(settings); + config.SetEntrypoint("emptyMain"); + std::unique_ptr shell = CreateShell(settings); + PlatformViewNotifyCreated(shell.get()); + RunEngine(shell.get(), std::move(config)); + + // Initially, we should have no SkSL cache + auto cache = PersistentCache::GetCacheForProcess()->LoadSkSLs(); + ASSERT_EQ(cache.size(), 0u); + + PumpOneFrame(shell.get(), 100, 100, builder); + firstFrameLatch.Wait(); + WaitForIO(shell.get()); + + // Count the number of shaders this builder generated. We use this as a + // proxy for whether new shaders were generated, since skia will dump an skp + // any time a new shader is compiled. + int skp_count = 0; + fml::FileVisitor skp_count_visitor = [&skp_count]( + const fml::UniqueFD& directory, + const std::string& filename) { + if (filename.size() >= 4 && + filename.substr(filename.size() - 4, 4) == ".skp") { + skp_count += 1; + } + return true; + }; + fml::VisitFilesRecursively(dir.fd(), skp_count_visitor); + int first_skp_count = skp_count; + skp_count = 0; + ASSERT_GT(first_skp_count, 0); + + // Deserialize all skps into memory + std::vector> pictures; + fml::FileVisitor skp_deserialize_visitor = + [&pictures](const fml::UniqueFD& directory, + const std::string& filename) { + if (filename.size() >= 4 && + filename.substr(filename.size() - 4, 4) == ".skp") { + auto fd = fml::OpenFileReadOnly(directory, filename.c_str()); + if (fd.get() < 0) { + FML_LOG(ERROR) << "Failed to open " << filename; + return true; + } + // Deserialize + sk_sp data = SkData::MakeFromFD(fd.get()); + std::unique_ptr stream = SkMemoryStream::Make(data); + sk_sp picture = SkPicture::MakeFromStream( + stream.get(), /*const SkDeserialProcs* */ nullptr); + pictures.push_back(std::move(picture)); + fd.reset(); + } + return true; + }; + fml::VisitFilesRecursively(dir.fd(), skp_deserialize_visitor); + ASSERT_GT(pictures.size(), 0ul); + + // Reinitialize shell with clean cache and verify that drawing again dumps + // the same number of shaders + fml::RemoveFilesInDirectory(dir.fd()); + PersistentCache::ResetCacheForProcess(); + DestroyShell(std::move(shell)); + auto config2 = RunConfiguration::InferFromSettings(settings); + config2.SetEntrypoint("emptyMain"); + shell = CreateShell(settings); + PlatformViewNotifyCreated(shell.get()); + RunEngine(shell.get(), std::move(config2)); + firstFrameLatch.Reset(); + PumpOneFrame(shell.get(), 100, 100, builder); + firstFrameLatch.Wait(); + WaitForIO(shell.get()); + + // Verify same number of shaders dumped + fml::VisitFilesRecursively(dir.fd(), skp_count_visitor); + int second_skp_count = skp_count; + skp_count = 0; + ASSERT_EQ(second_skp_count, first_skp_count); + + // Reinitialize shell and draw deserialized skps to warm up shaders + fml::RemoveFilesInDirectory(dir.fd()); + PersistentCache::ResetCacheForProcess(); + DestroyShell(std::move(shell)); + auto config3 = RunConfiguration::InferFromSettings(settings); + config3.SetEntrypoint("emptyMain"); + shell = CreateShell(settings); + PlatformViewNotifyCreated(shell.get()); + RunEngine(shell.get(), std::move(config3)); + firstFrameLatch.Reset(); + + for (auto& picture : pictures) { + fml::RefPtr queue = fml::MakeRefCounted( + this->GetCurrentTaskRunner(), fml::TimeDelta::FromSeconds(0)); + LayerTreeBuilder picture_builder = + [picture, queue](std::shared_ptr root) { + auto picture_layer = std::make_shared( + SkPoint::Make(0, 0), SkiaGPUObject(picture, queue), + /* is_complex */ false, + /* will_change */ false); + root->Add(picture_layer); + }; + PumpOneFrame(shell.get(), picture->cullRect().width(), + picture->cullRect().height(), picture_builder); + } + firstFrameLatch.Wait(); + WaitForIO(shell.get()); + + // Verify same number of shaders dumped + fml::VisitFilesRecursively(dir.fd(), skp_count_visitor); + int third_skp_count = skp_count; + skp_count = 0; + ASSERT_EQ(third_skp_count, first_skp_count); + + // Remove files generated + fml::RemoveFilesInDirectory(dir.fd()); + + // Draw orignal material again + firstFrameLatch.Reset(); + PumpOneFrame(shell.get(), 100, 100, builder); + firstFrameLatch.Wait(); + WaitForIO(shell.get()); + + // Verify no new shaders dumped + fml::VisitFilesRecursively(dir.fd(), skp_count_visitor); + int fourth_skp_count = skp_count; + skp_count = 0; + ASSERT_EQ(fourth_skp_count, 0); + + // Clean Up + fml::RemoveFilesInDirectory(dir.fd()); + } +}; + +TEST_F(SkpWarmupTest, Basic) { + // Draw something to trigger shader compilations. + LayerTreeBuilder builder = [](std::shared_ptr root) { + SkPath path; + path.addCircle(50, 50, 20); + auto physical_shape_layer = std::make_shared( + SK_ColorRED, SK_ColorBLUE, 1.0f, path, Clip::antiAlias); + root->Add(physical_shape_layer); + }; + TestWarmup(builder); +} + +#endif + +} // namespace testing +} // namespace flutter From 160313e384582edeb3125105c6e50dd1ec6e0970 Mon Sep 17 00:00:00 2001 From: Forrest Reiling Date: Fri, 19 Jun 2020 03:08:40 +0000 Subject: [PATCH 3/3] [fuchsia] use custom image serialization when writing out SKP's This change adds custom image serialization functions for skp serialization which write out onlythe image metadata and not the contents of the images themselves. This allows SKP's to be used for shader warmup with a significantly reduced disk space footprint. --- ci/licenses_golden/licenses_flutter | 3 + shell/common/BUILD.gn | 2 + shell/common/rasterizer.cc | 12 ++-- shell/common/serialization_callbacks.cc | 74 +++++++++++++++++++++ shell/common/serialization_callbacks.h | 27 ++++++++ shell/common/skp_shader_warmup_unittests.cc | 74 +++++++++++++++++---- 6 files changed, 174 insertions(+), 18 deletions(-) create mode 100644 shell/common/serialization_callbacks.cc create mode 100644 shell/common/serialization_callbacks.h diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index b1735e606a812..05c6078103ac5 100755 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -611,6 +611,8 @@ FILE: ../../../flutter/shell/common/rasterizer.cc FILE: ../../../flutter/shell/common/rasterizer.h FILE: ../../../flutter/shell/common/run_configuration.cc FILE: ../../../flutter/shell/common/run_configuration.h +FILE: ../../../flutter/shell/common/serialization_callbacks.cc +FILE: ../../../flutter/shell/common/serialization_callbacks.h FILE: ../../../flutter/shell/common/shell.cc FILE: ../../../flutter/shell/common/shell.h FILE: ../../../flutter/shell/common/shell_benchmarks.cc @@ -629,6 +631,7 @@ FILE: ../../../flutter/shell/common/shell_test_platform_view_vulkan.h FILE: ../../../flutter/shell/common/shell_unittests.cc FILE: ../../../flutter/shell/common/skia_event_tracer_impl.cc FILE: ../../../flutter/shell/common/skia_event_tracer_impl.h +FILE: ../../../flutter/shell/common/skp_shader_warmup_unittests.cc FILE: ../../../flutter/shell/common/switches.cc FILE: ../../../flutter/shell/common/switches.h FILE: ../../../flutter/shell/common/thread_host.cc diff --git a/shell/common/BUILD.gn b/shell/common/BUILD.gn index b6c9c2afac03a..9e5fa56148f07 100644 --- a/shell/common/BUILD.gn +++ b/shell/common/BUILD.gn @@ -82,6 +82,8 @@ source_set_maybe_fuchsia_legacy("common") { "rasterizer.h", "run_configuration.cc", "run_configuration.h", + "serialization_callbacks.cc", + "serialization_callbacks.h", "shell.cc", "shell.h", "shell_io_manager.cc", diff --git a/shell/common/rasterizer.cc b/shell/common/rasterizer.cc index 79ad22b5a9da5..85e1ef1fbae3f 100644 --- a/shell/common/rasterizer.cc +++ b/shell/common/rasterizer.cc @@ -9,6 +9,7 @@ #include "flutter/fml/time/time_delta.h" #include "flutter/fml/time/time_point.h" #include "flutter/shell/common/persistent_cache.h" +#include "flutter/shell/common/serialization_callbacks.h" #include "third_party/skia/include/core/SkEncodedImageFormat.h" #include "third_party/skia/include/core/SkImageEncoder.h" #include "third_party/skia/include/core/SkPictureRecorder.h" @@ -447,10 +448,6 @@ RasterStatus Rasterizer::DrawToSurface(flutter::LayerTree& layer_tree) { return RasterStatus::kFailed; } -static sk_sp SerializeTypeface(SkTypeface* typeface, void* ctx) { - return typeface->serialize(SkTypeface::SerializeBehavior::kDoIncludeData); -} - static sk_sp ScreenshotLayerTreeAsPicture( flutter::LayerTree* tree, flutter::CompositorContext& compositor_context) { @@ -479,8 +476,13 @@ static sk_sp ScreenshotLayerTreeAsPicture( frame->Raster(*tree, true); +#if defined(OS_FUCHSIA) SkSerialProcs procs = {0}; - procs.fTypefaceProc = SerializeTypeface; + procs.fImageProc = SerializeImageWithoutData; +#else + SkSerialProcs procs = {0}; + procs.fTypefaceProc = SerializeTypefaceWithData; +#endif return recorder.finishRecordingAsPicture()->serialize(&procs); } diff --git a/shell/common/serialization_callbacks.cc b/shell/common/serialization_callbacks.cc new file mode 100644 index 0000000000000..b482f5fab9f42 --- /dev/null +++ b/shell/common/serialization_callbacks.cc @@ -0,0 +1,74 @@ +// 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/fml/logging.h" +#include "include/core/SkImage.h" +#include "include/core/SkPicture.h" +#include "include/core/SkStream.h" +#include "include/core/SkTypeface.h" + +namespace flutter { + +sk_sp SerializeTypefaceWithoutData(SkTypeface* typeface, void* ctx) { + return typeface->serialize(SkTypeface::SerializeBehavior::kDoIncludeData); +} + +sk_sp SerializeTypefaceWithData(SkTypeface* typeface, void* ctx) { + return typeface->serialize(SkTypeface::SerializeBehavior::kDontIncludeData); +} + +struct ImageMetaData { + int32_t width; + int32_t height; + uint32_t color_type; + uint32_t alpha_type; + bool has_color_space; +} __attribute__((packed)); + +sk_sp SerializeImageWithoutData(SkImage* image, void* ctx) { + auto info = image->imageInfo(); + SkDynamicMemoryWStream stream; + + ImageMetaData metadata = {info.width(), info.height(), + static_cast(info.colorType()), + static_cast(info.alphaType()), + static_cast(info.colorSpace())}; + stream.write(&metadata, sizeof(ImageMetaData)); + + if (info.colorSpace()) { + auto color_space_data = info.colorSpace()->serialize(); + FML_CHECK(color_space_data); + SkMemoryStream color_space_stream(color_space_data); + stream.writeStream(&color_space_stream, color_space_data->size()); + } + + return stream.detachAsData(); +}; + +sk_sp DeserializeImageWithoutData(const void* data, + size_t length, + void* ctx) { + FML_CHECK(length >= sizeof(ImageMetaData)); + auto metadata = static_cast(data); + sk_sp color_space = nullptr; + if (metadata->has_color_space) { + color_space = SkColorSpace::Deserialize( + static_cast(data) + sizeof(ImageMetaData), + length - sizeof(ImageMetaData)); + } + + auto image_size = SkISize::Make(metadata->width, metadata->height); + auto info = SkImageInfo::Make( + image_size, static_cast(metadata->color_type), + static_cast(metadata->alpha_type), color_space); + sk_sp image_data = + SkData::MakeUninitialized(image_size.width() * image_size.height() * 4); + memset(image_data->writable_data(), 0x0f, image_data->size()); + sk_sp image = + SkImage::MakeRasterData(info, image_data, image_size.width() * 4); + + return image; +}; + +} // namespace flutter diff --git a/shell/common/serialization_callbacks.h b/shell/common/serialization_callbacks.h new file mode 100644 index 0000000000000..6e339fd3c48c2 --- /dev/null +++ b/shell/common/serialization_callbacks.h @@ -0,0 +1,27 @@ +// 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. + +#ifndef FLUTTER_SHELL_COMMON_SERIALIZATION_CALLBACKS_H_ +#define FLUTTER_SHELL_COMMON_SERIALIZATION_CALLBACKS_H_ + +#include "flutter/fml/logging.h" +#include "include/core/SkImage.h" +#include "include/core/SkPicture.h" +#include "include/core/SkStream.h" +#include "include/core/SkTypeface.h" + +namespace flutter { + +sk_sp SerializeTypefaceWithoutData(SkTypeface* typeface, void* ctx); +sk_sp SerializeTypefaceWithData(SkTypeface* typeface, void* ctx); + +// Serializes only the metadata of the image and not the underlying pixel data. +sk_sp SerializeImageWithoutData(SkImage* image, void* ctx); +sk_sp DeserializeImageWithoutData(const void* data, + size_t length, + void* ctx); + +} // namespace flutter + +#endif // FLUTTER_SHELL_COMMON_SERIALIZATION_CALLBACKS_H_ diff --git a/shell/common/skp_shader_warmup_unittests.cc b/shell/common/skp_shader_warmup_unittests.cc index 2ce9419fc0294..689f2d9322fcc 100644 --- a/shell/common/skp_shader_warmup_unittests.cc +++ b/shell/common/skp_shader_warmup_unittests.cc @@ -14,11 +14,14 @@ #include "flutter/fml/log_settings.h" #include "flutter/fml/unique_fd.h" #include "flutter/shell/common/persistent_cache.h" +#include "flutter/shell/common/serialization_callbacks.h" #include "flutter/shell/common/shell_test.h" #include "flutter/shell/common/switches.h" #include "flutter/shell/version/version.h" #include "flutter/testing/testing.h" #include "include/core/SkPicture.h" +#include "include/core/SkPictureRecorder.h" +#include "include/core/SkSerialProcs.h" namespace flutter { namespace testing { @@ -36,7 +39,7 @@ class SkpWarmupTest : public ShellTest { public: SkpWarmupTest() {} - void TestWarmup(const LayerTreeBuilder& builder) { + void TestWarmup(const SkISize& draw_size, const LayerTreeBuilder& builder) { // Create a temp dir to store the persistent cache fml::ScopedTemporaryDirectory dir; PersistentCache::SetCacheDirectoryPath(dir.path()); @@ -60,7 +63,7 @@ class SkpWarmupTest : public ShellTest { auto cache = PersistentCache::GetCacheForProcess()->LoadSkSLs(); ASSERT_EQ(cache.size(), 0u); - PumpOneFrame(shell.get(), 100, 100, builder); + PumpOneFrame(shell.get(), draw_size.width(), draw_size.height(), builder); firstFrameLatch.Wait(); WaitForIO(shell.get()); @@ -97,8 +100,11 @@ class SkpWarmupTest : public ShellTest { // Deserialize sk_sp data = SkData::MakeFromFD(fd.get()); std::unique_ptr stream = SkMemoryStream::Make(data); - sk_sp picture = SkPicture::MakeFromStream( - stream.get(), /*const SkDeserialProcs* */ nullptr); + + SkDeserialProcs procs = {0}; + procs.fImageProc = DeserializeImageWithoutData; + sk_sp picture = + SkPicture::MakeFromStream(stream.get(), &procs); pictures.push_back(std::move(picture)); fd.reset(); } @@ -118,7 +124,7 @@ class SkpWarmupTest : public ShellTest { PlatformViewNotifyCreated(shell.get()); RunEngine(shell.get(), std::move(config2)); firstFrameLatch.Reset(); - PumpOneFrame(shell.get(), 100, 100, builder); + PumpOneFrame(shell.get(), draw_size.width(), draw_size.height(), builder); firstFrameLatch.Wait(); WaitForIO(shell.get()); @@ -167,7 +173,8 @@ class SkpWarmupTest : public ShellTest { // Draw orignal material again firstFrameLatch.Reset(); - PumpOneFrame(shell.get(), 100, 100, builder); + PumpOneFrame(shell.get(), draw_size.width(), draw_size.height(), builder); + firstFrameLatch.Wait(); WaitForIO(shell.get()); @@ -183,15 +190,56 @@ class SkpWarmupTest : public ShellTest { }; TEST_F(SkpWarmupTest, Basic) { + SkISize draw_size = SkISize::Make(100, 100); // Draw something to trigger shader compilations. - LayerTreeBuilder builder = [](std::shared_ptr root) { - SkPath path; - path.addCircle(50, 50, 20); - auto physical_shape_layer = std::make_shared( - SK_ColorRED, SK_ColorBLUE, 1.0f, path, Clip::antiAlias); - root->Add(physical_shape_layer); + LayerTreeBuilder builder = + [&draw_size](std::shared_ptr root) { + SkPath path; + path.addCircle(draw_size.width() / 2, draw_size.height() / 2, 20); + auto physical_shape_layer = std::make_shared( + SK_ColorRED, SK_ColorBLUE, 1.0f, path, Clip::antiAlias); + root->Add(physical_shape_layer); + }; + TestWarmup(draw_size, builder); +} + +TEST_F(SkpWarmupTest, Image) { + SkISize draw_size = SkISize::Make(100, 100); + // We reuse this builder to draw the same content sever times in this test + LayerTreeBuilder builder = [&draw_size, + this](std::shared_ptr root) { + SkPictureRecorder recorder; + auto canvas = + recorder.beginRecording(draw_size.width(), draw_size.height()); + + // include an image so we can test that the warmup works even with image + // data excluded from the skp + auto image_size = + SkISize::Make(draw_size.width() / 2, draw_size.height() / 2); + auto color_space = SkColorSpace::MakeSRGB(); + auto info = + SkImageInfo::Make(image_size, SkColorType::kRGBA_8888_SkColorType, + SkAlphaType::kPremul_SkAlphaType, color_space); + sk_sp image_data = + SkData::MakeUninitialized(image_size.width() * image_size.height() * 4); + memset(image_data->writable_data(), 0x0f, image_data->size()); + sk_sp image = + SkImage::MakeRasterData(info, image_data, image_size.width() * 4); + + canvas->drawImage(image, image_size.width(), image_size.height()); + + auto picture = recorder.finishRecordingAsPicture(); + + fml::RefPtr queue = fml::MakeRefCounted( + this->GetCurrentTaskRunner(), fml::TimeDelta::FromSeconds(0)); + auto picture_layer = std::make_shared( + SkPoint::Make(0, 0), SkiaGPUObject(picture, queue), + /* is_complex */ false, + /* will_change */ false); + root->Add(picture_layer); }; - TestWarmup(builder); + + TestWarmup(draw_size, builder); } #endif