diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index 8b5fea520bf03..1c0977092063e 100755 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -308,6 +308,7 @@ FILE: ../../../flutter/lib/ui/fixtures/hello_loop_2.webp FILE: ../../../flutter/lib/ui/fixtures/ui_test.dart FILE: ../../../flutter/lib/ui/geometry.dart FILE: ../../../flutter/lib/ui/hash_codes.dart +FILE: ../../../flutter/lib/ui/hint_freed_delegate.h FILE: ../../../flutter/lib/ui/hooks.dart FILE: ../../../flutter/lib/ui/io_manager.h FILE: ../../../flutter/lib/ui/isolate_name_server.dart @@ -337,6 +338,7 @@ FILE: ../../../flutter/lib/ui/painting/image_decoder.h FILE: ../../../flutter/lib/ui/painting/image_decoder_unittests.cc FILE: ../../../flutter/lib/ui/painting/image_descriptor.cc FILE: ../../../flutter/lib/ui/painting/image_descriptor.h +FILE: ../../../flutter/lib/ui/painting/image_dispose_unittests.cc FILE: ../../../flutter/lib/ui/painting/image_encoding.cc FILE: ../../../flutter/lib/ui/painting/image_encoding.h FILE: ../../../flutter/lib/ui/painting/image_encoding_unittests.cc diff --git a/lib/ui/BUILD.gn b/lib/ui/BUILD.gn index decca25d81f90..6c72102c2d9b8 100644 --- a/lib/ui/BUILD.gn +++ b/lib/ui/BUILD.gn @@ -181,6 +181,7 @@ if (enable_unittests) { public_configs = [ "//flutter:export_dynamic_symbols" ] sources = [ + "painting/image_dispose_unittests.cc", "painting/image_encoding_unittests.cc", "painting/vertices_unittests.cc", "window/platform_configuration_unittests.cc", diff --git a/lib/ui/fixtures/ui_test.dart b/lib/ui/fixtures/ui_test.dart index e2fcdaa7c3e8b..87effaad4f5c7 100644 --- a/lib/ui/fixtures/ui_test.dart +++ b/lib/ui/fixtures/ui_test.dart @@ -3,6 +3,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'dart:async'; import 'dart:typed_data'; import 'dart:ui'; @@ -73,3 +74,61 @@ Future encodeImageProducesExternalUint8List() async { void _encodeImage(Image i, int format, void Function(Uint8List result)) native 'EncodeImage'; void _validateExternal(Uint8List result) native 'ValidateExternal'; + +@pragma('vm:entry-point') +Future pumpImage() async { + const int width = 6000; + const int height = 6000; + final Completer completer = Completer(); + decodeImageFromPixels( + Uint8List.fromList(List.filled(width * height * 4, 0xFF)), + width, + height, + PixelFormat.rgba8888, + (Image image) => completer.complete(image), + ); + final Image image = await completer.future; + + final FrameCallback renderBlank = (Duration duration) { + image.dispose(); + + final PictureRecorder recorder = PictureRecorder(); + final Canvas canvas = Canvas(recorder); + canvas.drawRect(Rect.largest, Paint()); + final Picture picture = recorder.endRecording(); + + final SceneBuilder builder = SceneBuilder(); + builder.addPicture(Offset.zero, picture); + + final Scene scene = builder.build(); + window.render(scene); + scene.dispose(); + window.onBeginFrame = (Duration duration) { + window.onDrawFrame = _onBeginFrameDone; + }; + window.scheduleFrame(); + }; + + final FrameCallback renderImage = (Duration duration) { + final PictureRecorder recorder = PictureRecorder(); + final Canvas canvas = Canvas(recorder); + canvas.drawImage(image, Offset.zero, Paint()); + final Picture picture = recorder.endRecording(); + + final SceneBuilder builder = SceneBuilder(); + builder.addPicture(Offset.zero, picture); + + _captureImageAndPicture(image, picture); + + final Scene scene = builder.build(); + window.render(scene); + scene.dispose(); + window.onBeginFrame = renderBlank; + window.scheduleFrame(); + }; + + window.onBeginFrame = renderImage; + window.scheduleFrame(); +} +void _captureImageAndPicture(Image image, Picture picture) native 'CaptureImageAndPicture'; +Future _onBeginFrameDone() native 'OnBeginFrameDone'; diff --git a/lib/ui/hint_freed_delegate.h b/lib/ui/hint_freed_delegate.h new file mode 100644 index 0000000000000..0e4a38b41454c --- /dev/null +++ b/lib/ui/hint_freed_delegate.h @@ -0,0 +1,24 @@ +// 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_LIB_UI_HINT_FREED_DELEGATE_H_ +#define FLUTTER_LIB_UI_HINT_FREED_DELEGATE_H_ + +namespace flutter { + +class HintFreedDelegate { + public: + //---------------------------------------------------------------------------- + /// @brief Notifies the engine that native bytes might be freed if a + /// garbage collection ran at the next NotifyIdle period. + /// + /// @param[in] size The number of bytes freed. This size adds to any + /// previously supplied value, rather than replacing. + /// + virtual void HintFreed(size_t size) = 0; +}; + +} // namespace flutter + +#endif // FLUTTER_LIB_UI_HINT_FREED_DELEGATE_H_ diff --git a/lib/ui/painting/image.cc b/lib/ui/painting/image.cc index 7da7c0ad029ec..64474227404a9 100644 --- a/lib/ui/painting/image.cc +++ b/lib/ui/painting/image.cc @@ -37,6 +37,10 @@ Dart_Handle CanvasImage::toByteData(int format, Dart_Handle callback) { } void CanvasImage::dispose() { + auto hint_freed_delegate = UIDartState::Current()->GetHintFreedDelegate(); + if (hint_freed_delegate) { + hint_freed_delegate->HintFreed(GetAllocationSize()); + } image_.reset(); ClearDartWrapper(); } diff --git a/lib/ui/painting/image_dispose_unittests.cc b/lib/ui/painting/image_dispose_unittests.cc new file mode 100644 index 0000000000000..3763d70539f95 --- /dev/null +++ b/lib/ui/painting/image_dispose_unittests.cc @@ -0,0 +1,121 @@ +// 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. + +#define FML_USED_ON_EMBEDDER + +#include "flutter/common/task_runners.h" +#include "flutter/fml/synchronization/waitable_event.h" +#include "flutter/lib/ui/painting/image.h" +#include "flutter/lib/ui/painting/picture.h" +#include "flutter/runtime/dart_vm.h" +#include "flutter/shell/common/shell_test.h" +#include "flutter/shell/common/thread_host.h" +#include "flutter/testing/testing.h" + +namespace flutter { +namespace testing { + +class ImageDisposeTest : public ShellTest { + public: + template + T* GetNativePeer(Dart_NativeArguments args, int index) { + auto handle = Dart_GetNativeArgument(args, index); + intptr_t peer = 0; + EXPECT_FALSE(Dart_IsError(Dart_GetNativeInstanceField( + handle, tonic::DartWrappable::kPeerIndex, &peer))); + return reinterpret_cast(peer); + } + + // Used to wait on Dart callbacks or Shell task runner flushing + fml::AutoResetWaitableEvent message_latch_; + + fml::AutoResetWaitableEvent picture_finalizer_latch_; + static void picture_finalizer(void* isolate_callback_data, void* peer) { + auto latch = reinterpret_cast(peer); + latch->Signal(); + } + + sk_sp current_picture_; + sk_sp current_image_; +}; + +TEST_F(ImageDisposeTest, ImageReleasedAfterFrame) { + auto native_capture_image_and_picture = [&](Dart_NativeArguments args) { + CanvasImage* image = GetNativePeer(args, 0); + Picture* picture = GetNativePeer(args, 1); + ASSERT_FALSE(image->image()->unique()); + ASSERT_FALSE(picture->picture()->unique()); + current_image_ = image->image(); + current_picture_ = picture->picture(); + + Dart_NewFinalizableHandle(Dart_GetNativeArgument(args, 1), + &picture_finalizer_latch_, 0, &picture_finalizer); + }; + + auto native_on_begin_frame_done = [&](Dart_NativeArguments args) { + message_latch_.Signal(); + }; + + Settings settings = CreateSettingsForFixture(); + auto task_runner = CreateNewThread(); + TaskRunners task_runners("test", // label + GetCurrentTaskRunner(), // platform + task_runner, // raster + task_runner, // ui + task_runner // io + ); + + AddNativeCallback("CaptureImageAndPicture", + CREATE_NATIVE_ENTRY(native_capture_image_and_picture)); + AddNativeCallback("OnBeginFrameDone", + CREATE_NATIVE_ENTRY(native_on_begin_frame_done)); + + std::unique_ptr shell = CreateShell(std::move(settings), task_runners); + + ASSERT_TRUE(shell->IsSetup()); + + SetViewportMetrics(shell.get(), 800, 600); + + shell->GetPlatformView()->NotifyCreated(); + + auto configuration = RunConfiguration::InferFromSettings(settings); + configuration.SetEntrypoint("pumpImage"); + + shell->RunEngine(std::move(configuration), [&](auto result) { + ASSERT_EQ(result, Engine::RunStatus::Success); + }); + + message_latch_.Wait(); + + ASSERT_TRUE(current_picture_); + ASSERT_TRUE(current_image_); + + // Simulate a large notify idle, as the animator would do + // when it has no frames left. + // On slower machines, this is especially important - we capture that + // this happens normally in devicelab bnechmarks like large_image_changer. + NotifyIdle(shell.get(), Dart_TimelineGetMicros() + 100000); + + picture_finalizer_latch_.Wait(); + + // Force a drain the SkiaUnrefQueue. + message_latch_.Reset(); + task_runner->PostTask([&, io_manager = shell->GetIOManager()]() { + io_manager->GetSkiaUnrefQueue()->Drain(); + message_latch_.Signal(); + }); + message_latch_.Wait(); + + EXPECT_TRUE(current_picture_->unique()); + current_picture_.reset(); + + EXPECT_TRUE(current_image_->unique()); + current_image_.reset(); + + shell->GetPlatformView()->NotifyDestroyed(); + DestroyShell(std::move(shell), std::move(task_runners)); +} + +} // namespace testing +} // namespace flutter diff --git a/lib/ui/ui_dart_state.cc b/lib/ui/ui_dart_state.cc index b43a442f849e0..eac218548cfbf 100644 --- a/lib/ui/ui_dart_state.cc +++ b/lib/ui/ui_dart_state.cc @@ -18,6 +18,7 @@ UIDartState::UIDartState( TaskObserverAdd add_callback, TaskObserverRemove remove_callback, fml::WeakPtr snapshot_delegate, + fml::WeakPtr hint_freed_delegate, fml::WeakPtr io_manager, fml::RefPtr skia_unref_queue, fml::WeakPtr image_decoder, @@ -31,6 +32,7 @@ UIDartState::UIDartState( add_callback_(std::move(add_callback)), remove_callback_(std::move(remove_callback)), snapshot_delegate_(std::move(snapshot_delegate)), + hint_freed_delegate_(std::move(hint_freed_delegate)), io_manager_(std::move(io_manager)), skia_unref_queue_(std::move(skia_unref_queue)), image_decoder_(std::move(image_decoder)), @@ -136,6 +138,10 @@ fml::WeakPtr UIDartState::GetSnapshotDelegate() const { return snapshot_delegate_; } +fml::WeakPtr UIDartState::GetHintFreedDelegate() const { + return hint_freed_delegate_; +} + fml::WeakPtr UIDartState::GetResourceContext() const { if (!io_manager_) { return {}; diff --git a/lib/ui/ui_dart_state.h b/lib/ui/ui_dart_state.h index 71755931a30d1..d5c22ffa3ff70 100644 --- a/lib/ui/ui_dart_state.h +++ b/lib/ui/ui_dart_state.h @@ -15,6 +15,7 @@ #include "flutter/fml/build_config.h" #include "flutter/fml/memory/weak_ptr.h" #include "flutter/fml/synchronization/waitable_event.h" +#include "flutter/lib/ui/hint_freed_delegate.h" #include "flutter/lib/ui/io_manager.h" #include "flutter/lib/ui/isolate_name_server/isolate_name_server.h" #include "flutter/lib/ui/painting/image_decoder.h" @@ -60,6 +61,8 @@ class UIDartState : public tonic::DartState { fml::WeakPtr GetSnapshotDelegate() const; + fml::WeakPtr GetHintFreedDelegate() const; + fml::WeakPtr GetResourceContext() const; fml::WeakPtr GetImageDecoder() const; @@ -87,6 +90,7 @@ class UIDartState : public tonic::DartState { TaskObserverAdd add_callback, TaskObserverRemove remove_callback, fml::WeakPtr snapshot_delegate, + fml::WeakPtr hint_freed_delegate, fml::WeakPtr io_manager, fml::RefPtr skia_unref_queue, fml::WeakPtr image_decoder, @@ -113,6 +117,7 @@ class UIDartState : public tonic::DartState { const TaskObserverAdd add_callback_; const TaskObserverRemove remove_callback_; fml::WeakPtr snapshot_delegate_; + fml::WeakPtr hint_freed_delegate_; fml::WeakPtr io_manager_; fml::RefPtr skia_unref_queue_; fml::WeakPtr image_decoder_; diff --git a/runtime/dart_isolate.cc b/runtime/dart_isolate.cc index 06ef693b4b46b..919a9a532c17a 100644 --- a/runtime/dart_isolate.cc +++ b/runtime/dart_isolate.cc @@ -58,6 +58,7 @@ std::weak_ptr DartIsolate::CreateRootIsolate( TaskRunners task_runners, std::unique_ptr platform_configuration, fml::WeakPtr snapshot_delegate, + fml::WeakPtr hint_freed_delegate, fml::WeakPtr io_manager, fml::RefPtr unref_queue, fml::WeakPtr image_decoder, @@ -84,15 +85,16 @@ std::weak_ptr DartIsolate::CreateRootIsolate( auto isolate_data = std::make_unique>( std::shared_ptr(new DartIsolate( - settings, // settings - task_runners, // task runners - std::move(snapshot_delegate), // snapshot delegate - std::move(io_manager), // IO manager - std::move(unref_queue), // Skia unref queue - std::move(image_decoder), // Image Decoder - advisory_script_uri, // advisory URI - advisory_script_entrypoint, // advisory entrypoint - true // is_root_isolate + settings, // settings + task_runners, // task runners + std::move(snapshot_delegate), // snapshot delegate + std::move(hint_freed_delegate), // hint freed delegate + std::move(io_manager), // IO manager + std::move(unref_queue), // Skia unref queue + std::move(image_decoder), // Image Decoder + advisory_script_uri, // advisory URI + advisory_script_entrypoint, // advisory entrypoint + true // is_root_isolate ))); DartErrorString error; @@ -120,6 +122,7 @@ std::weak_ptr DartIsolate::CreateRootIsolate( DartIsolate::DartIsolate(const Settings& settings, TaskRunners task_runners, fml::WeakPtr snapshot_delegate, + fml::WeakPtr hint_freed_delegate, fml::WeakPtr io_manager, fml::RefPtr unref_queue, fml::WeakPtr image_decoder, @@ -130,6 +133,7 @@ DartIsolate::DartIsolate(const Settings& settings, settings.task_observer_add, settings.task_observer_remove, std::move(snapshot_delegate), + std::move(hint_freed_delegate), std::move(io_manager), std::move(unref_queue), std::move(image_decoder), @@ -603,6 +607,7 @@ Dart_Isolate DartIsolate::DartCreateAndStartServiceIsolate( null_task_runners, // task runners nullptr, // platform_configuration {}, // snapshot delegate + {}, // Hint freed delegate {}, // IO Manager {}, // Skia unref queue {}, // Image Decoder @@ -706,6 +711,7 @@ Dart_Isolate DartIsolate::DartIsolateGroupCreateCallback( (*isolate_group_data)->GetSettings(), // settings null_task_runners, // task_runners fml::WeakPtr{}, // snapshot_delegate + fml::WeakPtr{}, // hint_freed_delegate fml::WeakPtr{}, // io_manager fml::RefPtr{}, // unref_queue fml::WeakPtr{}, // image_decoder @@ -749,6 +755,7 @@ bool DartIsolate::DartIsolateInitializeCallback(void** child_callback_data, (*isolate_group_data)->GetSettings(), // settings null_task_runners, // task_runners fml::WeakPtr{}, // snapshot_delegate + fml::WeakPtr{}, // hint_freed_delegate fml::WeakPtr{}, // io_manager fml::RefPtr{}, // unref_queue fml::WeakPtr{}, // image_decoder diff --git a/runtime/dart_isolate.h b/runtime/dart_isolate.h index d3862d577ba73..d1f741518ec21 100644 --- a/runtime/dart_isolate.h +++ b/runtime/dart_isolate.h @@ -13,6 +13,7 @@ #include "flutter/fml/compiler_specific.h" #include "flutter/fml/macros.h" #include "flutter/fml/mapping.h" +#include "flutter/lib/ui/hint_freed_delegate.h" #include "flutter/lib/ui/io_manager.h" #include "flutter/lib/ui/snapshot_delegate.h" #include "flutter/lib/ui/ui_dart_state.h" @@ -194,6 +195,7 @@ class DartIsolate : public UIDartState { TaskRunners task_runners, std::unique_ptr platform_configuration, fml::WeakPtr snapshot_delegate, + fml::WeakPtr hint_freed_delegate, fml::WeakPtr io_manager, fml::RefPtr skia_unref_queue, fml::WeakPtr image_decoder, @@ -404,6 +406,7 @@ class DartIsolate : public UIDartState { DartIsolate(const Settings& settings, TaskRunners task_runners, fml::WeakPtr snapshot_delegate, + fml::WeakPtr hint_freed_delegate, fml::WeakPtr io_manager, fml::RefPtr unref_queue, fml::WeakPtr image_decoder, diff --git a/runtime/dart_isolate_unittests.cc b/runtime/dart_isolate_unittests.cc index 0e9e4cf59521c..68be0efb8eeaa 100644 --- a/runtime/dart_isolate_unittests.cc +++ b/runtime/dart_isolate_unittests.cc @@ -51,6 +51,7 @@ TEST_F(DartIsolateTest, RootIsolateCreationAndShutdown) { std::move(task_runners), // task runners nullptr, // window {}, // snapshot delegate + {}, // hint freed delegate {}, // io manager {}, // unref queue {}, // image decoder @@ -85,6 +86,7 @@ TEST_F(DartIsolateTest, IsolateShutdownCallbackIsInIsolateScope) { std::move(task_runners), // task runners nullptr, // window {}, // snapshot delegate + {}, // hint freed delegate {}, // io manager {}, // unref queue {}, // image decoder diff --git a/runtime/dart_lifecycle_unittests.cc b/runtime/dart_lifecycle_unittests.cc index 94e3598038d4d..3c45c702a80fe 100644 --- a/runtime/dart_lifecycle_unittests.cc +++ b/runtime/dart_lifecycle_unittests.cc @@ -56,6 +56,7 @@ static std::shared_ptr CreateAndRunRootIsolate( runners, // task_runners {}, // window {}, // snapshot_delegate + {}, // hint_freed_delegate {}, // io_manager {}, // unref_queue {}, // image_decoder diff --git a/runtime/runtime_controller.cc b/runtime/runtime_controller.cc index f66cfed778660..7761bee4ea7ea 100644 --- a/runtime/runtime_controller.cc +++ b/runtime/runtime_controller.cc @@ -26,6 +26,7 @@ RuntimeController::RuntimeController( fml::RefPtr p_isolate_snapshot, TaskRunners p_task_runners, fml::WeakPtr p_snapshot_delegate, + fml::WeakPtr p_hint_freed_delegate, fml::WeakPtr p_io_manager, fml::RefPtr p_unref_queue, fml::WeakPtr p_image_decoder, @@ -41,6 +42,7 @@ RuntimeController::RuntimeController( isolate_snapshot_(std::move(p_isolate_snapshot)), task_runners_(p_task_runners), snapshot_delegate_(p_snapshot_delegate), + hint_freed_delegate_(p_hint_freed_delegate), io_manager_(p_io_manager), unref_queue_(p_unref_queue), image_decoder_(p_image_decoder), @@ -61,6 +63,7 @@ RuntimeController::RuntimeController( task_runners_, // std::make_unique(this), // snapshot_delegate_, // + hint_freed_delegate_, // io_manager_, // unref_queue_, // image_decoder_, // @@ -122,6 +125,7 @@ std::unique_ptr RuntimeController::Clone() const { isolate_snapshot_, // task_runners_, // snapshot_delegate_, // + hint_freed_delegate_, // io_manager_, // unref_queue_, // image_decoder_, // @@ -233,7 +237,7 @@ bool RuntimeController::ReportTimings(std::vector timings) { return false; } -bool RuntimeController::NotifyIdle(int64_t deadline) { +bool RuntimeController::NotifyIdle(int64_t deadline, size_t freed_hint) { std::shared_ptr root_isolate = root_isolate_.lock(); if (!root_isolate) { return false; @@ -241,6 +245,9 @@ bool RuntimeController::NotifyIdle(int64_t deadline) { tonic::DartState::Scope scope(root_isolate); + // Dart will use the freed hint at the next idle notification. Make sure to + // Update it with our latest value before calling NotifyIdle. + Dart_HintFreed(freed_hint); Dart_NotifyIdle(deadline); // Idle notifications being in isolate scope are part of the contract. diff --git a/runtime/runtime_controller.h b/runtime/runtime_controller.h index 18adbc2c1d720..4767d01fbcb13 100644 --- a/runtime/runtime_controller.h +++ b/runtime/runtime_controller.h @@ -67,6 +67,11 @@ class RuntimeController : public PlatformConfigurationClient { /// @param[in] snapshot_delegate The snapshot delegate used by the /// isolate to gather raster snapshots /// of Flutter view hierarchies. + /// @param[in] hint_freed_delegate The delegate used by the isolate + /// to hint the Dart VM when + /// additional memory may be freed + /// if a GC ran at the next + /// NotifyIdle. /// @param[in] io_manager The IO manager used by the isolate /// for asynchronous texture uploads. /// @param[in] unref_queue The unref queue used by the @@ -111,6 +116,7 @@ class RuntimeController : public PlatformConfigurationClient { fml::RefPtr isolate_snapshot, TaskRunners task_runners, fml::WeakPtr snapshot_delegate, + fml::WeakPtr hint_freed_delegate, fml::WeakPtr io_manager, fml::RefPtr unref_queue, fml::WeakPtr image_decoder, @@ -328,10 +334,12 @@ class RuntimeController : public PlatformConfigurationClient { /// @param[in] deadline The deadline measures in microseconds against the /// system's monotonic time. The clock can be accessed via /// `Dart_TimelineGetMicros`. + /// @param[in] freed_hint A hint of the number of bytes potentially freed + /// since the last call to NotifyIdle if a GC were run. /// /// @return If the idle notification was forwarded to the running isolate. /// - bool NotifyIdle(int64_t deadline); + bool NotifyIdle(int64_t deadline, size_t freed_hint); //---------------------------------------------------------------------------- /// @brief Returns if the root isolate is running. The isolate must be @@ -464,6 +472,7 @@ class RuntimeController : public PlatformConfigurationClient { fml::RefPtr isolate_snapshot_; TaskRunners task_runners_; fml::WeakPtr snapshot_delegate_; + fml::WeakPtr hint_freed_delegate_; fml::WeakPtr io_manager_; fml::RefPtr unref_queue_; fml::WeakPtr image_decoder_; diff --git a/shell/common/engine.cc b/shell/common/engine.cc index 315780f23346b..f7171b2fe6c1f 100644 --- a/shell/common/engine.cc +++ b/shell/common/engine.cc @@ -76,11 +76,12 @@ Engine::Engine(Delegate& delegate, io_manager, nullptr) { runtime_controller_ = std::make_unique( - *this, // runtime delegate - &vm, // VM - std::move(isolate_snapshot), // isolate snapshot - task_runners_, // task runners - std::move(snapshot_delegate), + *this, // runtime delegate + &vm, // VM + std::move(isolate_snapshot), // isolate snapshot + task_runners_, // task runners + std::move(snapshot_delegate), // snapshot delegate + GetWeakPtr(), // hint freed delegate std::move(io_manager), // io manager std::move(unref_queue), // Skia unref queue image_decoder_.GetWeakPtr(), // image decoder @@ -248,11 +249,16 @@ void Engine::ReportTimings(std::vector timings) { runtime_controller_->ReportTimings(std::move(timings)); } +void Engine::HintFreed(size_t size) { + hint_freed_bytes_since_last_idle_ += size; +} + void Engine::NotifyIdle(int64_t deadline) { auto trace_event = std::to_string(deadline - Dart_TimelineGetMicros()); TRACE_EVENT1("flutter", "Engine::NotifyIdle", "deadline_now_delta", trace_event.c_str()); - runtime_controller_->NotifyIdle(deadline); + runtime_controller_->NotifyIdle(deadline, hint_freed_bytes_since_last_idle_); + hint_freed_bytes_since_last_idle_ = 0; } std::pair Engine::GetUIIsolateReturnCode() { diff --git a/shell/common/engine.h b/shell/common/engine.h index d16ae0ffed2c7..e4de94846e62f 100644 --- a/shell/common/engine.h +++ b/shell/common/engine.h @@ -12,6 +12,7 @@ #include "flutter/common/task_runners.h" #include "flutter/fml/macros.h" #include "flutter/fml/memory/weak_ptr.h" +#include "flutter/lib/ui/hint_freed_delegate.h" #include "flutter/lib/ui/painting/image_decoder.h" #include "flutter/lib/ui/semantics/custom_accessibility_action.h" #include "flutter/lib/ui/semantics/semantics_node.h" @@ -68,7 +69,9 @@ namespace flutter { /// name and it does happen to be one of the older classes in the /// repository. /// -class Engine final : public RuntimeDelegate, PointerDataDispatcher::Delegate { +class Engine final : public RuntimeDelegate, + public HintFreedDelegate, + PointerDataDispatcher::Delegate { public: //---------------------------------------------------------------------------- /// @brief Indicates the result of the call to `Engine::Run`. @@ -465,6 +468,9 @@ class Engine final : public RuntimeDelegate, PointerDataDispatcher::Delegate { /// void BeginFrame(fml::TimePoint frame_time); + // |HintFreedDelegate| + void HintFreed(size_t size) override; + //---------------------------------------------------------------------------- /// @brief Notifies the engine that the UI task runner is not expected to /// undertake a new frame workload till a specified timepoint. The @@ -797,6 +803,7 @@ class Engine final : public RuntimeDelegate, PointerDataDispatcher::Delegate { FontCollection font_collection_; ImageDecoder image_decoder_; TaskRunners task_runners_; + size_t hint_freed_bytes_since_last_idle_ = 0; fml::WeakPtrFactory weak_factory_; // |RuntimeDelegate| diff --git a/shell/common/shell.cc b/shell/common/shell.cc index 61e190ba3f39e..4a423fa208452 100644 --- a/shell/common/shell.cc +++ b/shell/common/shell.cc @@ -608,6 +608,11 @@ fml::WeakPtr Shell::GetPlatformView() { return weak_platform_view_; } +fml::WeakPtr Shell::GetIOManager() { + FML_DCHECK(is_setup_); + return io_manager_->GetWeakPtr(); +} + DartVM* Shell::GetDartVM() { return &vm_; } diff --git a/shell/common/shell.h b/shell/common/shell.h index dd09fad2ae7a6..f7070f72cda8e 100644 --- a/shell/common/shell.h +++ b/shell/common/shell.h @@ -274,6 +274,13 @@ class Shell final : public PlatformView::Delegate, /// fml::WeakPtr GetPlatformView(); + //---------------------------------------------------------------------------- + /// @brief The IO Manager may only be accessed on the IO task runner. + /// + /// @return A weak pointer to the IO manager. + /// + fml::WeakPtr GetIOManager(); + // Embedders should call this under low memory conditions to free up // internal caches used. // diff --git a/shell/common/shell_test.cc b/shell/common/shell_test.cc index 8144d3accaacd..b43aaa0c06ca1 100644 --- a/shell/common/shell_test.cc +++ b/shell/common/shell_test.cc @@ -85,7 +85,8 @@ void ShellTest::RestartEngine(Shell* shell, RunConfiguration configuration) { void ShellTest::VSyncFlush(Shell* shell, bool& will_draw_new_frame) { fml::AutoResetWaitableEvent latch; - shell->GetTaskRunners().GetPlatformTaskRunner()->PostTask( + fml::TaskRunner::RunNowOrPostTask( + shell->GetTaskRunners().GetPlatformTaskRunner(), [shell, &will_draw_new_frame, &latch] { // The following UI task ensures that all previous UI tasks are flushed. fml::AutoResetWaitableEvent ui_latch; @@ -106,6 +107,54 @@ void ShellTest::VSyncFlush(Shell* shell, bool& will_draw_new_frame) { latch.Wait(); } +void ShellTest::SetViewportMetrics(Shell* shell, double width, double height) { + flutter::ViewportMetrics viewport_metrics = { + 1, // device pixel ratio + width, // physical width + height, // physical height + 0, // padding top + 0, // padding right + 0, // padding bottom + 0, // padding left + 0, // view inset top + 0, // view inset right + 0, // view inset bottom + 0, // view inset left + 0, // gesture inset top + 0, // gesture inset right + 0, // gesture inset bottom + 0 // gesture inset left + }; + // Set viewport to nonempty, and call Animator::BeginFrame to make the layer + // tree pipeline nonempty. Without either of this, the layer tree below + // won't be rasterized. + fml::AutoResetWaitableEvent latch; + shell->GetTaskRunners().GetUITaskRunner()->PostTask( + [&latch, engine = shell->weak_engine_, viewport_metrics]() { + if (engine) { + engine->SetViewportMetrics(std::move(viewport_metrics)); + const auto frame_begin_time = fml::TimePoint::Now(); + const auto frame_end_time = + frame_begin_time + fml::TimeDelta::FromSecondsF(1.0 / 60.0); + engine->animator_->BeginFrame(frame_begin_time, frame_end_time); + } + latch.Signal(); + }); + latch.Wait(); +} + +void ShellTest::NotifyIdle(Shell* shell, int64_t deadline) { + fml::AutoResetWaitableEvent latch; + shell->GetTaskRunners().GetUITaskRunner()->PostTask( + [&latch, engine = shell->weak_engine_, deadline]() { + if (engine) { + engine->NotifyIdle(deadline); + } + latch.Signal(); + }); + latch.Wait(); +} + void ShellTest::PumpOneFrame(Shell* shell, double width, double height, diff --git a/shell/common/shell_test.h b/shell/common/shell_test.h index c61aba37c7647..5c8fd9d9b574a 100644 --- a/shell/common/shell_test.h +++ b/shell/common/shell_test.h @@ -63,6 +63,10 @@ class ShellTest : public FixtureTest { /// in PumpOneFrame. using LayerTreeBuilder = std::function root)>; + + static void SetViewportMetrics(Shell* shell, double width, double height); + static void NotifyIdle(Shell* shell, int64_t deadline); + static void PumpOneFrame(Shell* shell, double width = 1, double height = 1, diff --git a/testing/dart_isolate_runner.cc b/testing/dart_isolate_runner.cc index 8fe1d1f5ee09b..1515f2e5afb15 100644 --- a/testing/dart_isolate_runner.cc +++ b/testing/dart_isolate_runner.cc @@ -73,6 +73,7 @@ void RunDartCodeInIsolate(DartVMRef& vm_ref, std::move(task_runners), // task runners nullptr, // window {}, // snapshot delegate + {}, // hint freed delegate io_manager, // io manager {}, // unref queue {}, // image decoder diff --git a/testing/run_tests.py b/testing/run_tests.py index 3330cb985db15..a11f91c47b1e8 100755 --- a/testing/run_tests.py +++ b/testing/run_tests.py @@ -139,7 +139,8 @@ def RunCCTests(build_dir, filter): RunEngineExecutable(build_dir, 'jni_unittests', filter, shuffle_flags) RunEngineExecutable(build_dir, 'platform_view_android_delegate_unittests', filter, shuffle_flags) - RunEngineExecutable(build_dir, 'ui_unittests', filter, shuffle_flags) + # The image release unit test can take a while on slow machines. + RunEngineExecutable(build_dir, 'ui_unittests', filter, shuffle_flags + ['--timeout=90']) RunEngineExecutable(build_dir, 'testing_unittests', filter, shuffle_flags)