From 51706cd06f1dec41f62ce0b6a98e3afcd8af644b Mon Sep 17 00:00:00 2001 From: Chris Bracken Date: Tue, 23 Jul 2024 16:55:04 -0700 Subject: [PATCH] [iOS] Flush layer pool after platform view dispose (flutter/engine#54056) `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 layers are no longer needed. We now flush the layer pool on `SubmitFrame` if a previously-used layer is no longer used in the current frame. In theory, this could cause a performance regression in the case where an additional layer is needed every second or third frame, but we should keep it simple on the first pass and only complicate things later if warranted. Fixes: https://github.com/flutter/flutter/issues/152053 [C++, Objective-C, Java style guides]: https://github.com/flutter/engine/blob/main/CONTRIBUTING.md#style --- .../framework/Source/FlutterPlatformViews.mm | 15 ++++++++--- .../Source/FlutterPlatformViewsTest.mm | 27 +++++++++++++++++++ .../Source/FlutterPlatformViews_Internal.h | 10 ++++--- 3 files changed, 45 insertions(+), 7 deletions(-) diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm index c3c76ec82d..6290299ca6 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm @@ -152,14 +152,19 @@ void FlutterPlatformViewLayerPool::RecycleLayers() { } 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; } +size_t FlutterPlatformViewLayerPool::size() const { + return layers_.size(); +} + void FlutterPlatformViewsController::SetFlutterView(UIView* flutter_view) { flutter_view_.reset(flutter_view); } @@ -402,6 +407,10 @@ size_t FlutterPlatformViewsController::EmbeddedViewCount() const { 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]; } @@ -873,8 +882,7 @@ std::shared_ptr FlutterPlatformViewsController::GetLay } 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 +923,6 @@ void FlutterPlatformViewsController::DisposeViews() { clip_count_.erase(viewId); views_to_recomposite_.erase(viewId); } - views_to_dispose_ = std::move(views_to_delay_dispose); } diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatformViewsTest.mm b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatformViewsTest.mm index 8a65d7c1cf..a14dcf8007 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatformViewsTest.mm +++ b/engine/src/flutter/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 @@ fml::RefPtr CreateNewThread(const std::string& name) { 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 diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h index afda710e7f..e1f8f3e071 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h @@ -176,13 +176,15 @@ 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(); + // 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: @@ -236,6 +238,8 @@ class FlutterPlatformViewsController { size_t EmbeddedViewCount() const; + 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