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

Commit ef09d5b

Browse files
author
Chris Yang
committed
reuse maskView
reuse mask view format format draft draft fixes test
1 parent 2a11023 commit ef09d5b

File tree

4 files changed

+224
-18
lines changed

4 files changed

+224
-18
lines changed

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

Lines changed: 37 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
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+
2123
@implementation UIView (FirstResponder)
2224
- (BOOL)flt_hasFirstResponderInViewHierarchySubtree {
2325
if (self.isFirstResponder) {
@@ -451,6 +453,17 @@ static bool ClipRRectContainsPlatformViewBoundingRect(const SkRRect& clip_rrect,
451453
return clipCount;
452454
}
453455

456+
void FlutterPlatformViewsController::ClipViewAddMaskView(UIView* clipView) {
457+
if (clipView.maskView) {
458+
return;
459+
}
460+
UIView* flutterView = flutter_view_.get();
461+
CGRect frame =
462+
CGRectMake(-clipView.frame.origin.x, -clipView.frame.origin.y,
463+
CGRectGetWidth(flutterView.bounds), CGRectGetHeight(flutterView.bounds));
464+
clipView.maskView = [mask_view_pool_.get() getMaskViewWithFrame:frame];
465+
}
466+
454467
void FlutterPlatformViewsController::ApplyMutators(const MutatorsStack& mutators_stack,
455468
UIView* embedded_view,
456469
const SkRect& bounding_rect) {
@@ -461,18 +474,15 @@ static bool ClipRRectContainsPlatformViewBoundingRect(const SkRRect& clip_rrect,
461474
ResetAnchor(embedded_view.layer);
462475
ChildClippingView* clipView = (ChildClippingView*)embedded_view.superview;
463476

464-
CGFloat screenScale = [UIScreen mainScreen].scale;
465-
466-
UIView* flutter_view = flutter_view_.get();
467-
FlutterClippingMaskView* maskView = [[[FlutterClippingMaskView alloc]
468-
initWithFrame:CGRectMake(-clipView.frame.origin.x, -clipView.frame.origin.y,
469-
CGRectGetWidth(flutter_view.bounds),
470-
CGRectGetHeight(flutter_view.bounds))
471-
screenScale:screenScale] autorelease];
472-
473477
SkMatrix transformMatrix;
474478
NSMutableArray* blurFilters = [[[NSMutableArray alloc] init] autorelease];
475-
479+
FML_DCHECK(!clipView.maskView ||
480+
[clipView.maskView isKindOfClass:[FlutterClippingMaskView class]]);
481+
if (mask_view_pool_.get() == nil) {
482+
mask_view_pool_.reset([[FlutterClippingMaskViewPool alloc]
483+
initWithCapacity:kFlutterClippingMaskViewPoolCapacity]);
484+
}
485+
[mask_view_pool_.get() recycleMaskViews];
476486
clipView.maskView = nil;
477487
auto iter = mutators_stack.Begin();
478488
while (iter != mutators_stack.End()) {
@@ -486,25 +496,28 @@ static bool ClipRRectContainsPlatformViewBoundingRect(const SkRRect& clip_rrect,
486496
transformMatrix)) {
487497
break;
488498
}
489-
[maskView clipRect:(*iter)->GetRect() matrix:transformMatrix];
490-
clipView.maskView = maskView;
499+
ClipViewAddMaskView(clipView);
500+
[(FlutterClippingMaskView*)clipView.maskView clipRect:(*iter)->GetRect()
501+
matrix:transformMatrix];
491502
break;
492503
}
493504
case kClipRRect: {
494505
if (ClipRRectContainsPlatformViewBoundingRect((*iter)->GetRRect(), bounding_rect,
495506
transformMatrix)) {
496507
break;
497508
}
498-
[maskView clipRRect:(*iter)->GetRRect() matrix:transformMatrix];
499-
clipView.maskView = maskView;
509+
ClipViewAddMaskView(clipView);
510+
[(FlutterClippingMaskView*)clipView.maskView clipRRect:(*iter)->GetRRect()
511+
matrix:transformMatrix];
500512
break;
501513
}
502514
case kClipPath: {
503515
// TODO(cyanglaz): Find a way to pre-determine if path contains the PlatformView boudning
504516
// rect. See `ClipRRectContainsPlatformViewBoundingRect`.
505517
// https://github.com/flutter/flutter/issues/118650
506-
[maskView clipPath:(*iter)->GetPath() matrix:transformMatrix];
507-
clipView.maskView = maskView;
518+
ClipViewAddMaskView(clipView);
519+
[(FlutterClippingMaskView*)clipView.maskView clipPath:(*iter)->GetPath()
520+
matrix:transformMatrix];
508521
break;
509522
}
510523
case kOpacity:
@@ -551,10 +564,13 @@ static bool ClipRRectContainsPlatformViewBoundingRect(const SkRRect& clip_rrect,
551564
[clipView applyBlurBackdropFilters:blurFilters];
552565
}
553566

567+
CGFloat screenScale = [UIScreen mainScreen].scale;
554568
// The UIKit frame is set based on the logical resolution (points) instead of physical.
569+
//
555570
// (https://developer.apple.com/library/archive/documentation/DeviceInformation/Reference/iOSDeviceCompatibility/Displays/Displays.html).
556571
// However, flow is based on the physical resolution. For example, 1000 pixels in flow equals
557-
// 500 points in UIKit for devices that has screenScale of 2. We need to scale the transformMatrix
572+
// 500 points in UIKit for devices that has screenScale of 2. We need to scale the
573+
// transformMatrix
558574
// down to the logical resoltion before applying it to the layer of PlatformView.
559575
transformMatrix.postScale(1 / screenScale, 1 / screenScale);
560576

@@ -563,11 +579,13 @@ static bool ClipRRectContainsPlatformViewBoundingRect(const SkRRect& clip_rrect,
563579
// Thus, this translate needs to be reversed so the platform view can layout at the correct
564580
// offset.
565581
//
566-
// Note that the transforms are not applied to the clipping paths because clipping paths happen on
582+
// Note that the transforms are not applied to the clipping paths because clipping paths happen
583+
// on
567584
// the mask view, whose origin is always (0,0) to the flutter_view.
568585
transformMatrix.postTranslate(-clipView.frame.origin.x, -clipView.frame.origin.y);
569586

570587
embedded_view.layer.transform = flutter::GetCATransform3DFromSkMatrix(transformMatrix);
588+
// embedded_view.frame = clipView.bounds;
571589
}
572590

573591
void FlutterPlatformViewsController::CompositeWithParams(int view_id,
@@ -893,6 +911,7 @@ static bool ClipRRectContainsPlatformViewBoundingRect(const SkRRect& clip_rrect,
893911
if (catransaction_added_) {
894912
FML_DCHECK([[NSThread currentThread] isMainThread]);
895913
[CATransaction commit];
914+
896915
catransaction_added_ = false;
897916
}
898917
}

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

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2445,6 +2445,111 @@ - (void)testHasFirstResponderInViewHierarchySubtree_descendantViewBecomesFirstRe
24452445
XCTAssertFalse(view.flt_hasFirstResponderInViewHierarchySubtree);
24462446
}
24472447

2448+
- (void)testFlutterClippingMaskViewPoolReuseViewsAfterRecycle {
2449+
FlutterClippingMaskViewPool* pool =
2450+
[[[FlutterClippingMaskViewPool alloc] initWithCapacity:2] autorelease];
2451+
FlutterClippingMaskView* view1 = [pool getMaskViewWithFrame:CGRectZero];
2452+
FlutterClippingMaskView* view2 = [pool getMaskViewWithFrame:CGRectZero];
2453+
[pool recycleMaskViews];
2454+
CGRect newRect = CGRectMake(0, 0, 10, 10);
2455+
FlutterClippingMaskView* view3 = [pool getMaskViewWithFrame:newRect];
2456+
FlutterClippingMaskView* view4 = [pool getMaskViewWithFrame:newRect];
2457+
XCTAssertEqual(view1, view3);
2458+
XCTAssertEqual(view2, view4);
2459+
XCTAssertTrue(CGRectEqualToRect(view3.frame, newRect));
2460+
XCTAssertTrue(CGRectEqualToRect(view4.frame, newRect));
2461+
}
2462+
2463+
- (void)testFlutterClippingMaskViewPoolAllocsNewMaskViewsAfterReachingCapacity {
2464+
FlutterClippingMaskViewPool* pool =
2465+
[[[FlutterClippingMaskViewPool alloc] initWithCapacity:2] autorelease];
2466+
FlutterClippingMaskView* view1 = [pool getMaskViewWithFrame:CGRectZero];
2467+
FlutterClippingMaskView* view2 = [pool getMaskViewWithFrame:CGRectZero];
2468+
FlutterClippingMaskView* view3 = [pool getMaskViewWithFrame:CGRectZero];
2469+
XCTAssertNotEqual(view1, view3);
2470+
XCTAssertNotEqual(view2, view3);
2471+
}
2472+
2473+
- (void)testClipMaskViewIsReused {
2474+
flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
2475+
auto thread_task_runner = CreateNewThread("FlutterPlatformViewsTest");
2476+
flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
2477+
/*platform=*/thread_task_runner,
2478+
/*raster=*/thread_task_runner,
2479+
/*ui=*/thread_task_runner,
2480+
/*io=*/thread_task_runner);
2481+
auto flutterPlatformViewsController = std::make_shared<flutter::FlutterPlatformViewsController>();
2482+
auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
2483+
/*delegate=*/mock_delegate,
2484+
/*rendering_api=*/flutter::IOSRenderingAPI::kSoftware,
2485+
/*platform_views_controller=*/flutterPlatformViewsController,
2486+
/*task_runners=*/runners);
2487+
2488+
FlutterPlatformViewsTestMockFlutterPlatformFactory* factory =
2489+
[[FlutterPlatformViewsTestMockFlutterPlatformFactory new] autorelease];
2490+
flutterPlatformViewsController->RegisterViewFactory(
2491+
factory, @"MockFlutterPlatformView",
2492+
FlutterPlatformViewGestureRecognizersBlockingPolicyEager);
2493+
FlutterResult result = ^(id result) {
2494+
};
2495+
flutterPlatformViewsController->OnMethodCall(
2496+
[FlutterMethodCall
2497+
methodCallWithMethodName:@"create"
2498+
arguments:@{@"id" : @1, @"viewType" : @"MockFlutterPlatformView"}],
2499+
result);
2500+
2501+
XCTAssertNotNil(gMockPlatformView);
2502+
UIView* mockFlutterView = [[[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 10)] autorelease];
2503+
flutterPlatformViewsController->SetFlutterView(mockFlutterView);
2504+
// Create embedded view params
2505+
flutter::MutatorsStack stack1;
2506+
// Layer tree always pushes a screen scale factor to the stack
2507+
SkMatrix screenScaleMatrix =
2508+
SkMatrix::Scale([UIScreen mainScreen].scale, [UIScreen mainScreen].scale);
2509+
stack1.PushTransform(screenScaleMatrix);
2510+
// Push a clip rect
2511+
SkRect rect = SkRect::MakeXYWH(2, 2, 3, 3);
2512+
stack1.PushClipRect(rect);
2513+
2514+
auto embeddedViewParams1 = std::make_unique<flutter::EmbeddedViewParams>(
2515+
screenScaleMatrix, SkSize::Make(10, 10), stack1);
2516+
2517+
flutter::MutatorsStack stack2;
2518+
auto embeddedViewParams2 = std::make_unique<flutter::EmbeddedViewParams>(
2519+
screenScaleMatrix, SkSize::Make(10, 10), stack2);
2520+
2521+
flutterPlatformViewsController->PrerollCompositeEmbeddedView(1, std::move(embeddedViewParams1));
2522+
flutterPlatformViewsController->CompositeEmbeddedView(1);
2523+
UIView* childClippingView1 = gMockPlatformView.superview.superview;
2524+
UIView* maskView1 = childClippingView1.maskView;
2525+
XCTAssertNotNil(maskView1);
2526+
2527+
// Composite a new frame.
2528+
auto embeddedViewParams3 = std::make_unique<flutter::EmbeddedViewParams>(
2529+
screenScaleMatrix, SkSize::Make(10, 10), stack2);
2530+
flutterPlatformViewsController->PrerollCompositeEmbeddedView(1, std::move(embeddedViewParams3));
2531+
flutterPlatformViewsController->CompositeEmbeddedView(1);
2532+
childClippingView1 = gMockPlatformView.superview.superview;
2533+
2534+
// This overrides gMockPlatformView to point to the newly created platform view.
2535+
flutterPlatformViewsController->OnMethodCall(
2536+
[FlutterMethodCall
2537+
methodCallWithMethodName:@"create"
2538+
arguments:@{@"id" : @2, @"viewType" : @"MockFlutterPlatformView"}],
2539+
result);
2540+
2541+
auto embeddedViewParams4 = std::make_unique<flutter::EmbeddedViewParams>(
2542+
screenScaleMatrix, SkSize::Make(10, 10), stack1);
2543+
flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams4));
2544+
flutterPlatformViewsController->CompositeEmbeddedView(2);
2545+
UIView* childClippingView2 = gMockPlatformView.superview.superview;
2546+
2547+
UIView* maskView2 = childClippingView2.maskView;
2548+
XCTAssertEqual(maskView1, maskView2);
2549+
XCTAssertNotNil(childClippingView2.maskView);
2550+
XCTAssertNil(childClippingView1.maskView);
2551+
}
2552+
24482553
// Return true if a correct visual effect view is found. It also implies all the validation in this
24492554
// method passes.
24502555
//

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

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@
3030

3131
- (instancetype)initWithFrame:(CGRect)frame screenScale:(CGFloat)screenScale;
3232

33+
- (void)reset;
34+
3335
// Adds a clip rect operation to the queue.
3436
//
3537
// The `clipSkRect` is transformed with the `matrix` before adding to the queue.
@@ -47,6 +49,20 @@
4749

4850
@end
4951

52+
// A pool that provides |FlutterClippingMaskView|s.
53+
//
54+
// Allocation and deallocation of |FlutterClippingMaskView| is minimized while using the pool.
55+
@interface FlutterClippingMaskViewPool : NSObject
56+
57+
- (instancetype)initWithCapacity:(NSInteger)capacity;
58+
59+
// Reuse a maskView from the pool, or allocate a new one.
60+
- (FlutterClippingMaskView*)getMaskViewWithFrame:(CGRect)frame;
61+
62+
- (void)recycleMaskViews;
63+
64+
@end
65+
5066
// An object represents a blur filter.
5167
//
5268
// This object produces a `backdropFilterView`.
@@ -268,6 +284,7 @@ class FlutterPlatformViewsController {
268284
// Traverse the `mutators_stack` and return the number of clip operations.
269285
int CountClips(const MutatorsStack& mutators_stack);
270286

287+
void ClipViewAddMaskView(UIView* clipView);
271288
// Applies the mutators in the mutators_stack to the UIView chain that was constructed by
272289
// `ReconstructClipViewsChain`
273290
//
@@ -328,6 +345,7 @@ class FlutterPlatformViewsController {
328345
fml::scoped_nsobject<FlutterMethodChannel> channel_;
329346
fml::scoped_nsobject<UIView> flutter_view_;
330347
fml::scoped_nsobject<UIViewController> flutter_view_controller_;
348+
fml::scoped_nsobject<FlutterClippingMaskViewPool> mask_view_pool_;
331349
std::map<std::string, fml::scoped_nsobject<NSObject<FlutterPlatformViewFactory>>> factories_;
332350
std::map<int64_t, fml::scoped_nsobject<NSObject<FlutterPlatformView>>> views_;
333351
std::map<int64_t, fml::scoped_nsobject<FlutterTouchInterceptingView>> touch_interceptors_;

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

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,10 @@ - (instancetype)initWithFrame:(CGRect)frame screenScale:(CGFloat)screenScale {
271271
return self;
272272
}
273273

274+
- (void)reset {
275+
paths_.clear();
276+
}
277+
274278
// In some scenarios, when we add this view as a maskView of the ChildClippingView, iOS added
275279
// this view as a subview of the ChildClippingView.
276280
// This results this view blocking touch events on the ChildClippingView.
@@ -447,3 +451,63 @@ - (void)clipPath:(const SkPath&)path matrix:(const SkMatrix&)matrix {
447451
}
448452

449453
@end
454+
455+
@interface FlutterClippingMaskViewPool ()
456+
457+
@property(assign, nonatomic) NSUInteger capacity;
458+
@property(retain, nonatomic) NSMutableArray<FlutterClippingMaskView*>* pool;
459+
@property(assign, nonatomic) NSUInteger availableIndex;
460+
461+
@end
462+
463+
@implementation FlutterClippingMaskViewPool : NSObject
464+
465+
- (instancetype)initWithCapacity:(NSInteger)capacity {
466+
if (self = [super init]) {
467+
_pool = [[NSMutableArray alloc] initWithCapacity:capacity];
468+
_capacity = capacity;
469+
_availableIndex = 0;
470+
}
471+
return self;
472+
}
473+
474+
// Reuse a maskView from the pool, or allocate a new one.
475+
- (FlutterClippingMaskView*)getMaskViewWithFrame:(CGRect)frame {
476+
FML_DCHECK(self.availableIndex <= self.capacity);
477+
FlutterClippingMaskView* maskView;
478+
if (self.availableIndex == self.capacity) {
479+
// The pool is full, alloc a new one.
480+
maskView =
481+
[[[FlutterClippingMaskView alloc] initWithFrame:frame
482+
screenScale:[UIScreen mainScreen].scale] autorelease];
483+
return maskView;
484+
}
485+
486+
if (self.availableIndex >= self.pool.count) {
487+
// The pool doesn't have enough maskViews, alloc a new one and add to the pool.
488+
maskView =
489+
[[[FlutterClippingMaskView alloc] initWithFrame:frame
490+
screenScale:[UIScreen mainScreen].scale] autorelease];
491+
[self.pool addObject:maskView];
492+
FML_DCHECK(self.pool.count <= self.capacity);
493+
} else {
494+
maskView = [self.pool objectAtIndex:self.availableIndex];
495+
maskView.frame = frame;
496+
[maskView reset];
497+
}
498+
self.availableIndex++;
499+
return maskView;
500+
}
501+
502+
- (void)recycleMaskViews {
503+
self.availableIndex = 0;
504+
}
505+
506+
- (void)dealloc {
507+
[_pool release];
508+
_pool = nil;
509+
510+
[super dealloc];
511+
}
512+
513+
@end

0 commit comments

Comments
 (0)