diff --git a/flow/raster_cache.cc b/flow/raster_cache.cc index 1e39e3701a679..6a45232fce1ca 100644 --- a/flow/raster_cache.cc +++ b/flow/raster_cache.cc @@ -299,35 +299,34 @@ void RasterCache::SetCheckboardCacheImages(bool checkerboard) { void RasterCache::TraceStatsToTimeline() const { #if !FLUTTER_RELEASE + constexpr double kMegaBytes = (1 << 20); + FML_TRACE_COUNTER("flutter", "RasterCache", reinterpret_cast(this), + "LayerCount", layer_cache_.size(), "LayerMBytes", + EstimateLayerCacheByteSize() / kMegaBytes, "PictureCount", + picture_cache_.size(), "PictureMBytes", + EstimatePictureCacheByteSize() / kMegaBytes); - size_t layer_cache_count = 0; - size_t layer_cache_bytes = 0; - size_t picture_cache_count = 0; - size_t picture_cache_bytes = 0; +#endif // !FLUTTER_RELEASE +} +size_t RasterCache::EstimateLayerCacheByteSize() const { + size_t layer_cache_bytes = 0; for (const auto& item : layer_cache_) { - layer_cache_count++; if (item.second.image) { layer_cache_bytes += item.second.image->image_bytes(); } } + return layer_cache_bytes; +} +size_t RasterCache::EstimatePictureCacheByteSize() const { + size_t picture_cache_bytes = 0; for (const auto& item : picture_cache_) { - picture_cache_count++; if (item.second.image) { picture_cache_bytes += item.second.image->image_bytes(); } } - - FML_TRACE_COUNTER("flutter", "RasterCache", - reinterpret_cast(this), // - "LayerCount", layer_cache_count, // - "LayerMBytes", layer_cache_bytes * 1e-6, // - "PictureCount", picture_cache_count, // - "PictureMBytes", picture_cache_bytes * 1e-6 // - ); - -#endif // !FLUTTER_RELEASE + return picture_cache_bytes; } } // namespace flutter diff --git a/flow/raster_cache.h b/flow/raster_cache.h index ace5a0c958b58..297a58482f056 100644 --- a/flow/raster_cache.h +++ b/flow/raster_cache.h @@ -29,9 +29,7 @@ class RasterCacheResult { }; virtual int64_t image_bytes() const { - return image_ ? image_->dimensions().area() * - image_->imageInfo().bytesPerPixel() - : 0; + return image_ ? image_->imageInfo().computeMinByteSize() : 0; }; private: @@ -170,6 +168,26 @@ class RasterCache { size_t GetPictureCachedEntriesCount() const; + /** + * @brief Estimate how much memory is used by picture raster cache entries in + * bytes. + * + * Only SkImage's memory usage is counted as other objects are often much + * smaller compared to SkImage. SkImageInfo::computeMinByteSize is used to + * estimate the SkImage memory usage. + */ + size_t EstimatePictureCacheByteSize() const; + + /** + * @brief Estimate how much memory is used by layer raster cache entries in + * bytes. + * + * Only SkImage's memory usage is counted as other objects are often much + * smaller compared to SkImage. SkImageInfo::computeMinByteSize is used to + * estimate the SkImage memory usage. + */ + size_t EstimateLayerCacheByteSize() const; + private: struct Entry { bool used_this_frame = false; diff --git a/runtime/service_protocol.cc b/runtime/service_protocol.cc index b5320f42acb68..ace3039f2f959 100644 --- a/runtime/service_protocol.cc +++ b/runtime/service_protocol.cc @@ -35,6 +35,9 @@ const std::string_view ServiceProtocol::kGetDisplayRefreshRateExtensionName = "_flutter.getDisplayRefreshRate"; const std::string_view ServiceProtocol::kGetSkSLsExtensionName = "_flutter.getSkSLs"; +const std::string_view + ServiceProtocol::kEstimateRasterCacheMemoryExtensionName = + "_flutter.estimateRasterCacheMemory"; static constexpr std::string_view kViewIdPrefx = "_flutterView/"; static constexpr std::string_view kListViewsExtensionName = @@ -53,6 +56,7 @@ ServiceProtocol::ServiceProtocol() kSetAssetBundlePathExtensionName, kGetDisplayRefreshRateExtensionName, kGetSkSLsExtensionName, + kEstimateRasterCacheMemoryExtensionName, }), handlers_mutex_(fml::SharedMutex::Create()) {} diff --git a/runtime/service_protocol.h b/runtime/service_protocol.h index dfc0cd458fbd9..e809b7d833fc4 100644 --- a/runtime/service_protocol.h +++ b/runtime/service_protocol.h @@ -28,6 +28,7 @@ class ServiceProtocol { static const std::string_view kSetAssetBundlePathExtensionName; static const std::string_view kGetDisplayRefreshRateExtensionName; static const std::string_view kGetSkSLsExtensionName; + static const std::string_view kEstimateRasterCacheMemoryExtensionName; class Handler { public: diff --git a/shell/common/shell.cc b/shell/common/shell.cc index d11a14f06b4e2..13bd40638991a 100644 --- a/shell/common/shell.cc +++ b/shell/common/shell.cc @@ -375,6 +375,11 @@ Shell::Shell(DartVMRef vm, TaskRunners task_runners, Settings settings) task_runners_.GetIOTaskRunner(), std::bind(&Shell::OnServiceProtocolGetSkSLs, this, std::placeholders::_1, std::placeholders::_2)}; + service_protocol_handlers_ + [ServiceProtocol::kEstimateRasterCacheMemoryExtensionName] = { + task_runners_.GetRasterTaskRunner(), + std::bind(&Shell::OnServiceProtocolEstimateRasterCacheMemory, this, + std::placeholders::_1, std::placeholders::_2)}; } Shell::~Shell() { @@ -1424,6 +1429,23 @@ bool Shell::OnServiceProtocolGetSkSLs( return true; } +bool Shell::OnServiceProtocolEstimateRasterCacheMemory( + const ServiceProtocol::Handler::ServiceProtocolMap& params, + rapidjson::Document* response) { + FML_DCHECK(task_runners_.GetRasterTaskRunner()->RunsTasksOnCurrentThread()); + const auto& raster_cache = rasterizer_->compositor_context()->raster_cache(); + response->SetObject(); + response->AddMember("type", "EstimateRasterCacheMemory", + response->GetAllocator()); + response->AddMember("layerBytes", + raster_cache.EstimateLayerCacheByteSize(), + response->GetAllocator()); + response->AddMember("pictureBytes", + raster_cache.EstimatePictureCacheByteSize(), + response->GetAllocator()); + return true; +} + // Service protocol handler bool Shell::OnServiceProtocolSetAssetBundlePath( const ServiceProtocol::Handler::ServiceProtocolMap& params, diff --git a/shell/common/shell.h b/shell/common/shell.h index 847792e7ef4a1..dd09fad2ae7a6 100644 --- a/shell/common/shell.h +++ b/shell/common/shell.h @@ -584,6 +584,11 @@ class Shell final : public PlatformView::Delegate, const ServiceProtocol::Handler::ServiceProtocolMap& params, rapidjson::Document* response); + // Service protocol handler + bool OnServiceProtocolEstimateRasterCacheMemory( + const ServiceProtocol::Handler::ServiceProtocolMap& params, + rapidjson::Document* response); + fml::WeakPtrFactory weak_factory_; // For accessing the Shell via the raster thread, necessary for various diff --git a/shell/common/shell_test.cc b/shell/common/shell_test.cc index 86124694f980e..d60c80805ce9d 100644 --- a/shell/common/shell_test.cc +++ b/shell/common/shell_test.cc @@ -186,6 +186,9 @@ void ShellTest::OnServiceProtocol( case ServiceProtocolEnum::kGetSkSLs: shell->OnServiceProtocolGetSkSLs(params, response); break; + case ServiceProtocolEnum::kEstimateRasterCacheMemory: + shell->OnServiceProtocolEstimateRasterCacheMemory(params, response); + break; case ServiceProtocolEnum::kSetAssetBundlePath: shell->OnServiceProtocolSetAssetBundlePath(params, response); break; diff --git a/shell/common/shell_test.h b/shell/common/shell_test.h index e296ba118c73e..e534fcec3cef0 100644 --- a/shell/common/shell_test.h +++ b/shell/common/shell_test.h @@ -80,6 +80,7 @@ class ShellTest : public FixtureTest { enum ServiceProtocolEnum { kGetSkSLs, + kEstimateRasterCacheMemory, kSetAssetBundlePath, kRunInView, }; diff --git a/shell/common/shell_unittests.cc b/shell/common/shell_unittests.cc index 6d62bc503be2c..95ab0071fdb36 100644 --- a/shell/common/shell_unittests.cc +++ b/shell/common/shell_unittests.cc @@ -1345,5 +1345,93 @@ TEST_F(ShellTest, RasterizerMakeRasterSnapshot) { DestroyShell(std::move(shell), std::move(task_runners)); } +static sk_sp MakeSizedPicture(int width, int height) { + SkPictureRecorder recorder; + SkCanvas* recording_canvas = + recorder.beginRecording(SkRect::MakeXYWH(0, 0, width, height)); + recording_canvas->drawRect(SkRect::MakeXYWH(0, 0, width, height), + SkPaint(SkColor4f::FromColor(SK_ColorRED))); + return recorder.finishRecordingAsPicture(); +} + +TEST_F(ShellTest, OnServiceProtocolEstimateRasterCacheMemoryWorks) { + Settings settings = CreateSettingsForFixture(); + std::unique_ptr shell = CreateShell(settings); + + // 1. Construct a picture and a picture layer to be raster cached. + sk_sp picture = MakeSizedPicture(10, 10); + fml::RefPtr queue = fml::MakeRefCounted( + GetCurrentTaskRunner(), fml::TimeDelta::FromSeconds(0)); + auto picture_layer = std::make_shared( + SkPoint::Make(0, 0), + flutter::SkiaGPUObject({MakeSizedPicture(100, 100), queue}), + false, false); + picture_layer->set_paint_bounds(SkRect::MakeWH(100, 100)); + + // 2. Rasterize the picture and the picture layer in the raster cache. + std::promise rasterized; + shell->GetTaskRunners().GetRasterTaskRunner()->PostTask( + [&shell, &rasterized, &picture, &picture_layer] { + auto* compositor_context = shell->GetRasterizer()->compositor_context(); + auto& raster_cache = compositor_context->raster_cache(); + // 2.1. Rasterize the picture. Call Draw multiple times to pass the + // access threshold (default to 3) so a cache can be generated. + SkCanvas dummy_canvas; + bool picture_cache_generated; + for (int i = 0; i < 4; i += 1) { + picture_cache_generated = + raster_cache.Prepare(nullptr, // GrDirectContext + picture.get(), SkMatrix::I(), + nullptr, // SkColorSpace + true, // isComplex + false // willChange + ); + raster_cache.Draw(*picture, dummy_canvas); + } + ASSERT_TRUE(picture_cache_generated); + + // 2.2. Rasterize the picture layer. + Stopwatch raster_time; + Stopwatch ui_time; + MutatorsStack mutators_stack; + TextureRegistry texture_registry; + PrerollContext preroll_context = { + nullptr, /* raster_cache */ + nullptr, /* gr_context */ + nullptr, /* external_view_embedder */ + mutators_stack, nullptr, /* color_space */ + kGiantRect, /* cull_rect */ + false, /* layer reads from surface */ + raster_time, ui_time, texture_registry, + false, /* checkerboard_offscreen_layers */ + 100.0f, /* frame_physical_depth */ + 1.0f, /* frame_device_pixel_ratio */ + 0.0f, /* total_elevation */ + false, /* has_platform_view */ + }; + raster_cache.Prepare(&preroll_context, picture_layer.get(), + SkMatrix::I()); + rasterized.set_value(true); + }); + rasterized.get_future().wait(); + + // 3. Call the service protocol and check its output. + ServiceProtocol::Handler::ServiceProtocolMap empty_params; + rapidjson::Document document; + OnServiceProtocol( + shell.get(), ServiceProtocolEnum::kEstimateRasterCacheMemory, + shell->GetTaskRunners().GetRasterTaskRunner(), empty_params, &document); + rapidjson::StringBuffer buffer; + rapidjson::Writer writer(buffer); + document.Accept(writer); + std::string expected_json = + "{\"type\":\"EstimateRasterCacheMemory\",\"layerBytes\":40000,\"picture" + "Bytes\":400}"; + std::string actual_json = buffer.GetString(); + ASSERT_EQ(actual_json, expected_json); + + DestroyShell(std::move(shell)); +} + } // namespace testing } // namespace flutter