From fe05e811c51b3054fb72f8e7539ce11e25d56eab Mon Sep 17 00:00:00 2001 From: Chris Bracken Date: Tue, 23 Jul 2024 11:53:58 -0700 Subject: [PATCH 1/3] [iOS] Flush layer pool after platform view dispose `FlutterPlatformViewLayerPool` is a pool of iOS layers used for rendering platform views on iOS. When layers are no longer needed, we currently mark them available for re-use but we never actually flush them and thus recover the memory associated with these layers once we know that the platformview is no longer used. We now flush the layer pool to recover unused memory after platform views have been disposed. See: https://github.com/flutter/flutter/issues/152053 --- .../darwin/ios/framework/Source/FlutterPlatformViews.mm | 7 +++---- .../ios/framework/Source/FlutterPlatformViews_Internal.h | 5 ++--- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm index 551be716b15de..62104467ff9eb 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm @@ -152,11 +152,12 @@ static bool ClipRRectContainsPlatformViewBoundingRect(const SkRRect& clip_rrect, } std::vector> -FlutterPlatformViewLayerPool::GetUnusedLayers() { +FlutterPlatformViewLayerPool::RemoveUnusedLayers() { std::vector> results; for (size_t i = available_layer_index_; i < layers_.size(); i++) { results.push_back(layers_[i]); } + layers_.erase(layers_.begin() + available_layer_index_, layers_.end()); return results; } @@ -873,8 +874,7 @@ static bool ClipRRectContainsPlatformViewBoundingRect(const SkRRect& clip_rrect, } void FlutterPlatformViewsController::RemoveUnusedLayers() { - std::vector> layers = layer_pool_->GetUnusedLayers(); - for (const std::shared_ptr& layer : layers) { + for (const auto& layer : layer_pool_->RemoveUnusedLayers()) { [layer->overlay_view_wrapper removeFromSuperview]; } @@ -915,7 +915,6 @@ static bool ClipRRectContainsPlatformViewBoundingRect(const SkRRect& clip_rrect, clip_count_.erase(viewId); views_to_recomposite_.erase(viewId); } - views_to_dispose_ = std::move(views_to_delay_dispose); } diff --git a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h index 6373e1feaa40c..b4b3d29bce520 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h +++ b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h @@ -176,9 +176,8 @@ class FlutterPlatformViewLayerPool { const std::shared_ptr& ios_context, MTLPixelFormat pixel_format); - // Gets the layers in the pool that aren't currently used. - // This method doesn't mark the layers as unused. - std::vector> GetUnusedLayers(); + // Removes unused layers from the pool. Returns the unused layers. + std::vector> RemoveUnusedLayers(); // Marks the layers in the pool as available for reuse. void RecycleLayers(); From 172d10085cbbb5288378b3dd8e205a48de1775ce Mon Sep 17 00:00:00 2001 From: Chris Bracken Date: Tue, 23 Jul 2024 13:19:44 -0700 Subject: [PATCH 2/3] Add LayerPoolSize --- .../darwin/ios/framework/Source/FlutterPlatformViews.mm | 8 ++++++++ .../ios/framework/Source/FlutterPlatformViews_Internal.h | 5 +++++ 2 files changed, 13 insertions(+) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm index 62104467ff9eb..4f9eca317927a 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm @@ -161,6 +161,10 @@ static bool ClipRRectContainsPlatformViewBoundingRect(const SkRRect& clip_rrect, return results; } +size_t FlutterPlatformViewLayerPool::size() const { + return layers_.size(); +} + void FlutterPlatformViewsController::SetFlutterView(UIView* flutter_view) { flutter_view_.reset(flutter_view); } @@ -403,6 +407,10 @@ static bool ClipRRectContainsPlatformViewBoundingRect(const SkRRect& clip_rrect, return composition_order_.size(); } +size_t FlutterPlatformViewsController::LayerPoolSize() const { + return layer_pool_->size(); +} + UIView* FlutterPlatformViewsController::GetPlatformViewByID(int64_t view_id) { return [GetFlutterTouchInterceptingViewByID(view_id) embeddedView]; } diff --git a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h index b4b3d29bce520..256d43e8897e8 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h +++ b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h @@ -182,6 +182,9 @@ class FlutterPlatformViewLayerPool { // Marks the layers in the pool as available for reuse. void RecycleLayers(); + // Returns the count of layers currently in the pool. + size_t size() const; + private: // The index of the entry in the layers_ vector that determines the beginning of the unused // layers. For example, consider the following vector: @@ -235,6 +238,8 @@ class FlutterPlatformViewsController { size_t EmbeddedViewCount(); + size_t LayerPoolSize() const; + // Returns the `FlutterPlatformView`'s `view` object associated with the view_id. // // If the `FlutterPlatformViewsController` does not contain any `FlutterPlatformView` object or From b5ab12e040bd90685a1587ee73fc15ba0631998a Mon Sep 17 00:00:00 2001 From: Chris Bracken Date: Tue, 23 Jul 2024 15:58:52 -0700 Subject: [PATCH 3/3] Add test --- .../Source/FlutterPlatformViewsTest.mm | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViewsTest.mm b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViewsTest.mm index 8a65d7c1cff16..a14dcf8007844 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViewsTest.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViewsTest.mm @@ -10,6 +10,7 @@ #import "flutter/shell/platform/darwin/common/framework/Headers/FlutterMacros.h" #import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterPlatformViews.h" #import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterViewController.h" +#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterEngine_Internal.h" #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h" #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterTouchInterceptingView_Test.h" #import "flutter/shell/platform/darwin/ios/platform_view_ios.h" @@ -3314,4 +3315,30 @@ - (void)testFlutterTouchInterceptingViewLinksToAccessibilityContainer { XCTAssertEqualObjects([touchInteceptorView accessibilityContainer], container); } +- (void)testLayerPool { + // Create an IOSContext and GrDirectContext. + FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"foobar"]; + [engine run]; + XCTAssertTrue([engine iosPlatformView] != nullptr); + auto ios_context = [engine iosPlatformView]->GetIosContext(); + auto gr_context = ios_context->GetMainContext(); + + auto pool = flutter::FlutterPlatformViewLayerPool{}; + + // Add layers to the pool. + auto layer1 = pool.GetLayer(gr_context.get(), ios_context, MTLPixelFormatBGRA8Unorm); + XCTAssertEqual(pool.size(), 1u); + auto layer2 = pool.GetLayer(gr_context.get(), ios_context, MTLPixelFormatBGRA8Unorm); + XCTAssertEqual(pool.size(), 2u); + + // Mark all layers as unused. + pool.RecycleLayers(); + XCTAssertEqual(pool.size(), 2u); + + // Free the unused layers. + auto unused_layers = pool.RemoveUnusedLayers(); + XCTAssertEqual(unused_layers.size(), 2u); + XCTAssertEqual(pool.size(), 0u); +} + @end