[go: nahoru, domu]

Skip to content

Commit

Permalink
reuse maskView
Browse files Browse the repository at this point in the history
reuse mask view

format

format

draft

draft

fixes

test
  • Loading branch information
Chris Yang committed Jan 18, 2023
1 parent 2722c54 commit d9aa181
Show file tree
Hide file tree
Showing 4 changed files with 224 additions and 18 deletions.
55 changes: 37 additions & 18 deletions shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -419,6 +421,17 @@ static bool ClipBoundsContainsPlatformViewBoundingRect(const SkRect& clip_bounds
return clipCount;
}

void FlutterPlatformViewsController::ClipViewAddMaskView(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) {
Expand All @@ -429,18 +442,15 @@ static bool ClipBoundsContainsPlatformViewBoundingRect(const SkRect& clip_bounds
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;
auto iter = mutators_stack.Begin();
while (iter != mutators_stack.End()) {
Expand All @@ -454,26 +464,29 @@ static bool ClipBoundsContainsPlatformViewBoundingRect(const SkRect& clip_bounds
transformMatrix)) {
break;
}
[maskView clipRect:(*iter)->GetRect() matrix:transformMatrix];
clipView.maskView = maskView;
ClipViewAddMaskView(clipView);
[(FlutterClippingMaskView*)clipView.maskView clipRect:(*iter)->GetRect()
matrix:transformMatrix];
break;
}
case kClipRRect: {
if (ClipBoundsContainsPlatformViewBoundingRect((*iter)->GetRRect().getBounds(),
bounding_rect, transformMatrix)) {
break;
}
[maskView clipRRect:(*iter)->GetRRect() matrix:transformMatrix];
clipView.maskView = maskView;
ClipViewAddMaskView(clipView);
[(FlutterClippingMaskView*)clipView.maskView clipRRect:(*iter)->GetRRect()
matrix:transformMatrix];
break;
}
case kClipPath: {
if (ClipBoundsContainsPlatformViewBoundingRect((*iter)->GetPath().getBounds(),
bounding_rect, transformMatrix)) {
break;
}
[maskView clipPath:(*iter)->GetPath() matrix:transformMatrix];
clipView.maskView = maskView;
ClipViewAddMaskView(clipView);
[(FlutterClippingMaskView*)clipView.maskView clipPath:(*iter)->GetPath()
matrix:transformMatrix];
break;
}
case kOpacity:
Expand Down Expand Up @@ -520,10 +533,13 @@ static bool ClipBoundsContainsPlatformViewBoundingRect(const SkRect& clip_bounds
[clipView applyBlurBackdropFilters:blurFilters];
}

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

Expand All @@ -532,11 +548,13 @@ static bool ClipBoundsContainsPlatformViewBoundingRect(const SkRect& clip_bounds
// Thus, this translate needs to be reversed so the platform view can layout at the correct
// offset.
//
// Note that the transforms are not applied to the clipping paths because clipping paths happen on
// Note that the transforms are not applied to the clipping paths because clipping paths happen
// on
// the mask view, whose origin is always (0,0) to the flutter_view.
transformMatrix.postTranslate(-clipView.frame.origin.x, -clipView.frame.origin.y);

embedded_view.layer.transform = flutter::GetCATransform3DFromSkMatrix(transformMatrix);
// embedded_view.frame = clipView.bounds;
}

void FlutterPlatformViewsController::CompositeWithParams(int view_id,
Expand Down Expand Up @@ -862,6 +880,7 @@ static bool ClipBoundsContainsPlatformViewBoundingRect(const SkRect& clip_bounds
if (catransaction_added_) {
FML_DCHECK([[NSThread currentThread] isMainThread]);
[CATransaction commit];

catransaction_added_ = false;
}
}
Expand Down
105 changes: 105 additions & 0 deletions shell/platform/darwin/ios/framework/Source/FlutterPlatformViewsTest.mm
Original file line number Diff line number Diff line change
Expand Up @@ -2384,6 +2384,111 @@ - (void)testHasFirstResponderInViewHierarchySubtree_descendantViewBecomesFirstRe
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)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.
//
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -47,6 +49,20 @@

@end

// A pool that provides |FlutterClippingMaskView|s.
//
// Allocation and deallocation of |FlutterClippingMaskView| is minimized while using the pool.
@interface FlutterClippingMaskViewPool : NSObject

- (instancetype)initWithCapacity:(NSInteger)capacity;

// Reuse a maskView from the pool, or allocate a new one.
- (FlutterClippingMaskView*)getMaskViewWithFrame:(CGRect)frame;

- (void)recycleMaskViews;

@end

// An object represents a blur filter.
//
// This object produces a `backdropFilterView`.
Expand Down Expand Up @@ -268,6 +284,7 @@ class FlutterPlatformViewsController {
// Traverse the `mutators_stack` and return the number of clip operations.
int CountClips(const MutatorsStack& mutators_stack);

void ClipViewAddMaskView(UIView* clipView);
// Applies the mutators in the mutators_stack to the UIView chain that was constructed by
// `ReconstructClipViewsChain`
//
Expand Down Expand Up @@ -328,6 +345,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_;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,10 @@ - (instancetype)initWithFrame:(CGRect)frame screenScale:(CGFloat)screenScale {
return self;
}

- (void)reset {
paths_.clear();
}

// In some scenarios, when we add this view as a maskView of the ChildClippingView, iOS added
// this view as a subview of the ChildClippingView.
// This results this view blocking touch events on the ChildClippingView.
Expand Down Expand Up @@ -447,3 +451,63 @@ - (void)clipPath:(const SkPath&)path matrix:(const SkMatrix&)matrix {
}

@end

@interface FlutterClippingMaskViewPool ()

@property(assign, nonatomic) NSUInteger capacity;
@property(retain, nonatomic) NSMutableArray<FlutterClippingMaskView*>* 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;
}

// Reuse a maskView from the pool, or allocate a new one.
- (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 {
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

0 comments on commit d9aa181

Please sign in to comment.