mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
Revert "Revert "[ios_platform_view] MaskView pool to reuse maskViews." (#39608)" (flutter/engine#40456)
Reland "[ios_platform_view] MaskView pool to reuse maskViews" (#39608)"
This commit is contained in:
parent
e6817b2a03
commit
63569eee26
@ -18,6 +18,8 @@
|
||||
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterViewController_Internal.h"
|
||||
#import "flutter/shell/platform/darwin/ios/ios_surface.h"
|
||||
|
||||
static const NSUInteger kFlutterClippingMaskViewPoolCapacity = 5;
|
||||
|
||||
@implementation UIView (FirstResponder)
|
||||
- (BOOL)flt_hasFirstResponderInViewHierarchySubtree {
|
||||
if (self.isFirstResponder) {
|
||||
@ -434,6 +436,17 @@ int FlutterPlatformViewsController::CountClips(const MutatorsStack& mutators_sta
|
||||
return clipCount;
|
||||
}
|
||||
|
||||
void FlutterPlatformViewsController::ClipViewSetMaskView(UIView* clipView) {
|
||||
if (clipView.maskView) {
|
||||
return;
|
||||
}
|
||||
UIView* flutterView = flutter_view_.get();
|
||||
CGRect frame =
|
||||
CGRectMake(-clipView.frame.origin.x, -clipView.frame.origin.y,
|
||||
CGRectGetWidth(flutterView.bounds), CGRectGetHeight(flutterView.bounds));
|
||||
clipView.maskView = [mask_view_pool_.get() getMaskViewWithFrame:frame];
|
||||
}
|
||||
|
||||
void FlutterPlatformViewsController::ApplyMutators(const MutatorsStack& mutators_stack,
|
||||
UIView* embedded_view,
|
||||
const SkRect& bounding_rect) {
|
||||
@ -444,18 +457,17 @@ void FlutterPlatformViewsController::ApplyMutators(const MutatorsStack& mutators
|
||||
ResetAnchor(embedded_view.layer);
|
||||
ChildClippingView* clipView = (ChildClippingView*)embedded_view.superview;
|
||||
|
||||
CGFloat screenScale = [UIScreen mainScreen].scale;
|
||||
|
||||
UIView* flutter_view = flutter_view_.get();
|
||||
FlutterClippingMaskView* maskView = [[[FlutterClippingMaskView alloc]
|
||||
initWithFrame:CGRectMake(-clipView.frame.origin.x, -clipView.frame.origin.y,
|
||||
CGRectGetWidth(flutter_view.bounds),
|
||||
CGRectGetHeight(flutter_view.bounds))
|
||||
screenScale:screenScale] autorelease];
|
||||
|
||||
SkMatrix transformMatrix;
|
||||
NSMutableArray* blurFilters = [[[NSMutableArray alloc] init] autorelease];
|
||||
FML_DCHECK(!clipView.maskView ||
|
||||
[clipView.maskView isKindOfClass:[FlutterClippingMaskView class]]);
|
||||
if (mask_view_pool_.get() == nil) {
|
||||
mask_view_pool_.reset([[FlutterClippingMaskViewPool alloc]
|
||||
initWithCapacity:kFlutterClippingMaskViewPoolCapacity]);
|
||||
}
|
||||
[mask_view_pool_.get() recycleMaskViews];
|
||||
clipView.maskView = nil;
|
||||
CGFloat screenScale = [UIScreen mainScreen].scale;
|
||||
auto iter = mutators_stack.Begin();
|
||||
while (iter != mutators_stack.End()) {
|
||||
switch ((*iter)->GetType()) {
|
||||
@ -468,8 +480,9 @@ void FlutterPlatformViewsController::ApplyMutators(const MutatorsStack& mutators
|
||||
transformMatrix)) {
|
||||
break;
|
||||
}
|
||||
[maskView clipRect:(*iter)->GetRect() matrix:transformMatrix];
|
||||
clipView.maskView = maskView;
|
||||
ClipViewSetMaskView(clipView);
|
||||
[(FlutterClippingMaskView*)clipView.maskView clipRect:(*iter)->GetRect()
|
||||
matrix:transformMatrix];
|
||||
break;
|
||||
}
|
||||
case kClipRRect: {
|
||||
@ -477,16 +490,18 @@ void FlutterPlatformViewsController::ApplyMutators(const MutatorsStack& mutators
|
||||
transformMatrix)) {
|
||||
break;
|
||||
}
|
||||
[maskView clipRRect:(*iter)->GetRRect() matrix:transformMatrix];
|
||||
clipView.maskView = maskView;
|
||||
ClipViewSetMaskView(clipView);
|
||||
[(FlutterClippingMaskView*)clipView.maskView clipRRect:(*iter)->GetRRect()
|
||||
matrix:transformMatrix];
|
||||
break;
|
||||
}
|
||||
case kClipPath: {
|
||||
// TODO(cyanglaz): Find a way to pre-determine if path contains the PlatformView boudning
|
||||
// rect. See `ClipRRectContainsPlatformViewBoundingRect`.
|
||||
// https://github.com/flutter/flutter/issues/118650
|
||||
[maskView clipPath:(*iter)->GetPath() matrix:transformMatrix];
|
||||
clipView.maskView = maskView;
|
||||
ClipViewSetMaskView(clipView);
|
||||
[(FlutterClippingMaskView*)clipView.maskView clipPath:(*iter)->GetPath()
|
||||
matrix:transformMatrix];
|
||||
break;
|
||||
}
|
||||
case kOpacity:
|
||||
|
||||
@ -2644,6 +2644,125 @@ fml::RefPtr<fml::TaskRunner> CreateNewThread(std::string name) {
|
||||
XCTAssertFalse(view.flt_hasFirstResponderInViewHierarchySubtree);
|
||||
}
|
||||
|
||||
- (void)testFlutterClippingMaskViewPoolReuseViewsAfterRecycle {
|
||||
FlutterClippingMaskViewPool* pool =
|
||||
[[[FlutterClippingMaskViewPool alloc] initWithCapacity:2] autorelease];
|
||||
FlutterClippingMaskView* view1 = [pool getMaskViewWithFrame:CGRectZero];
|
||||
FlutterClippingMaskView* view2 = [pool getMaskViewWithFrame:CGRectZero];
|
||||
[pool recycleMaskViews];
|
||||
CGRect newRect = CGRectMake(0, 0, 10, 10);
|
||||
FlutterClippingMaskView* view3 = [pool getMaskViewWithFrame:newRect];
|
||||
FlutterClippingMaskView* view4 = [pool getMaskViewWithFrame:newRect];
|
||||
XCTAssertEqual(view1, view3);
|
||||
XCTAssertEqual(view2, view4);
|
||||
XCTAssertTrue(CGRectEqualToRect(view3.frame, newRect));
|
||||
XCTAssertTrue(CGRectEqualToRect(view4.frame, newRect));
|
||||
}
|
||||
|
||||
- (void)testFlutterClippingMaskViewPoolAllocsNewMaskViewsAfterReachingCapacity {
|
||||
FlutterClippingMaskViewPool* pool =
|
||||
[[[FlutterClippingMaskViewPool alloc] initWithCapacity:2] autorelease];
|
||||
FlutterClippingMaskView* view1 = [pool getMaskViewWithFrame:CGRectZero];
|
||||
FlutterClippingMaskView* view2 = [pool getMaskViewWithFrame:CGRectZero];
|
||||
FlutterClippingMaskView* view3 = [pool getMaskViewWithFrame:CGRectZero];
|
||||
XCTAssertNotEqual(view1, view3);
|
||||
XCTAssertNotEqual(view2, view3);
|
||||
}
|
||||
|
||||
- (void)testMaskViewsReleasedWhenPoolIsReleased {
|
||||
UIView* retainedView;
|
||||
@autoreleasepool {
|
||||
FlutterClippingMaskViewPool* pool =
|
||||
[[[FlutterClippingMaskViewPool alloc] initWithCapacity:2] autorelease];
|
||||
FlutterClippingMaskView* view = [pool getMaskViewWithFrame:CGRectZero];
|
||||
retainedView = [view retain];
|
||||
XCTAssertGreaterThan(retainedView.retainCount, 1u);
|
||||
}
|
||||
// The only retain left is our manual retain called inside the autorelease pool, meaning the
|
||||
// maskViews are dealloc'd.
|
||||
XCTAssertEqual(retainedView.retainCount, 1u);
|
||||
}
|
||||
|
||||
- (void)testClipMaskViewIsReused {
|
||||
flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
|
||||
auto thread_task_runner = CreateNewThread("FlutterPlatformViewsTest");
|
||||
flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
|
||||
/*platform=*/thread_task_runner,
|
||||
/*raster=*/thread_task_runner,
|
||||
/*ui=*/thread_task_runner,
|
||||
/*io=*/thread_task_runner);
|
||||
auto flutterPlatformViewsController = std::make_shared<flutter::FlutterPlatformViewsController>();
|
||||
auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
|
||||
/*delegate=*/mock_delegate,
|
||||
/*rendering_api=*/flutter::IOSRenderingAPI::kSoftware,
|
||||
/*platform_views_controller=*/flutterPlatformViewsController,
|
||||
/*task_runners=*/runners);
|
||||
|
||||
FlutterPlatformViewsTestMockFlutterPlatformFactory* factory =
|
||||
[[FlutterPlatformViewsTestMockFlutterPlatformFactory new] autorelease];
|
||||
flutterPlatformViewsController->RegisterViewFactory(
|
||||
factory, @"MockFlutterPlatformView",
|
||||
FlutterPlatformViewGestureRecognizersBlockingPolicyEager);
|
||||
FlutterResult result = ^(id result) {
|
||||
};
|
||||
flutterPlatformViewsController->OnMethodCall(
|
||||
[FlutterMethodCall
|
||||
methodCallWithMethodName:@"create"
|
||||
arguments:@{@"id" : @1, @"viewType" : @"MockFlutterPlatformView"}],
|
||||
result);
|
||||
|
||||
XCTAssertNotNil(gMockPlatformView);
|
||||
UIView* mockFlutterView = [[[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 10)] autorelease];
|
||||
flutterPlatformViewsController->SetFlutterView(mockFlutterView);
|
||||
// Create embedded view params
|
||||
flutter::MutatorsStack stack1;
|
||||
// Layer tree always pushes a screen scale factor to the stack
|
||||
SkMatrix screenScaleMatrix =
|
||||
SkMatrix::Scale([UIScreen mainScreen].scale, [UIScreen mainScreen].scale);
|
||||
stack1.PushTransform(screenScaleMatrix);
|
||||
// Push a clip rect
|
||||
SkRect rect = SkRect::MakeXYWH(2, 2, 3, 3);
|
||||
stack1.PushClipRect(rect);
|
||||
|
||||
auto embeddedViewParams1 = std::make_unique<flutter::EmbeddedViewParams>(
|
||||
screenScaleMatrix, SkSize::Make(10, 10), stack1);
|
||||
|
||||
flutter::MutatorsStack stack2;
|
||||
auto embeddedViewParams2 = std::make_unique<flutter::EmbeddedViewParams>(
|
||||
screenScaleMatrix, SkSize::Make(10, 10), stack2);
|
||||
|
||||
flutterPlatformViewsController->PrerollCompositeEmbeddedView(1, std::move(embeddedViewParams1));
|
||||
flutterPlatformViewsController->CompositeEmbeddedView(1);
|
||||
UIView* childClippingView1 = gMockPlatformView.superview.superview;
|
||||
UIView* maskView1 = childClippingView1.maskView;
|
||||
XCTAssertNotNil(maskView1);
|
||||
|
||||
// Composite a new frame.
|
||||
auto embeddedViewParams3 = std::make_unique<flutter::EmbeddedViewParams>(
|
||||
screenScaleMatrix, SkSize::Make(10, 10), stack2);
|
||||
flutterPlatformViewsController->PrerollCompositeEmbeddedView(1, std::move(embeddedViewParams3));
|
||||
flutterPlatformViewsController->CompositeEmbeddedView(1);
|
||||
childClippingView1 = gMockPlatformView.superview.superview;
|
||||
|
||||
// This overrides gMockPlatformView to point to the newly created platform view.
|
||||
flutterPlatformViewsController->OnMethodCall(
|
||||
[FlutterMethodCall
|
||||
methodCallWithMethodName:@"create"
|
||||
arguments:@{@"id" : @2, @"viewType" : @"MockFlutterPlatformView"}],
|
||||
result);
|
||||
|
||||
auto embeddedViewParams4 = std::make_unique<flutter::EmbeddedViewParams>(
|
||||
screenScaleMatrix, SkSize::Make(10, 10), stack1);
|
||||
flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams4));
|
||||
flutterPlatformViewsController->CompositeEmbeddedView(2);
|
||||
UIView* childClippingView2 = gMockPlatformView.superview.superview;
|
||||
|
||||
UIView* maskView2 = childClippingView2.maskView;
|
||||
XCTAssertEqual(maskView1, maskView2);
|
||||
XCTAssertNotNil(childClippingView2.maskView);
|
||||
XCTAssertNil(childClippingView1.maskView);
|
||||
}
|
||||
|
||||
// Return true if a correct visual effect view is found. It also implies all the validation in this
|
||||
// method passes.
|
||||
//
|
||||
|
||||
@ -30,6 +30,8 @@
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame screenScale:(CGFloat)screenScale;
|
||||
|
||||
- (void)reset;
|
||||
|
||||
// Adds a clip rect operation to the queue.
|
||||
//
|
||||
// The `clipSkRect` is transformed with the `matrix` before adding to the queue.
|
||||
@ -47,6 +49,28 @@
|
||||
|
||||
@end
|
||||
|
||||
// A pool that provides |FlutterClippingMaskView|s.
|
||||
//
|
||||
// The pool has a capacity that can be set in the initializer.
|
||||
// When requesting a FlutterClippingMaskView, the pool will first try to reuse an available maskView
|
||||
// in the pool. If there are none available, a new FlutterClippingMaskView is constructed. If the
|
||||
// capacity is reached, the newly constructed FlutterClippingMaskView is not added to the pool.
|
||||
//
|
||||
// Call |recycleMaskViews| to mark all the FlutterClippingMaskViews in the pool available.
|
||||
@interface FlutterClippingMaskViewPool : NSObject
|
||||
|
||||
// Initialize the pool with `capacity`. When the `capacity` is reached, a FlutterClippingMaskView is
|
||||
// constructed when requested, and it is not added to the pool.
|
||||
- (instancetype)initWithCapacity:(NSInteger)capacity;
|
||||
|
||||
// Reuse a maskView from the pool, or allocate a new one.
|
||||
- (FlutterClippingMaskView*)getMaskViewWithFrame:(CGRect)frame;
|
||||
|
||||
// Mark all the maskViews available.
|
||||
- (void)recycleMaskViews;
|
||||
|
||||
@end
|
||||
|
||||
// An object represents a blur filter.
|
||||
//
|
||||
// This object produces a `backdropFilterView`.
|
||||
@ -266,6 +290,7 @@ class FlutterPlatformViewsController {
|
||||
// Traverse the `mutators_stack` and return the number of clip operations.
|
||||
int CountClips(const MutatorsStack& mutators_stack);
|
||||
|
||||
void ClipViewSetMaskView(UIView* clipView);
|
||||
// Applies the mutators in the mutators_stack to the UIView chain that was constructed by
|
||||
// `ReconstructClipViewsChain`
|
||||
//
|
||||
@ -326,6 +351,7 @@ class FlutterPlatformViewsController {
|
||||
fml::scoped_nsobject<FlutterMethodChannel> channel_;
|
||||
fml::scoped_nsobject<UIView> flutter_view_;
|
||||
fml::scoped_nsobject<UIViewController> flutter_view_controller_;
|
||||
fml::scoped_nsobject<FlutterClippingMaskViewPool> mask_view_pool_;
|
||||
std::map<std::string, fml::scoped_nsobject<NSObject<FlutterPlatformViewFactory>>> factories_;
|
||||
std::map<int64_t, fml::scoped_nsobject<NSObject<FlutterPlatformView>>> views_;
|
||||
std::map<int64_t, fml::scoped_nsobject<FlutterTouchInterceptingView>> touch_interceptors_;
|
||||
|
||||
@ -452,3 +452,66 @@ static BOOL _preparedOnce = NO;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@interface FlutterClippingMaskViewPool ()
|
||||
|
||||
// The maximum number of `FlutterClippingMaskView` the pool can contain.
|
||||
// This prevents the pool to grow infinately and limits the maximum memory a pool can use.
|
||||
@property(assign, nonatomic) NSUInteger capacity;
|
||||
@property(retain, nonatomic) NSMutableArray<FlutterClippingMaskView*>* pool;
|
||||
// The index points to the first available FlutterClippingMaskView in the `pool`.
|
||||
@property(assign, nonatomic) NSUInteger availableIndex;
|
||||
|
||||
@end
|
||||
|
||||
@implementation FlutterClippingMaskViewPool : NSObject
|
||||
|
||||
- (instancetype)initWithCapacity:(NSInteger)capacity {
|
||||
if (self = [super init]) {
|
||||
_pool = [[NSMutableArray alloc] initWithCapacity:capacity];
|
||||
_capacity = capacity;
|
||||
_availableIndex = 0;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (FlutterClippingMaskView*)getMaskViewWithFrame:(CGRect)frame {
|
||||
FML_DCHECK(self.availableIndex <= self.capacity);
|
||||
FlutterClippingMaskView* maskView;
|
||||
if (self.availableIndex == self.capacity) {
|
||||
// The pool is full, alloc a new one.
|
||||
maskView =
|
||||
[[[FlutterClippingMaskView alloc] initWithFrame:frame
|
||||
screenScale:[UIScreen mainScreen].scale] autorelease];
|
||||
return maskView;
|
||||
}
|
||||
|
||||
if (self.availableIndex >= self.pool.count) {
|
||||
// The pool doesn't have enough maskViews, alloc a new one and add to the pool.
|
||||
maskView =
|
||||
[[[FlutterClippingMaskView alloc] initWithFrame:frame
|
||||
screenScale:[UIScreen mainScreen].scale] autorelease];
|
||||
[self.pool addObject:maskView];
|
||||
FML_DCHECK(self.pool.count <= self.capacity);
|
||||
} else {
|
||||
// Reuse a maskView from the pool.
|
||||
maskView = [self.pool objectAtIndex:self.availableIndex];
|
||||
maskView.frame = frame;
|
||||
[maskView reset];
|
||||
}
|
||||
self.availableIndex++;
|
||||
return maskView;
|
||||
}
|
||||
|
||||
- (void)recycleMaskViews {
|
||||
self.availableIndex = 0;
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
[_pool release];
|
||||
_pool = nil;
|
||||
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user