From 31e332ee32c3b0206e33ee56cc0a356565fd4fe3 Mon Sep 17 00:00:00 2001 From: ColdPaleLight <31977171+ColdPaleLight@users.noreply.github.com> Date: Fri, 1 Apr 2022 12:11:05 +0800 Subject: [PATCH] Tuning resource cache max bytes in lightweight engine scenarios (flutter/engine#32156) --- .../ci/licenses_golden/licenses_flutter | 3 + engine/src/flutter/common/settings.h | 3 + engine/src/flutter/shell/common/BUILD.gn | 3 + .../common/resource_cache_limit_calculator.cc | 25 ++++ .../common/resource_cache_limit_calculator.h | 48 +++++++ ...source_cache_limit_calculator_unittests.cc | 54 ++++++++ engine/src/flutter/shell/common/shell.cc | 34 ++++- engine/src/flutter/shell/common/shell.h | 17 ++- .../flutter/shell/common/shell_unittests.cc | 130 ++++++++++++++++++ engine/src/flutter/shell/common/switches.cc | 10 ++ engine/src/flutter/shell/common/switches.h | 4 + .../engine/loader/FlutterLoader.java | 10 +- .../engine/loader/FlutterLoaderTest.java | 31 +++++ .../framework/Source/FlutterDartProject.mm | 8 ++ .../Source/FlutterDartProjectTest.mm | 10 ++ 15 files changed, 381 insertions(+), 9 deletions(-) create mode 100644 engine/src/flutter/shell/common/resource_cache_limit_calculator.cc create mode 100644 engine/src/flutter/shell/common/resource_cache_limit_calculator.h create mode 100644 engine/src/flutter/shell/common/resource_cache_limit_calculator_unittests.cc diff --git a/engine/src/flutter/ci/licenses_golden/licenses_flutter b/engine/src/flutter/ci/licenses_golden/licenses_flutter index 9423d76dd1c..b64efc428d5 100755 --- a/engine/src/flutter/ci/licenses_golden/licenses_flutter +++ b/engine/src/flutter/ci/licenses_golden/licenses_flutter @@ -1108,6 +1108,9 @@ FILE: ../../../flutter/shell/common/pointer_data_dispatcher.h FILE: ../../../flutter/shell/common/rasterizer.cc FILE: ../../../flutter/shell/common/rasterizer.h FILE: ../../../flutter/shell/common/rasterizer_unittests.cc +FILE: ../../../flutter/shell/common/resource_cache_limit_calculator.cc +FILE: ../../../flutter/shell/common/resource_cache_limit_calculator.h +FILE: ../../../flutter/shell/common/resource_cache_limit_calculator_unittests.cc FILE: ../../../flutter/shell/common/run_configuration.cc FILE: ../../../flutter/shell/common/run_configuration.h FILE: ../../../flutter/shell/common/serialization_callbacks.cc diff --git a/engine/src/flutter/common/settings.h b/engine/src/flutter/common/settings.h index fc1000df543..e745ecb0c12 100644 --- a/engine/src/flutter/common/settings.h +++ b/engine/src/flutter/common/settings.h @@ -294,6 +294,9 @@ struct Settings { /// https://github.com/dart-lang/sdk/blob/ca64509108b3e7219c50d6c52877c85ab6a35ff2/runtime/vm/flag_list.h#L150 int64_t old_gen_heap_size = -1; + // Max bytes threshold of resource cache, or 0 for unlimited. + size_t resource_cache_max_bytes_threshold = 0; + /// A timestamp representing when the engine started. The value is based /// on the clock used by the Dart timeline APIs. This timestamp is used /// to log a timeline event that tracks the latency of engine startup. diff --git a/engine/src/flutter/shell/common/BUILD.gn b/engine/src/flutter/shell/common/BUILD.gn index d3bc759817a..a1c1fed6205 100644 --- a/engine/src/flutter/shell/common/BUILD.gn +++ b/engine/src/flutter/shell/common/BUILD.gn @@ -81,6 +81,8 @@ source_set("common") { "pointer_data_dispatcher.h", "rasterizer.cc", "rasterizer.h", + "resource_cache_limit_calculator.cc", + "resource_cache_limit_calculator.h", "run_configuration.cc", "run_configuration.h", "serialization_callbacks.cc", @@ -270,6 +272,7 @@ if (enable_unittests) { "persistent_cache_unittests.cc", "pipeline_unittests.cc", "rasterizer_unittests.cc", + "resource_cache_limit_calculator_unittests.cc", "shell_unittests.cc", "skp_shader_warmup_unittests.cc", "switches_unittests.cc", diff --git a/engine/src/flutter/shell/common/resource_cache_limit_calculator.cc b/engine/src/flutter/shell/common/resource_cache_limit_calculator.cc new file mode 100644 index 00000000000..52378c17cfb --- /dev/null +++ b/engine/src/flutter/shell/common/resource_cache_limit_calculator.cc @@ -0,0 +1,25 @@ +// 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/shell/common/resource_cache_limit_calculator.h" + +namespace flutter { + +size_t ResourceCacheLimitCalculator::GetResourceCacheMaxBytes() { + size_t max_bytes = 0; + size_t max_bytes_threshold = max_bytes_threshold_ > 0 + ? max_bytes_threshold_ + : std::numeric_limits::max(); + std::vector> live_items; + for (auto item : items_) { + if (item) { + live_items.push_back(item); + max_bytes += item->GetResourceCacheLimit(); + } + } + items_ = std::move(live_items); + return std::min(max_bytes, max_bytes_threshold); +} + +} // namespace flutter diff --git a/engine/src/flutter/shell/common/resource_cache_limit_calculator.h b/engine/src/flutter/shell/common/resource_cache_limit_calculator.h new file mode 100644 index 00000000000..cb47727ec4e --- /dev/null +++ b/engine/src/flutter/shell/common/resource_cache_limit_calculator.h @@ -0,0 +1,48 @@ +// 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_RESOURCE_CACHE_LIMIT_CALCULATOR_ +#define FLUTTER_SHELL_COMMON_RESOURCE_CACHE_LIMIT_CALCULATOR_ + +#include +#include + +#include "flutter/fml/macros.h" +#include "flutter/fml/memory/weak_ptr.h" + +namespace flutter { +class ResourceCacheLimitItem { + public: + // The expected GPU resource cache limit in bytes. This will be called on the + // platform thread. + virtual size_t GetResourceCacheLimit() = 0; + + protected: + virtual ~ResourceCacheLimitItem() = default; +}; + +class ResourceCacheLimitCalculator { + public: + ResourceCacheLimitCalculator(size_t max_bytes_threshold) + : max_bytes_threshold_(max_bytes_threshold) {} + + ~ResourceCacheLimitCalculator() = default; + + // This will be called on the platform thread. + void AddResourceCacheLimitItem(fml::WeakPtr item) { + items_.push_back(item); + } + + // The maximum GPU resource cache limit in bytes calculated by + // 'ResourceCacheLimitItem's. This will be called on the platform thread. + size_t GetResourceCacheMaxBytes(); + + private: + std::vector> items_; + size_t max_bytes_threshold_; + FML_DISALLOW_COPY_AND_ASSIGN(ResourceCacheLimitCalculator); +}; +} // namespace flutter + +#endif // FLUTTER_SHELL_COMMON_RESOURCE_CACHE_LIMIT_CALCULATOR_ diff --git a/engine/src/flutter/shell/common/resource_cache_limit_calculator_unittests.cc b/engine/src/flutter/shell/common/resource_cache_limit_calculator_unittests.cc new file mode 100644 index 00000000000..dc928d4c508 --- /dev/null +++ b/engine/src/flutter/shell/common/resource_cache_limit_calculator_unittests.cc @@ -0,0 +1,54 @@ +// 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/shell/common/resource_cache_limit_calculator.h" + +#include "gtest/gtest.h" + +namespace flutter { +namespace testing { + +class TestResourceCacheLimitItem : public ResourceCacheLimitItem { + public: + explicit TestResourceCacheLimitItem(size_t resource_cache_limit) + : resource_cache_limit_(resource_cache_limit), weak_factory_(this) {} + + size_t GetResourceCacheLimit() override { return resource_cache_limit_; } + + fml::WeakPtr GetWeakPtr() { + return weak_factory_.GetWeakPtr(); + } + + private: + size_t resource_cache_limit_; + fml::WeakPtrFactory weak_factory_; +}; + +TEST(ResourceCacheLimitCalculatorTest, GetResourceCacheMaxBytes) { + ResourceCacheLimitCalculator calculator(800U); + auto item1 = std::make_unique(100.0); + calculator.AddResourceCacheLimitItem(item1->GetWeakPtr()); + EXPECT_EQ(calculator.GetResourceCacheMaxBytes(), static_cast(100U)); + + auto item2 = std::make_unique(200.0); + calculator.AddResourceCacheLimitItem(item2->GetWeakPtr()); + EXPECT_EQ(calculator.GetResourceCacheMaxBytes(), static_cast(300U)); + + auto item3 = std::make_unique(300.0); + calculator.AddResourceCacheLimitItem(item3->GetWeakPtr()); + EXPECT_EQ(calculator.GetResourceCacheMaxBytes(), static_cast(600U)); + + auto item4 = std::make_unique(400.0); + calculator.AddResourceCacheLimitItem(item4->GetWeakPtr()); + EXPECT_EQ(calculator.GetResourceCacheMaxBytes(), static_cast(800U)); + + item3.reset(); + EXPECT_EQ(calculator.GetResourceCacheMaxBytes(), static_cast(700U)); + + item2.reset(); + EXPECT_EQ(calculator.GetResourceCacheMaxBytes(), static_cast(500U)); +} + +} // namespace testing +} // namespace flutter diff --git a/engine/src/flutter/shell/common/shell.cc b/engine/src/flutter/shell/common/shell.cc index b9858f3083d..f240a595d9f 100644 --- a/engine/src/flutter/shell/common/shell.cc +++ b/engine/src/flutter/shell/common/shell.cc @@ -147,10 +147,14 @@ std::unique_ptr Shell::Create( if (!isolate_snapshot) { isolate_snapshot = vm->GetVMData()->GetIsolateSnapshot(); } + auto resource_cache_limit_calculator = + std::make_shared( + settings.resource_cache_max_bytes_threshold); return CreateWithSnapshot(std::move(platform_data), // std::move(task_runners), // /*parent_merger=*/nullptr, // /*parent_io_manager=*/nullptr, // + resource_cache_limit_calculator, // std::move(settings), // std::move(vm), // std::move(isolate_snapshot), // @@ -163,6 +167,8 @@ std::unique_ptr Shell::CreateShellOnPlatformThread( DartVMRef vm, fml::RefPtr parent_merger, std::shared_ptr parent_io_manager, + const std::shared_ptr& + resource_cache_limit_calculator, TaskRunners task_runners, const PlatformData& platform_data, Settings settings, @@ -177,7 +183,8 @@ std::unique_ptr Shell::CreateShellOnPlatformThread( } auto shell = std::unique_ptr( - new Shell(std::move(vm), task_runners, parent_merger, settings, + new Shell(std::move(vm), task_runners, parent_merger, + resource_cache_limit_calculator, settings, std::make_shared( task_runners.GetUITaskRunner(), !settings.skia_deterministic_rendering_on_cpu), @@ -313,6 +320,8 @@ std::unique_ptr Shell::CreateWithSnapshot( TaskRunners task_runners, fml::RefPtr parent_thread_merger, std::shared_ptr parent_io_manager, + const std::shared_ptr& + resource_cache_limit_calculator, Settings settings, DartVMRef vm, fml::RefPtr isolate_snapshot, @@ -340,6 +349,7 @@ std::unique_ptr Shell::CreateWithSnapshot( &shell, // parent_thread_merger, // parent_io_manager, // + resource_cache_limit_calculator, // task_runners = std::move(task_runners), // platform_data = std::move(platform_data), // settings = std::move(settings), // @@ -353,6 +363,7 @@ std::unique_ptr Shell::CreateWithSnapshot( std::move(vm), // parent_thread_merger, // parent_io_manager, // + resource_cache_limit_calculator, // std::move(task_runners), // std::move(platform_data), // std::move(settings), // @@ -369,11 +380,14 @@ std::unique_ptr Shell::CreateWithSnapshot( Shell::Shell(DartVMRef vm, TaskRunners task_runners, fml::RefPtr parent_merger, + const std::shared_ptr& + resource_cache_limit_calculator, Settings settings, std::shared_ptr volatile_path_tracker, bool is_gpu_disabled) : task_runners_(std::move(task_runners)), parent_raster_thread_merger_(parent_merger), + resource_cache_limit_calculator_(resource_cache_limit_calculator), settings_(std::move(settings)), vm_(std::move(vm)), is_gpu_disabled_sync_switch_(new fml::SyncSwitch(is_gpu_disabled)), @@ -385,6 +399,8 @@ Shell::Shell(DartVMRef vm, FML_DCHECK(task_runners_.GetPlatformTaskRunner()->RunsTasksOnCurrentThread()); display_manager_ = std::make_unique(); + resource_cache_limit_calculator->AddResourceCacheLimitItem( + weak_factory_.GetWeakPtr()); // Generate a WeakPtrFactory for use with the raster thread. This does not // need to wait on a latch because it can only ever be used from the raster @@ -502,8 +518,9 @@ std::unique_ptr Shell::Spawn( .SetIfTrue([&is_gpu_disabled] { is_gpu_disabled = true; })); std::unique_ptr result = CreateWithSnapshot( PlatformData{}, task_runners_, rasterizer_->GetRasterThreadMerger(), - io_manager_, GetSettings(), vm_, vm_->GetVMData()->GetIsolateSnapshot(), - on_create_platform_view, on_create_rasterizer, + io_manager_, resource_cache_limit_calculator_, GetSettings(), vm_, + vm_->GetVMData()->GetIsolateSnapshot(), on_create_platform_view, + on_create_rasterizer, [engine = this->engine_.get(), initial_route]( Engine::Delegate& delegate, const PointerDataDispatcherMaker& dispatcher_maker, DartVM& vm, @@ -911,12 +928,15 @@ void Shell::OnPlatformViewSetViewportMetrics(const ViewportMetrics& metrics) { } // This is the formula Android uses. - // https://android.googlesource.com/platform/frameworks/base/+/master/libs/hwui/renderthread/CacheManager.cpp#41 - size_t max_bytes = metrics.physical_width * metrics.physical_height * 12 * 4; + // https://android.googlesource.com/platform/frameworks/base/+/39ae5bac216757bc201490f4c7b8c0f63006c6cd/libs/hwui/renderthread/CacheManager.cpp#45 + resource_cache_limit_ = + metrics.physical_width * metrics.physical_height * 12 * 4; + size_t resource_cache_max_bytes = + resource_cache_limit_calculator_->GetResourceCacheMaxBytes(); task_runners_.GetRasterTaskRunner()->PostTask( - [rasterizer = rasterizer_->GetWeakPtr(), max_bytes] { + [rasterizer = rasterizer_->GetWeakPtr(), resource_cache_max_bytes] { if (rasterizer) { - rasterizer->SetResourceCacheMaxBytes(max_bytes, false); + rasterizer->SetResourceCacheMaxBytes(resource_cache_max_bytes, false); } }); diff --git a/engine/src/flutter/shell/common/shell.h b/engine/src/flutter/shell/common/shell.h index 562d64ded2c..da12183d2c3 100644 --- a/engine/src/flutter/shell/common/shell.h +++ b/engine/src/flutter/shell/common/shell.h @@ -38,6 +38,7 @@ #include "flutter/shell/common/engine.h" #include "flutter/shell/common/platform_view.h" #include "flutter/shell/common/rasterizer.h" +#include "flutter/shell/common/resource_cache_limit_calculator.h" #include "flutter/shell/common/shell_io_manager.h" namespace flutter { @@ -108,7 +109,8 @@ class Shell final : public PlatformView::Delegate, public Animator::Delegate, public Engine::Delegate, public Rasterizer::Delegate, - public ServiceProtocol::Handler { + public ServiceProtocol::Handler, + public ResourceCacheLimitItem { public: template using CreateCallback = std::function(Shell&)>; @@ -410,6 +412,9 @@ class Shell final : public PlatformView::Delegate, const TaskRunners task_runners_; const fml::RefPtr parent_raster_thread_merger_; + std::shared_ptr + resource_cache_limit_calculator_; + size_t resource_cache_limit_; const Settings settings_; DartVMRef vm_; mutable std::mutex time_recorder_mutex_; @@ -476,6 +481,8 @@ class Shell final : public PlatformView::Delegate, Shell(DartVMRef vm, TaskRunners task_runners, fml::RefPtr parent_merger, + const std::shared_ptr& + resource_cache_limit_calculator, Settings settings, std::shared_ptr volatile_path_tracker, bool is_gpu_disabled); @@ -484,6 +491,8 @@ class Shell final : public PlatformView::Delegate, DartVMRef vm, fml::RefPtr parent_merger, std::shared_ptr parent_io_manager, + const std::shared_ptr& + resource_cache_limit_calculator, TaskRunners task_runners, const PlatformData& platform_data, Settings settings, @@ -492,11 +501,14 @@ class Shell final : public PlatformView::Delegate, const Shell::CreateCallback& on_create_rasterizer, const EngineCreateCallback& on_create_engine, bool is_gpu_disabled); + static std::unique_ptr CreateWithSnapshot( const PlatformData& platform_data, TaskRunners task_runners, fml::RefPtr parent_thread_merger, std::shared_ptr parent_io_manager, + const std::shared_ptr& + resource_cache_limit_calculator, Settings settings, DartVMRef vm, fml::RefPtr isolate_snapshot, @@ -694,6 +706,9 @@ class Shell final : public PlatformView::Delegate, const ServiceProtocol::Handler::ServiceProtocolMap& params, rapidjson::Document* response); + // |ResourceCacheLimitItem| + size_t GetResourceCacheLimit() override { return resource_cache_limit_; }; + // Creates an asset bundle from the original settings asset path or // directory. std::unique_ptr RestoreOriginalAssetResolver(); diff --git a/engine/src/flutter/shell/common/shell_unittests.cc b/engine/src/flutter/shell/common/shell_unittests.cc index 20b2aea6c35..34669c28fad 100644 --- a/engine/src/flutter/shell/common/shell_unittests.cc +++ b/engine/src/flutter/shell/common/shell_unittests.cc @@ -107,6 +107,7 @@ class MockPlatformViewDelegate : public PlatformView::Delegate { }; class MockSurface : public Surface { + public: MOCK_METHOD0(IsValid, bool()); MOCK_METHOD1(AcquireFrame, @@ -130,6 +131,13 @@ class MockPlatformView : public PlatformView { std::shared_ptr()); }; +class TestPlatformView : public PlatformView { + public: + TestPlatformView(Shell& shell, TaskRunners task_runners) + : PlatformView(shell, task_runners) {} + MOCK_METHOD0(CreateRenderingSurface, std::unique_ptr()); +}; + class MockPlatformMessageHandler : public PlatformMessageHandler { public: MOCK_METHOD1(HandlePlatformMessage, @@ -1562,6 +1570,128 @@ static size_t GetRasterizerResourceCacheBytesSync(const Shell& shell) { return bytes; } +TEST_F(ShellTest, MultipleFluttersSetResourceCacheBytes) { + TaskRunners task_runners = GetTaskRunnersForFixture(); + auto settings = CreateSettingsForFixture(); + settings.resource_cache_max_bytes_threshold = 4000000U; + GrMockOptions main_context_options; + sk_sp main_context = + GrDirectContext::MakeMock(&main_context_options); + Shell::CreateCallback platform_view_create_callback = + [task_runners, main_context](flutter::Shell& shell) { + auto result = std::make_unique(shell, task_runners); + ON_CALL(*result, CreateRenderingSurface()) + .WillByDefault(::testing::Invoke([main_context] { + auto surface = std::make_unique(); + ON_CALL(*surface, GetContext()) + .WillByDefault(Return(main_context.get())); + ON_CALL(*surface, IsValid()).WillByDefault(Return(true)); + ON_CALL(*surface, MakeRenderContextCurrent()) + .WillByDefault(::testing::Invoke([] { + return std::make_unique(true); + })); + return surface; + })); + return result; + }; + + auto shell = CreateShell( + /*settings=*/std::move(settings), + /*task_runners=*/task_runners, + /*simulate_vsync=*/false, + /*shell_test_external_view_embedder=*/nullptr, + /*is_gpu_disabled=*/false, + /*rendering_backend=*/ + ShellTestPlatformView::BackendType::kDefaultBackend, + /*platform_view_create_callback=*/platform_view_create_callback); + + // Create the surface needed by rasterizer + PlatformViewNotifyCreated(shell.get()); + + auto configuration = RunConfiguration::InferFromSettings(settings); + configuration.SetEntrypoint("emptyMain"); + + RunEngine(shell.get(), std::move(configuration)); + PostSync(shell->GetTaskRunners().GetPlatformTaskRunner(), [&shell]() { + shell->GetPlatformView()->SetViewportMetrics({1.0, 100, 100, 22}); + }); + + // first cache bytes + EXPECT_EQ(GetRasterizerResourceCacheBytesSync(*shell), + static_cast(480000U)); + + auto shell_spawn_callback = [&]() { + std::unique_ptr spawn; + PostSync( + shell->GetTaskRunners().GetPlatformTaskRunner(), + [this, &spawn, &spawner = shell, platform_view_create_callback]() { + auto configuration = + RunConfiguration::InferFromSettings(CreateSettingsForFixture()); + configuration.SetEntrypoint("emptyMain"); + spawn = spawner->Spawn( + std::move(configuration), "", platform_view_create_callback, + [](Shell& shell) { return std::make_unique(shell); }); + ASSERT_NE(nullptr, spawn.get()); + ASSERT_TRUE(ValidateShell(spawn.get())); + }); + return spawn; + }; + + std::unique_ptr second_shell = shell_spawn_callback(); + PlatformViewNotifyCreated(second_shell.get()); + PostSync(second_shell->GetTaskRunners().GetPlatformTaskRunner(), + [&second_shell]() { + second_shell->GetPlatformView()->SetViewportMetrics( + {1.0, 100, 100, 22}); + }); + // first cache bytes + second cache bytes + EXPECT_EQ(GetRasterizerResourceCacheBytesSync(*shell), + static_cast(960000U)); + + PostSync(second_shell->GetTaskRunners().GetPlatformTaskRunner(), + [&second_shell]() { + second_shell->GetPlatformView()->SetViewportMetrics( + {1.0, 100, 300, 22}); + }); + // first cache bytes + second cache bytes + EXPECT_EQ(GetRasterizerResourceCacheBytesSync(*shell), + static_cast(1920000U)); + + std::unique_ptr third_shell = shell_spawn_callback(); + PlatformViewNotifyCreated(third_shell.get()); + PostSync( + third_shell->GetTaskRunners().GetPlatformTaskRunner(), [&third_shell]() { + third_shell->GetPlatformView()->SetViewportMetrics({1.0, 400, 100, 22}); + }); + // first cache bytes + second cache bytes + third cache bytes + EXPECT_EQ(GetRasterizerResourceCacheBytesSync(*shell), + static_cast(3840000U)); + + PostSync( + third_shell->GetTaskRunners().GetPlatformTaskRunner(), [&third_shell]() { + third_shell->GetPlatformView()->SetViewportMetrics({1.0, 800, 100, 22}); + }); + // max bytes threshold + EXPECT_EQ(GetRasterizerResourceCacheBytesSync(*shell), + static_cast(4000000U)); + DestroyShell(std::move(third_shell), std::move(task_runners)); + // max bytes threshold + EXPECT_EQ(GetRasterizerResourceCacheBytesSync(*shell), + static_cast(4000000U)); + + PostSync(second_shell->GetTaskRunners().GetPlatformTaskRunner(), + [&second_shell]() { + second_shell->GetPlatformView()->SetViewportMetrics( + {1.0, 100, 100, 22}); + }); + // first cache bytes + second cache bytes + EXPECT_EQ(GetRasterizerResourceCacheBytesSync(*shell), + static_cast(960000U)); + + DestroyShell(std::move(second_shell), std::move(task_runners)); + DestroyShell(std::move(shell), std::move(task_runners)); +} + TEST_F(ShellTest, SetResourceCacheSize) { Settings settings = CreateSettingsForFixture(); auto task_runner = CreateNewThread(); diff --git a/engine/src/flutter/shell/common/switches.cc b/engine/src/flutter/shell/common/switches.cc index 25c5da36dc9..9812bcf8bc0 100644 --- a/engine/src/flutter/shell/common/switches.cc +++ b/engine/src/flutter/shell/common/switches.cc @@ -470,6 +470,16 @@ Settings SettingsFromCommandLine(const fml::CommandLine& command_line) { settings.old_gen_heap_size = std::stoi(old_gen_heap_size); } + if (command_line.HasOption( + FlagForSwitch(Switch::ResourceCacheMaxBytesThreshold))) { + std::string resource_cache_max_bytes_threshold; + command_line.GetOptionValue( + FlagForSwitch(Switch::ResourceCacheMaxBytesThreshold), + &resource_cache_max_bytes_threshold); + settings.resource_cache_max_bytes_threshold = + std::stoi(resource_cache_max_bytes_threshold); + } + if (command_line.HasOption(FlagForSwitch(Switch::MsaaSamples))) { std::string msaa_samples; command_line.GetOptionValue(FlagForSwitch(Switch::MsaaSamples), diff --git a/engine/src/flutter/shell/common/switches.h b/engine/src/flutter/shell/common/switches.h index b5f64cef5e7..be393d88bc3 100644 --- a/engine/src/flutter/shell/common/switches.h +++ b/engine/src/flutter/shell/common/switches.h @@ -220,6 +220,10 @@ DEF_SWITCH( DEF_SWITCH(OldGenHeapSize, "old-gen-heap-size", "The size limit in megabytes for the Dart VM old gen heap space.") + +DEF_SWITCH(ResourceCacheMaxBytesThreshold, + "resource-cache-max-bytes-threshold", + "The max bytes threshold of resource cache, or 0 for unlimited.") DEF_SWITCH(EnableSkParagraph, "enable-skparagraph", "Selects the SkParagraph implementation of the text layout engine.") diff --git a/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/loader/FlutterLoader.java b/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/loader/FlutterLoader.java index a4b2394d6de..e76b16fcc9a 100644 --- a/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/loader/FlutterLoader.java +++ b/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/loader/FlutterLoader.java @@ -15,6 +15,7 @@ import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.os.SystemClock; +import android.util.DisplayMetrics; import android.view.WindowManager; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -302,9 +303,16 @@ public class FlutterLoader { activityManager.getMemoryInfo(memInfo); oldGenHeapSizeMegaBytes = (int) (memInfo.totalMem / 1e6 / 2); } - shellArgs.add("--old-gen-heap-size=" + oldGenHeapSizeMegaBytes); + DisplayMetrics displayMetrics = applicationContext.getResources().getDisplayMetrics(); + int screenWidth = displayMetrics.widthPixels; + int screenHeight = displayMetrics.heightPixels; + // This is the formula Android uses. + // https://android.googlesource.com/platform/frameworks/base/+/39ae5bac216757bc201490f4c7b8c0f63006c6cd/libs/hwui/renderthread/CacheManager.cpp#45 + int resourceCacheMaxBytesThreshold = screenWidth * screenHeight * 12 * 4; + shellArgs.add("--resource-cache-max-bytes-threshold=" + resourceCacheMaxBytesThreshold); + shellArgs.add("--prefetched-default-font-manager"); if (metaData == null || metaData.getBoolean(ENABLE_SKPARAGRAPH_META_DATA_KEY, true)) { diff --git a/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/loader/FlutterLoaderTest.java b/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/loader/FlutterLoaderTest.java index 4ec39d00ae5..d925bc42026 100644 --- a/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/loader/FlutterLoaderTest.java +++ b/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/loader/FlutterLoaderTest.java @@ -22,6 +22,7 @@ import android.annotation.TargetApi; import android.app.ActivityManager; import android.content.Context; import android.os.Bundle; +import android.util.DisplayMetrics; import androidx.test.ext.junit.runners.AndroidJUnit4; import io.flutter.embedding.engine.FlutterJNI; import java.util.Arrays; @@ -87,6 +88,36 @@ public class FlutterLoaderTest { assertTrue(arguments.contains(oldGenHeapArg)); } + @Test + public void itDefaultsTheResourceCacheMaxBytesThresholdAppropriately() { + FlutterJNI mockFlutterJNI = mock(FlutterJNI.class); + FlutterLoader flutterLoader = new FlutterLoader(mockFlutterJNI); + + assertFalse(flutterLoader.initialized()); + flutterLoader.startInitialization(RuntimeEnvironment.application); + flutterLoader.ensureInitializationComplete(RuntimeEnvironment.application, null); + shadowOf(getMainLooper()).idle(); + + DisplayMetrics displayMetrics = + RuntimeEnvironment.application.getResources().getDisplayMetrics(); + int screenWidth = displayMetrics.widthPixels; + int screenHeight = displayMetrics.heightPixels; + int resourceCacheMaxBytesThreshold = screenWidth * screenHeight * 12 * 4; + final String resourceCacheMaxBytesThresholdArg = + "--resource-cache-max-bytes-threshold=" + resourceCacheMaxBytesThreshold; + ArgumentCaptor shellArgsCaptor = ArgumentCaptor.forClass(String[].class); + verify(mockFlutterJNI, times(1)) + .init( + eq(RuntimeEnvironment.application), + shellArgsCaptor.capture(), + anyString(), + anyString(), + anyString(), + anyLong()); + List arguments = Arrays.asList(shellArgsCaptor.getValue()); + assertTrue(arguments.contains(resourceCacheMaxBytesThresholdArg)); + } + @Test public void itSetsLeakVMToTrueByDefault() { FlutterJNI mockFlutterJNI = mock(FlutterJNI.class); diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterDartProject.mm b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterDartProject.mm index c136cae373b..e5e350b2ba0 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterDartProject.mm +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterDartProject.mm @@ -195,6 +195,14 @@ flutter::Settings FLTDefaultSettingsForBundle(NSBundle* bundle) { settings.old_gen_heap_size = std::round([NSProcessInfo processInfo].physicalMemory * .48 / flutter::kMegaByteSizeInBytes); } + + // This is the formula Android uses. + // https://android.googlesource.com/platform/frameworks/base/+/39ae5bac216757bc201490f4c7b8c0f63006c6cd/libs/hwui/renderthread/CacheManager.cpp#45 + CGFloat scale = [UIScreen mainScreen].scale; + CGFloat screenWidth = [UIScreen mainScreen].bounds.size.width * scale; + CGFloat screenHeight = [UIScreen mainScreen].bounds.size.height * scale; + settings.resource_cache_max_bytes_threshold = screenWidth * screenHeight * 12 * 4; + return settings; } diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterDartProjectTest.mm b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterDartProjectTest.mm index c899793e405..57d98ea85dd 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterDartProjectTest.mm +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterDartProjectTest.mm @@ -29,6 +29,16 @@ FLUTTER_ASSERT_ARC XCTAssertEqual(project.settings.old_gen_heap_size, old_gen_heap_size); } +- (void)testResourceCacheMaxBytesThresholdSetting { + FlutterDartProject* project = [[FlutterDartProject alloc] init]; + CGFloat scale = [UIScreen mainScreen].scale; + CGFloat screenWidth = [UIScreen mainScreen].bounds.size.width * scale; + CGFloat screenHeight = [UIScreen mainScreen].bounds.size.height * scale; + size_t resource_cache_max_bytes_threshold = screenWidth * screenHeight * 12 * 4; + XCTAssertEqual(project.settings.resource_cache_max_bytes_threshold, + resource_cache_max_bytes_threshold); +} + - (void)testMainBundleSettingsAreCorrectlyParsed { NSBundle* mainBundle = [NSBundle mainBundle]; NSDictionary* appTransportSecurity =