Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit 17d5100

Browse files
author
Chris Yang
authored
[ios_platform_view] only recycle maskView when the view is applying mutators (#41573)
A mistake was introduced in #39498 where the maskViews are already recycles each frame. Sometimes a PlatformView does not need to be re-composite: (https://github.com/flutter/engine/blob/main/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm#L398-L401), so the mask view for such PlatformView should not be recycled. This PR changed the `recycleMaskViews` API to allow individual maskviews to be recycled. `ApplyMutator` then only recycle the maskView for that particular PlatformView. The MaskViewPool is also reworked to be simpler. - The pool now contains a single set of mask views, there is no index counter needed. - When a maskView is needed, try to get it from the pool. - If pool is empty, create a new view. - If pool has an available maskview, remove it from the pool. - When a PlatformView starts to `applyMutator`, it removes current the maskView, insert the maskView to the pool. - When the above PlatformView needs to a maskView, it grabs one from the pool. fixes: flutter/flutter#125620 [C++, Objective-C, Java style guides]: https://github.com/flutter/engine/blob/main/CONTRIBUTING.md#style
1 parent fe24767 commit 17d5100

13 files changed

+387
-65
lines changed

shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,6 @@
1818
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterViewController_Internal.h"
1919
#import "flutter/shell/platform/darwin/ios/ios_surface.h"
2020

21-
static const NSUInteger kFlutterClippingMaskViewPoolCapacity = 5;
22-
2321
@implementation UIView (FirstResponder)
2422
- (BOOL)flt_hasFirstResponderInViewHierarchySubtree {
2523
if (self.isFirstResponder) {
@@ -447,6 +445,8 @@ static bool ClipRRectContainsPlatformViewBoundingRect(const SkRRect& clip_rrect,
447445
clipView.maskView = [mask_view_pool_.get() getMaskViewWithFrame:frame];
448446
}
449447

448+
// This method is only called when the `embedded_view` needs to be re-composited at the current
449+
// frame. See: `CompositeWithParams` for details.
450450
void FlutterPlatformViewsController::ApplyMutators(const MutatorsStack& mutators_stack,
451451
UIView* embedded_view,
452452
const SkRect& bounding_rect) {
@@ -461,12 +461,10 @@ static bool ClipRRectContainsPlatformViewBoundingRect(const SkRRect& clip_rrect,
461461
NSMutableArray* blurFilters = [[[NSMutableArray alloc] init] autorelease];
462462
FML_DCHECK(!clipView.maskView ||
463463
[clipView.maskView isKindOfClass:[FlutterClippingMaskView class]]);
464-
if (mask_view_pool_.get() == nil) {
465-
mask_view_pool_.reset([[FlutterClippingMaskViewPool alloc]
466-
initWithCapacity:kFlutterClippingMaskViewPoolCapacity]);
464+
if (clipView.maskView) {
465+
[mask_view_pool_.get() insertViewToPoolIfNeeded:(FlutterClippingMaskView*)(clipView.maskView)];
466+
clipView.maskView = nil;
467467
}
468-
[mask_view_pool_.get() recycleMaskViews];
469-
clipView.maskView = nil;
470468
CGFloat screenScale = [UIScreen mainScreen].scale;
471469
auto iter = mutators_stack.Begin();
472470
while (iter != mutators_stack.End()) {
@@ -570,6 +568,14 @@ static bool ClipRRectContainsPlatformViewBoundingRect(const SkRRect& clip_rrect,
570568
embedded_view.layer.transform = flutter::GetCATransform3DFromSkMatrix(transformMatrix);
571569
}
572570

571+
// Composite the PlatformView with `view_id`.
572+
//
573+
// Every frame, during the paint traversal of the layer tree, this method is called for all
574+
// the PlatformViews in `views_to_recomposite_`.
575+
//
576+
// Note that `views_to_recomposite_` does not represent all the views in the view hierarchy,
577+
// if a PlatformView does not change its composition parameter from last frame, it is not
578+
// included in the `views_to_recomposite_`.
573579
void FlutterPlatformViewsController::CompositeWithParams(int64_t view_id,
574580
const EmbeddedViewParams& params) {
575581
CGRect frame = CGRectMake(0, 0, params.sizePoints().width(), params.sizePoints().height());

shell/platform/darwin/ios/framework/Source/FlutterPlatformViewsTest.mm

Lines changed: 77 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2649,7 +2649,8 @@ - (void)testFlutterClippingMaskViewPoolReuseViewsAfterRecycle {
26492649
[[[FlutterClippingMaskViewPool alloc] initWithCapacity:2] autorelease];
26502650
FlutterClippingMaskView* view1 = [pool getMaskViewWithFrame:CGRectZero];
26512651
FlutterClippingMaskView* view2 = [pool getMaskViewWithFrame:CGRectZero];
2652-
[pool recycleMaskViews];
2652+
[pool insertViewToPoolIfNeeded:view1];
2653+
[pool insertViewToPoolIfNeeded:view2];
26532654
CGRect newRect = CGRectMake(0, 0, 10, 10);
26542655
FlutterClippingMaskView* view3 = [pool getMaskViewWithFrame:newRect];
26552656
FlutterClippingMaskView* view4 = [pool getMaskViewWithFrame:newRect];
@@ -2727,17 +2728,17 @@ - (void)testClipMaskViewIsReused {
27272728
auto embeddedViewParams1 = std::make_unique<flutter::EmbeddedViewParams>(
27282729
screenScaleMatrix, SkSize::Make(10, 10), stack1);
27292730

2730-
flutter::MutatorsStack stack2;
2731-
auto embeddedViewParams2 = std::make_unique<flutter::EmbeddedViewParams>(
2732-
screenScaleMatrix, SkSize::Make(10, 10), stack2);
2733-
27342731
flutterPlatformViewsController->PrerollCompositeEmbeddedView(1, std::move(embeddedViewParams1));
27352732
flutterPlatformViewsController->CompositeEmbeddedView(1);
27362733
UIView* childClippingView1 = gMockPlatformView.superview.superview;
27372734
UIView* maskView1 = childClippingView1.maskView;
27382735
XCTAssertNotNil(maskView1);
27392736

27402737
// Composite a new frame.
2738+
flutterPlatformViewsController->BeginFrame(SkISize::Make(100, 100));
2739+
flutter::MutatorsStack stack2;
2740+
auto embeddedViewParams2 = std::make_unique<flutter::EmbeddedViewParams>(
2741+
screenScaleMatrix, SkSize::Make(10, 10), stack2);
27412742
auto embeddedViewParams3 = std::make_unique<flutter::EmbeddedViewParams>(
27422743
screenScaleMatrix, SkSize::Make(10, 10), stack2);
27432744
flutterPlatformViewsController->PrerollCompositeEmbeddedView(1, std::move(embeddedViewParams3));
@@ -2763,6 +2764,77 @@ - (void)testClipMaskViewIsReused {
27632764
XCTAssertNil(childClippingView1.maskView);
27642765
}
27652766

2767+
- (void)testDifferentClipMaskViewIsUsedForEachView {
2768+
flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
2769+
auto thread_task_runner = CreateNewThread("FlutterPlatformViewsTest");
2770+
flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
2771+
/*platform=*/thread_task_runner,
2772+
/*raster=*/thread_task_runner,
2773+
/*ui=*/thread_task_runner,
2774+
/*io=*/thread_task_runner);
2775+
auto flutterPlatformViewsController = std::make_shared<flutter::FlutterPlatformViewsController>();
2776+
auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
2777+
/*delegate=*/mock_delegate,
2778+
/*rendering_api=*/flutter::IOSRenderingAPI::kSoftware,
2779+
/*platform_views_controller=*/flutterPlatformViewsController,
2780+
/*task_runners=*/runners);
2781+
2782+
FlutterPlatformViewsTestMockFlutterPlatformFactory* factory =
2783+
[[FlutterPlatformViewsTestMockFlutterPlatformFactory new] autorelease];
2784+
flutterPlatformViewsController->RegisterViewFactory(
2785+
factory, @"MockFlutterPlatformView",
2786+
FlutterPlatformViewGestureRecognizersBlockingPolicyEager);
2787+
FlutterResult result = ^(id result) {
2788+
};
2789+
2790+
flutterPlatformViewsController->OnMethodCall(
2791+
[FlutterMethodCall
2792+
methodCallWithMethodName:@"create"
2793+
arguments:@{@"id" : @1, @"viewType" : @"MockFlutterPlatformView"}],
2794+
result);
2795+
UIView* view1 = gMockPlatformView;
2796+
2797+
// This overwrites `gMockPlatformView` to another view.
2798+
flutterPlatformViewsController->OnMethodCall(
2799+
[FlutterMethodCall
2800+
methodCallWithMethodName:@"create"
2801+
arguments:@{@"id" : @2, @"viewType" : @"MockFlutterPlatformView"}],
2802+
result);
2803+
UIView* view2 = gMockPlatformView;
2804+
2805+
XCTAssertNotNil(gMockPlatformView);
2806+
UIView* mockFlutterView = [[[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 10)] autorelease];
2807+
flutterPlatformViewsController->SetFlutterView(mockFlutterView);
2808+
// Create embedded view params
2809+
flutter::MutatorsStack stack1;
2810+
// Layer tree always pushes a screen scale factor to the stack
2811+
SkMatrix screenScaleMatrix =
2812+
SkMatrix::Scale([UIScreen mainScreen].scale, [UIScreen mainScreen].scale);
2813+
stack1.PushTransform(screenScaleMatrix);
2814+
// Push a clip rect
2815+
SkRect rect = SkRect::MakeXYWH(2, 2, 3, 3);
2816+
stack1.PushClipRect(rect);
2817+
2818+
auto embeddedViewParams1 = std::make_unique<flutter::EmbeddedViewParams>(
2819+
screenScaleMatrix, SkSize::Make(10, 10), stack1);
2820+
2821+
flutter::MutatorsStack stack2;
2822+
stack2.PushClipRect(rect);
2823+
auto embeddedViewParams2 = std::make_unique<flutter::EmbeddedViewParams>(
2824+
screenScaleMatrix, SkSize::Make(10, 10), stack2);
2825+
2826+
flutterPlatformViewsController->PrerollCompositeEmbeddedView(1, std::move(embeddedViewParams1));
2827+
flutterPlatformViewsController->CompositeEmbeddedView(1);
2828+
UIView* childClippingView1 = view1.superview.superview;
2829+
2830+
flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams2));
2831+
flutterPlatformViewsController->CompositeEmbeddedView(2);
2832+
UIView* childClippingView2 = view2.superview.superview;
2833+
UIView* maskView1 = childClippingView1.maskView;
2834+
UIView* maskView2 = childClippingView2.maskView;
2835+
XCTAssertNotEqual(maskView1, maskView2);
2836+
}
2837+
27662838
// Return true if a correct visual effect view is found. It also implies all the validation in this
27672839
// method passes.
27682840
//

shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h

Lines changed: 7 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@
5656
// in the pool. If there are none available, a new FlutterClippingMaskView is constructed. If the
5757
// capacity is reached, the newly constructed FlutterClippingMaskView is not added to the pool.
5858
//
59-
// Call |recycleMaskViews| to mark all the FlutterClippingMaskViews in the pool available.
59+
// Call |insertViewToPoolIfNeeded:| to return a maskView to the pool.
6060
@interface FlutterClippingMaskViewPool : NSObject
6161

6262
// Initialize the pool with `capacity`. When the `capacity` is reached, a FlutterClippingMaskView is
@@ -66,8 +66,8 @@
6666
// Reuse a maskView from the pool, or allocate a new one.
6767
- (FlutterClippingMaskView*)getMaskViewWithFrame:(CGRect)frame;
6868

69-
// Mark all the maskViews available.
70-
- (void)recycleMaskViews;
69+
// Insert the `maskView` into the pool.
70+
- (void)insertViewToPoolIfNeeded:(FlutterClippingMaskView*)maskView;
7171

7272
@end
7373

@@ -291,27 +291,20 @@ class FlutterPlatformViewsController {
291291
int CountClips(const MutatorsStack& mutators_stack);
292292

293293
void ClipViewSetMaskView(UIView* clipView);
294+
294295
// Applies the mutators in the mutators_stack to the UIView chain that was constructed by
295296
// `ReconstructClipViewsChain`
296297
//
297-
// Clips are applied to the super view with a CALayer mask. Transforms are applied to the
298-
// current view that's at the head of the chain. For example the following mutators stack [T_1,
299-
// C_2, T_3, T_4, C_5, T_6] where T denotes a transform and C denotes a clip, will result in the
300-
// following UIView tree:
301-
//
302-
// C_2 -> C_5 -> PLATFORM_VIEW
303-
// (PLATFORM_VIEW is a subview of C_5 which is a subview of C_2)
304-
//
305-
// T_1 is applied to C_2, T_3 and T_4 are applied to C_5, and T_6 is applied to PLATFORM_VIEW.
306-
//
307-
// After each clip operation, we update the head to the super view of the current head.
298+
// Clips are applied to the `embedded_view`'s super view(|ChildClippingView|) using a
299+
// |FlutterClippingMaskView|. Transforms are applied to `embedded_view`
308300
//
309301
// The `bounding_rect` is the final bounding rect of the PlatformView
310302
// (EmbeddedViewParams::finalBoundingRect). If a clip mutator's rect contains the final bounding
311303
// rect of the PlatformView, the clip mutator is not applied for performance optimization.
312304
void ApplyMutators(const MutatorsStack& mutators_stack,
313305
UIView* embedded_view,
314306
const SkRect& bounding_rect);
307+
315308
void CompositeWithParams(int64_t view_id, const EmbeddedViewParams& params);
316309

317310
// Allocates a new FlutterPlatformViewLayer if needed, draws the pixels within the rect from

shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.mm

Lines changed: 26 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#import "flutter/shell/platform/darwin/ios/ios_surface.h"
1010

1111
static int kMaxPointsInVerb = 4;
12+
static const NSUInteger kFlutterClippingMaskViewPoolCapacity = 5;
1213

1314
namespace flutter {
1415

@@ -26,7 +27,10 @@
2627

2728
FlutterPlatformViewsController::FlutterPlatformViewsController()
2829
: layer_pool_(std::make_unique<FlutterPlatformViewLayerPool>()),
29-
weak_factory_(std::make_unique<fml::WeakPtrFactory<FlutterPlatformViewsController>>(this)){};
30+
weak_factory_(std::make_unique<fml::WeakPtrFactory<FlutterPlatformViewsController>>(this)) {
31+
mask_view_pool_.reset(
32+
[[FlutterClippingMaskViewPool alloc] initWithCapacity:kFlutterClippingMaskViewPoolCapacity]);
33+
};
3034

3135
FlutterPlatformViewsController::~FlutterPlatformViewsController() = default;
3236

@@ -458,58 +462,53 @@ @interface FlutterClippingMaskViewPool ()
458462
// The maximum number of `FlutterClippingMaskView` the pool can contain.
459463
// This prevents the pool to grow infinately and limits the maximum memory a pool can use.
460464
@property(assign, nonatomic) NSUInteger capacity;
461-
@property(retain, nonatomic) NSMutableArray<FlutterClippingMaskView*>* pool;
462-
// The index points to the first available FlutterClippingMaskView in the `pool`.
463-
@property(assign, nonatomic) NSUInteger availableIndex;
465+
466+
// The pool contains the views that are available to use.
467+
// The number of items in the pool must not excceds `capacity`.
468+
@property(retain, nonatomic) NSMutableSet<FlutterClippingMaskView*>* pool;
464469

465470
@end
466471

467472
@implementation FlutterClippingMaskViewPool : NSObject
468473

469474
- (instancetype)initWithCapacity:(NSInteger)capacity {
470475
if (self = [super init]) {
471-
_pool = [[NSMutableArray alloc] initWithCapacity:capacity];
476+
// Most of cases, there are only one PlatformView in the scene.
477+
// Thus init with the capacity of 1.
478+
_pool = [[NSMutableSet alloc] initWithCapacity:1];
472479
_capacity = capacity;
473-
_availableIndex = 0;
474480
}
475481
return self;
476482
}
477483

478484
- (FlutterClippingMaskView*)getMaskViewWithFrame:(CGRect)frame {
479-
FML_DCHECK(self.availableIndex <= self.capacity);
485+
FML_DCHECK(self.pool.count <= self.capacity);
480486
FlutterClippingMaskView* maskView;
481-
if (self.availableIndex == self.capacity) {
482-
// The pool is full, alloc a new one.
487+
if (self.pool.count == 0) {
488+
// The pool is empty, alloc a new one.
483489
maskView =
484490
[[[FlutterClippingMaskView alloc] initWithFrame:frame
485491
screenScale:[UIScreen mainScreen].scale] autorelease];
486492
return maskView;
487493
}
488-
489-
if (self.availableIndex >= self.pool.count) {
490-
// The pool doesn't have enough maskViews, alloc a new one and add to the pool.
491-
maskView =
492-
[[[FlutterClippingMaskView alloc] initWithFrame:frame
493-
screenScale:[UIScreen mainScreen].scale] autorelease];
494-
[self.pool addObject:maskView];
495-
FML_DCHECK(self.pool.count <= self.capacity);
496-
} else {
497-
// Reuse a maskView from the pool.
498-
maskView = [self.pool objectAtIndex:self.availableIndex];
499-
maskView.frame = frame;
500-
[maskView reset];
501-
}
502-
self.availableIndex++;
494+
maskView = [self.pool anyObject];
495+
maskView.frame = frame;
496+
[maskView reset];
497+
[self.pool removeObject:maskView];
503498
return maskView;
504499
}
505500

506-
- (void)recycleMaskViews {
507-
self.availableIndex = 0;
501+
- (void)insertViewToPoolIfNeeded:(FlutterClippingMaskView*)maskView {
502+
FML_DCHECK(![self.pool containsObject:maskView]);
503+
FML_DCHECK(self.pool.count <= self.capacity);
504+
if (self.pool.count == self.capacity) {
505+
return;
506+
}
507+
[self.pool addObject:maskView];
508508
}
509509

510510
- (void)dealloc {
511511
[_pool release];
512-
_pool = nil;
513512

514513
[super dealloc];
515514
}

0 commit comments

Comments
 (0)