[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
This commit is contained in:
Chris Bracken
2024-07-23 16:55:04 -07:00
committed by GitHub
parent 16455a2543
commit 51706cd06f
3 changed files with 45 additions and 7 deletions

View File

@@ -152,14 +152,19 @@ void FlutterPlatformViewLayerPool::RecycleLayers() {
}
std::vector<std::shared_ptr<FlutterPlatformViewLayer>>
FlutterPlatformViewLayerPool::GetUnusedLayers() {
FlutterPlatformViewLayerPool::RemoveUnusedLayers() {
std::vector<std::shared_ptr<FlutterPlatformViewLayer>> 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<FlutterPlatformViewLayer> FlutterPlatformViewsController::GetLay
}
void FlutterPlatformViewsController::RemoveUnusedLayers() {
std::vector<std::shared_ptr<FlutterPlatformViewLayer>> layers = layer_pool_->GetUnusedLayers();
for (const std::shared_ptr<FlutterPlatformViewLayer>& 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);
}

View File

@@ -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<fml::TaskRunner> 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

View File

@@ -176,13 +176,15 @@ class FlutterPlatformViewLayerPool {
const std::shared_ptr<IOSContext>& 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<std::shared_ptr<FlutterPlatformViewLayer>> GetUnusedLayers();
// Removes unused layers from the pool. Returns the unused layers.
std::vector<std::shared_ptr<FlutterPlatformViewLayer>> 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