diff --git a/shell/common/BUILD.gn b/shell/common/BUILD.gn index 135bf43ae1799..bd4f7cae5ec52 100644 --- a/shell/common/BUILD.gn +++ b/shell/common/BUILD.gn @@ -164,6 +164,7 @@ if (current_toolchain == host_toolchain) { ":shell_unittests_gpu_configuration", "$flutter_root/common", "$flutter_root/flow", + "$flutter_root/lib/ui:ui", "$flutter_root/shell", "$flutter_root/testing:dart", "$flutter_root/testing:opengl", diff --git a/shell/common/fixtures/shell_test.dart b/shell/common/fixtures/shell_test.dart index 9dae97c1f1a37..dc7abff12381c 100644 --- a/shell/common/fixtures/shell_test.dart +++ b/shell/common/fixtures/shell_test.dart @@ -2,7 +2,9 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'dart:convert' show utf8; import 'dart:isolate'; +import 'dart:typed_data'; import 'dart:ui'; void main() {} @@ -57,3 +59,19 @@ void testCanLaunchSecondaryIsolate() { Isolate.spawn(secondaryIsolateMain, 'Hello from root isolate.'); notifyNative(); } + +@pragma('vm:entry-point') +void testSkiaResourceCacheSendsResponse() { + final PlatformMessageResponseCallback callback = (ByteData data) { + notifyNative(); + }; + const String json = '''{ + "method": "Skia.setResourceCacheMaxBytes", + "args": 10000 + }'''; + window.sendPlatformMessage( + 'flutter/skia', + Uint8List.fromList(utf8.encode(json)).buffer.asByteData(), + callback, + ); +} diff --git a/shell/common/rasterizer.cc b/shell/common/rasterizer.cc index 0d3cbcd0a11e8..35b4b29af7e8a 100644 --- a/shell/common/rasterizer.cc +++ b/shell/common/rasterizer.cc @@ -43,6 +43,7 @@ Rasterizer::Rasterizer( : delegate_(delegate), task_runners_(std::move(task_runners)), compositor_context_(std::move(compositor_context)), + user_override_resource_cache_bytes_(false), weak_factory_(this) { FML_DCHECK(compositor_context_); } @@ -55,6 +56,10 @@ fml::WeakPtr Rasterizer::GetWeakPtr() const { void Rasterizer::Setup(std::unique_ptr surface) { surface_ = std::move(surface); + if (max_cache_bytes_.has_value()) { + SetResourceCacheMaxBytes(max_cache_bytes_.value(), + user_override_resource_cache_bytes_); + } compositor_context_->OnGrContextCreated(); } @@ -355,7 +360,20 @@ void Rasterizer::FireNextFrameCallbackIfPresent() { callback(); } -void Rasterizer::SetResourceCacheMaxBytes(int max_bytes) { +void Rasterizer::SetResourceCacheMaxBytes(size_t max_bytes, bool from_user) { + user_override_resource_cache_bytes_ |= from_user; + + if (!from_user && user_override_resource_cache_bytes_) { + // We should not update the setting here if a user has explicitly set a + // value for this over the flutter/skia channel. + return; + } + + max_cache_bytes_ = max_bytes; + if (!surface_) { + return; + } + GrContext* context = surface_->GetContext(); if (context) { int max_resources; @@ -364,6 +382,19 @@ void Rasterizer::SetResourceCacheMaxBytes(int max_bytes) { } } +std::optional Rasterizer::GetResourceCacheMaxBytes() const { + if (!surface_) { + return std::nullopt; + } + GrContext* context = surface_->GetContext(); + if (context) { + size_t max_bytes; + context->getResourceCacheLimits(nullptr, &max_bytes); + return max_bytes; + } + return std::nullopt; +} + Rasterizer::Screenshot::Screenshot() {} Rasterizer::Screenshot::Screenshot(sk_sp p_data, SkISize p_size) diff --git a/shell/common/rasterizer.h b/shell/common/rasterizer.h index daa6f0fe19e44..0271a441dfa2e 100644 --- a/shell/common/rasterizer.h +++ b/shell/common/rasterizer.h @@ -6,6 +6,7 @@ #define SHELL_COMMON_RASTERIZER_H_ #include +#include #include "flutter/common/settings.h" #include "flutter/common/task_runners.h" @@ -374,8 +375,25 @@ class Rasterizer final { /// /// @param[in] max_bytes The maximum byte size of resource that may be /// cached for GPU rendering. + /// @param[in] from_user Whether this request was from user code, e.g. via + /// the flutter/skia message channel, in which case + /// it should not be overridden by the platform. /// - void SetResourceCacheMaxBytes(int max_bytes); + void SetResourceCacheMaxBytes(size_t max_bytes, bool from_user); + + //---------------------------------------------------------------------------- + /// @brief The current value of Skia's resource cache size, if a surface + /// is present. + /// + /// @attention This cache does not describe the entirety of GPU resources + /// that may be cached. The `RasterCache` also holds very large + /// GPU resources. + /// + /// @see `RasterCache` + /// + /// @return The size of Skia's resource cache, if available. + /// + std::optional GetResourceCacheMaxBytes() const; private: Delegate& delegate_; @@ -384,6 +402,8 @@ class Rasterizer final { std::unique_ptr compositor_context_; std::unique_ptr last_layer_tree_; fml::closure next_frame_callback_; + bool user_override_resource_cache_bytes_; + std::optional max_cache_bytes_; fml::WeakPtrFactory weak_factory_; void DoDraw(std::unique_ptr layer_tree); diff --git a/shell/common/shell.cc b/shell/common/shell.cc index 7bba32b761a83..29f3b2dd286bb 100644 --- a/shell/common/shell.cc +++ b/shell/common/shell.cc @@ -682,6 +682,16 @@ void Shell::OnPlatformViewSetViewportMetrics(const ViewportMetrics& metrics) { FML_DCHECK(is_setup_); FML_DCHECK(task_runners_.GetPlatformTaskRunner()->RunsTasksOnCurrentThread()); + // 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; + task_runners_.GetGPUTaskRunner()->PostTask( + [rasterizer = rasterizer_->GetWeakPtr(), max_bytes] { + if (rasterizer) { + rasterizer->SetResourceCacheMaxBytes(max_bytes, false); + } + }); + task_runners_.GetUITaskRunner()->PostTask( [engine = engine_->GetWeakPtr(), metrics]() { if (engine) { @@ -939,10 +949,14 @@ void Shell::HandleEngineSkiaMessage(fml::RefPtr message) { return; task_runners_.GetGPUTaskRunner()->PostTask( - [rasterizer = rasterizer_->GetWeakPtr(), - max_bytes = args->value.GetInt()] { + [rasterizer = rasterizer_->GetWeakPtr(), max_bytes = args->value.GetInt(), + response = std::move(message->response())] { if (rasterizer) { - rasterizer->SetResourceCacheMaxBytes(max_bytes); + rasterizer->SetResourceCacheMaxBytes(static_cast(max_bytes), + true); + } + if (response) { + response->CompleteEmpty(); } }); } diff --git a/shell/common/shell_test.cc b/shell/common/shell_test.cc index 491263fccf8a4..414275a3dcb49 100644 --- a/shell/common/shell_test.cc +++ b/shell/common/shell_test.cc @@ -22,6 +22,21 @@ ShellTest::ShellTest() ShellTest::~ShellTest() = default; +void ShellTest::SendEnginePlatformMessage( + Shell* shell, + fml::RefPtr message) { + fml::AutoResetWaitableEvent latch; + fml::TaskRunner::RunNowOrPostTask( + shell->GetTaskRunners().GetPlatformTaskRunner(), + [shell, &latch, message = std::move(message)]() { + if (auto engine = shell->weak_engine_) { + engine->HandlePlatformMessage(std::move(message)); + } + latch.Signal(); + }); + latch.Wait(); +} + void ShellTest::SetSnapshotsAndAssets(Settings& settings) { if (!assets_dir_.is_valid()) { return; diff --git a/shell/common/shell_test.h b/shell/common/shell_test.h index 4a69e7e516e3a..662453c0d0c44 100644 --- a/shell/common/shell_test.h +++ b/shell/common/shell_test.h @@ -9,6 +9,7 @@ #include "flutter/common/settings.h" #include "flutter/fml/macros.h" +#include "flutter/lib/ui/window/platform_message.h" #include "flutter/shell/common/run_configuration.h" #include "flutter/shell/common/shell.h" #include "flutter/shell/common/thread_host.h" @@ -32,6 +33,9 @@ class ShellTest : public ThreadTest { TaskRunners task_runners); TaskRunners GetTaskRunnersForFixture(); + void SendEnginePlatformMessage(Shell* shell, + fml::RefPtr message); + void AddNativeCallback(std::string name, Dart_NativeFunction callback); static void PlatformViewNotifyCreated( diff --git a/shell/common/shell_unittests.cc b/shell/common/shell_unittests.cc index 45d2a658e2707..6d8545e999eda 100644 --- a/shell/common/shell_unittests.cc +++ b/shell/common/shell_unittests.cc @@ -264,11 +264,11 @@ TEST_F(ShellTest, NoNeedToReportTimingsByDefault) { ASSERT_FALSE(GetNeedsReportTimings(shell.get())); // This assertion may or may not be the direct result of needs_report_timings_ - // being false. The count could be 0 simply because we just cleared unreported - // timings by reporting them. Hence this can't replace the - // ASSERT_FALSE(GetNeedsReportTimings(shell.get())) check. We added this - // assertion for an additional confidence that we're not pushing back to - // unreported timings unnecessarily. + // being false. The count could be 0 simply because we just cleared + // unreported timings by reporting them. Hence this can't replace the + // ASSERT_FALSE(GetNeedsReportTimings(shell.get())) check. We added + // this assertion for an additional confidence that we're not pushing + // back to unreported timings unnecessarily. // // Conversely, do not assert UnreportedTimingsCount(shell.get()) to be // positive in any tests. Otherwise those tests will be flaky as the clearing @@ -600,5 +600,124 @@ TEST_F(ShellTest, WaitForFirstFrameInlined) { ASSERT_FALSE(event.WaitWithTimeout(fml::TimeDelta::FromMilliseconds(1000))); } +TEST_F(ShellTest, SetResourceCacheSize) { + Settings settings = CreateSettingsForFixture(); + auto task_runner = GetThreadTaskRunner(); + TaskRunners task_runners("test", task_runner, task_runner, task_runner, + task_runner); + std::unique_ptr shell = + CreateShell(std::move(settings), std::move(task_runners)); + + // Create the surface needed by rasterizer + PlatformViewNotifyCreated(shell.get()); + + auto configuration = RunConfiguration::InferFromSettings(settings); + configuration.SetEntrypoint("emptyMain"); + + RunEngine(shell.get(), std::move(configuration)); + PumpOneFrame(shell.get()); + + EXPECT_EQ(shell->GetRasterizer()->GetResourceCacheMaxBytes().value_or(0U), + static_cast(24 * (1 << 20))); + + fml::TaskRunner::RunNowOrPostTask( + shell->GetTaskRunners().GetPlatformTaskRunner(), [&shell]() { + shell->GetPlatformView()->SetViewportMetrics( + {1.0, 400, 200, 0, 0, 0, 0, 0, 0, 0, 0}); + }); + PumpOneFrame(shell.get()); + + EXPECT_EQ(shell->GetRasterizer()->GetResourceCacheMaxBytes().value_or(0U), + 3840000U); + + std::string request_json = R"json({ + "method": "Skia.setResourceCacheMaxBytes", + "args": 10000 + })json"; + std::vector data(request_json.begin(), request_json.end()); + auto platform_message = fml::MakeRefCounted( + "flutter/skia", std::move(data), nullptr); + SendEnginePlatformMessage(shell.get(), std::move(platform_message)); + PumpOneFrame(shell.get()); + EXPECT_EQ(shell->GetRasterizer()->GetResourceCacheMaxBytes().value_or(0U), + 10000U); + + fml::TaskRunner::RunNowOrPostTask( + shell->GetTaskRunners().GetPlatformTaskRunner(), [&shell]() { + shell->GetPlatformView()->SetViewportMetrics( + {1.0, 800, 400, 0, 0, 0, 0, 0, 0, 0, 0}); + }); + PumpOneFrame(shell.get()); + + EXPECT_EQ(shell->GetRasterizer()->GetResourceCacheMaxBytes().value_or(0U), + 10000U); +} + +TEST_F(ShellTest, SetResourceCacheSizeEarly) { + Settings settings = CreateSettingsForFixture(); + auto task_runner = GetThreadTaskRunner(); + TaskRunners task_runners("test", task_runner, task_runner, task_runner, + task_runner); + std::unique_ptr shell = + CreateShell(std::move(settings), std::move(task_runners)); + + fml::TaskRunner::RunNowOrPostTask( + shell->GetTaskRunners().GetPlatformTaskRunner(), [&shell]() { + shell->GetPlatformView()->SetViewportMetrics( + {1.0, 400, 200, 0, 0, 0, 0, 0, 0, 0, 0}); + }); + PumpOneFrame(shell.get()); + + // Create the surface needed by rasterizer + PlatformViewNotifyCreated(shell.get()); + + auto configuration = RunConfiguration::InferFromSettings(settings); + configuration.SetEntrypoint("emptyMain"); + + RunEngine(shell.get(), std::move(configuration)); + PumpOneFrame(shell.get()); + + EXPECT_EQ(shell->GetRasterizer()->GetResourceCacheMaxBytes().value_or(0), + static_cast(3840000U)); +} + +TEST_F(ShellTest, SetResourceCacheSizeNotifiesDart) { + Settings settings = CreateSettingsForFixture(); + auto task_runner = GetThreadTaskRunner(); + TaskRunners task_runners("test", task_runner, task_runner, task_runner, + task_runner); + std::unique_ptr shell = + CreateShell(std::move(settings), std::move(task_runners)); + + fml::TaskRunner::RunNowOrPostTask( + shell->GetTaskRunners().GetPlatformTaskRunner(), [&shell]() { + shell->GetPlatformView()->SetViewportMetrics( + {1.0, 400, 200, 0, 0, 0, 0, 0, 0, 0, 0}); + }); + PumpOneFrame(shell.get()); + + // Create the surface needed by rasterizer + PlatformViewNotifyCreated(shell.get()); + + auto configuration = RunConfiguration::InferFromSettings(settings); + configuration.SetEntrypoint("testSkiaResourceCacheSendsResponse"); + + EXPECT_EQ(shell->GetRasterizer()->GetResourceCacheMaxBytes().value_or(0), + static_cast(3840000U)); + + fml::AutoResetWaitableEvent latch; + AddNativeCallback("NotifyNative", CREATE_NATIVE_ENTRY([&latch](auto args) { + latch.Signal(); + })); + + RunEngine(shell.get(), std::move(configuration)); + PumpOneFrame(shell.get()); + + latch.Wait(); + + EXPECT_EQ(shell->GetRasterizer()->GetResourceCacheMaxBytes().value_or(0), + static_cast(10000U)); +} + } // namespace testing } // namespace flutter diff --git a/shell/gpu/gpu_surface_gl.cc b/shell/gpu/gpu_surface_gl.cc index d344e7225a595..6ddbcde23b3c3 100644 --- a/shell/gpu/gpu_surface_gl.cc +++ b/shell/gpu/gpu_surface_gl.cc @@ -28,7 +28,10 @@ static const int kGrCacheMaxCount = 8192; // Default maximum number of bytes of GPU memory of budgeted resources in the // cache. -static const size_t kGrCacheMaxByteSize = 512 * (1 << 20); +// The shell will dynamically increase or decrease this cache based on the +// viewport size, unless a user has specifically requested a size on the Skia +// system channel. +static const size_t kGrCacheMaxByteSize = 24 * (1 << 20); GPUSurfaceGL::GPUSurfaceGL(GPUSurfaceGLDelegate* delegate) : delegate_(delegate), weak_factory_(this) {