Reverts "[ios][platform_view] Use CAShapeLayer as the mask to avoid software rendering (#53072)" (flutter/engine#53220)
Reverts: flutter/engine#53072 Initiated by: jason-simmons Reason for reverting: compilation errors on iOS targets ``` FlutterPlatformViews_Internal.mm:277:10: error: ARC forbids explicit message send of 'dealloc' 277 | [super dealloc]; | ~~~~~ ^ ``` (see https://logs.chromium.org/logs/flutter/buildbucket/cr-buildbucket/8746006425774972161/+/u/build_ci_ios_debug_unopt_sim_flutter_testing_scenario_app_flutter_shell_platform_darwin_ios:ios_test_flutter/st Original PR Author: hellohuanlin Reviewed By: {cbracken, jonahwilliams} This change reverts the following previous change: This PR uses `CAShapeLayer` as the mask to avoid software rendering. I kept `UIView` as the mask, so that we can measure just the improvement related to avoiding software rendering. This also allows me to land this change sooner. I created [a separate issue](https://github.com/flutter/flutter/issues/149212) to track removing UIView as the mask. Note: the previous behavior seems to be incorrect (or at least not pixel perfect). This PR fixed it. See comments. See design doc: https://docs.google.com/document/d/1TqG_N4GK_qctuk73Gk3zOdAiILUrwMqxoCMgroK_AeA/edit?resourcekey=0-jUiidfzIS642ngG2w9vSUA&tab=t.0 *List which issues are fixed by this PR. You must list at least one issue.* Fixes https://github.com/flutter/flutter/issues/142813 Fixes https://github.com/flutter/flutter/issues/142830 *If you had to change anything in the [flutter/tests] repo, include a link to the migration guide as per the [breaking change policy].* [C++, Objective-C, Java style guides]: https://github.com/flutter/engine/blob/main/CONTRIBUTING.md#style
@ -1773,15 +1773,17 @@ fml::RefPtr<fml::TaskRunner> CreateNewThread(const std::string& name) {
|
||||
[mockFlutterView setNeedsLayout];
|
||||
[mockFlutterView layoutIfNeeded];
|
||||
|
||||
CGRect insideClipping = CGRectMake(2, 2, 3, 3);
|
||||
for (int i = 0; i < 10; i++) {
|
||||
for (int j = 0; j < 10; j++) {
|
||||
CGPoint point = CGPointMake(i, j);
|
||||
int alpha = [self alphaOfPoint:CGPointMake(i, j) onView:mockFlutterView];
|
||||
// Edges of the clipping might have a semi transparent pixel, we only check the pixels that
|
||||
// are fully inside the clipped area.
|
||||
CGRect insideClipping = CGRectMake(3, 3, 1, 1);
|
||||
if (CGRectContainsPoint(insideClipping, point)) {
|
||||
XCTAssertEqual(alpha, 255);
|
||||
} else {
|
||||
XCTAssertEqual(alpha, 0);
|
||||
XCTAssertLessThan(alpha, 255);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1846,42 +1848,17 @@ fml::RefPtr<fml::TaskRunner> CreateNewThread(const std::string& name) {
|
||||
[mockFlutterView setNeedsLayout];
|
||||
[mockFlutterView layoutIfNeeded];
|
||||
|
||||
/*
|
||||
ClippingMask outterClipping
|
||||
2 3 4 5 6 7 2 3 4 5 6 7
|
||||
2 / - - - - \ 2 + - - - - +
|
||||
3 | | 3 | |
|
||||
4 | | 4 | |
|
||||
5 | | 5 | |
|
||||
6 | | 6 | |
|
||||
7 \ - - - - / 7 + - - - - +
|
||||
|
||||
innerClipping1 innerClipping2
|
||||
2 3 4 5 6 7 2 3 4 5 6 7
|
||||
2 + - - + 2
|
||||
3 | | 3 + - - - - +
|
||||
4 | | 4 | |
|
||||
5 | | 5 | |
|
||||
6 | | 6 + - - - - +
|
||||
7 + - - + 7
|
||||
*/
|
||||
CGRect innerClipping1 = CGRectMake(3, 2, 4, 6);
|
||||
CGRect innerClipping2 = CGRectMake(2, 3, 6, 4);
|
||||
CGRect outterClipping = CGRectMake(2, 2, 6, 6);
|
||||
for (int i = 0; i < 10; i++) {
|
||||
for (int j = 0; j < 10; j++) {
|
||||
CGPoint point = CGPointMake(i, j);
|
||||
int alpha = [self alphaOfPoint:CGPointMake(i, j) onView:mockFlutterView];
|
||||
if (CGRectContainsPoint(innerClipping1, point) ||
|
||||
CGRectContainsPoint(innerClipping2, point)) {
|
||||
// Pixels inside either of the 2 inner clippings should be fully opaque.
|
||||
// Edges of the clipping might have a semi transparent pixel, we only check the pixels that
|
||||
// are fully inside the clipped area.
|
||||
CGRect insideClipping = CGRectMake(3, 3, 4, 4);
|
||||
if (CGRectContainsPoint(insideClipping, point)) {
|
||||
XCTAssertEqual(alpha, 255);
|
||||
} else if (CGRectContainsPoint(outterClipping, point)) {
|
||||
// Corner pixels (i.e. (2, 2), (2, 7), (7, 2) and (7, 7)) should be partially transparent.
|
||||
XCTAssert(0 < alpha && alpha < 255);
|
||||
} else {
|
||||
// Pixels outside outterClipping should be fully transparent.
|
||||
XCTAssertEqual(alpha, 0);
|
||||
XCTAssertLessThan(alpha, 255);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1947,42 +1924,17 @@ fml::RefPtr<fml::TaskRunner> CreateNewThread(const std::string& name) {
|
||||
[mockFlutterView setNeedsLayout];
|
||||
[mockFlutterView layoutIfNeeded];
|
||||
|
||||
/*
|
||||
ClippingMask outterClipping
|
||||
2 3 4 5 6 7 2 3 4 5 6 7
|
||||
2 / - - - - \ 2 + - - - - +
|
||||
3 | | 3 | |
|
||||
4 | | 4 | |
|
||||
5 | | 5 | |
|
||||
6 | | 6 | |
|
||||
7 \ - - - - / 7 + - - - - +
|
||||
|
||||
innerClipping1 innerClipping2
|
||||
2 3 4 5 6 7 2 3 4 5 6 7
|
||||
2 + - - + 2
|
||||
3 | | 3 + - - - - +
|
||||
4 | | 4 | |
|
||||
5 | | 5 | |
|
||||
6 | | 6 + - - - - +
|
||||
7 + - - + 7
|
||||
*/
|
||||
CGRect innerClipping1 = CGRectMake(3, 2, 4, 6);
|
||||
CGRect innerClipping2 = CGRectMake(2, 3, 6, 4);
|
||||
CGRect outterClipping = CGRectMake(2, 2, 6, 6);
|
||||
for (int i = 0; i < 10; i++) {
|
||||
for (int j = 0; j < 10; j++) {
|
||||
CGPoint point = CGPointMake(i, j);
|
||||
int alpha = [self alphaOfPoint:CGPointMake(i, j) onView:mockFlutterView];
|
||||
if (CGRectContainsPoint(innerClipping1, point) ||
|
||||
CGRectContainsPoint(innerClipping2, point)) {
|
||||
// Pixels inside either of the 2 inner clippings should be fully opaque.
|
||||
// Edges of the clipping might have a semi transparent pixel, we only check the pixels that
|
||||
// are fully inside the clipped area.
|
||||
CGRect insideClipping = CGRectMake(3, 3, 4, 4);
|
||||
if (CGRectContainsPoint(insideClipping, point)) {
|
||||
XCTAssertEqual(alpha, 255);
|
||||
} else if (CGRectContainsPoint(outterClipping, point)) {
|
||||
// Corner pixels (i.e. (2, 2), (2, 7), (7, 2) and (7, 7)) should be partially transparent.
|
||||
XCTAssert(0 < alpha && alpha < 255);
|
||||
} else {
|
||||
// Pixels outside outterClipping should be fully transparent.
|
||||
XCTAssertEqual(alpha, 0);
|
||||
XCTAssertLessThan(alpha, 255);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -3037,69 +2989,6 @@ fml::RefPtr<fml::TaskRunner> CreateNewThread(const std::string& name) {
|
||||
XCTAssertNotEqual(maskView1, maskView2);
|
||||
}
|
||||
|
||||
- (void)testMaskViewUsesCAShapeLayerAsTheBackingLayer {
|
||||
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=*/mock_delegate.settings_.enable_impeller
|
||||
? flutter::IOSRenderingAPI::kMetal
|
||||
: flutter::IOSRenderingAPI::kSoftware,
|
||||
/*platform_views_controller=*/flutterPlatformViewsController,
|
||||
/*task_runners=*/runners,
|
||||
/*worker_task_runner=*/nil,
|
||||
/*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
|
||||
|
||||
FlutterPlatformViewsTestMockFlutterPlatformFactory* factory =
|
||||
[[FlutterPlatformViewsTestMockFlutterPlatformFactory alloc] init];
|
||||
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)];
|
||||
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;
|
||||
stack2.PushClipRect(rect);
|
||||
auto embeddedViewParams2 = std::make_unique<flutter::EmbeddedViewParams>(
|
||||
screenScaleMatrix, SkSize::Make(10, 10), stack2);
|
||||
|
||||
flutterPlatformViewsController->PrerollCompositeEmbeddedView(1, std::move(embeddedViewParams1));
|
||||
flutterPlatformViewsController->CompositeEmbeddedView(1);
|
||||
UIView* childClippingView = gMockPlatformView.superview.superview;
|
||||
|
||||
UIView* maskView = childClippingView.maskView;
|
||||
XCTAssert([maskView.layer isKindOfClass:[CAShapeLayer class]],
|
||||
@"Mask view must use CAShapeLayer as its backing layer.");
|
||||
}
|
||||
|
||||
// Return true if a correct visual effect view is found. It also implies all the validation in this
|
||||
// method passes.
|
||||
//
|
||||
|
||||
@ -236,12 +236,12 @@ static BOOL _preparedOnce = NO;
|
||||
// information about screen scale.
|
||||
@property(nonatomic) CATransform3D reverseScreenScale;
|
||||
|
||||
- (void)addTransformedPath:(CGPathRef)path matrix:(CATransform3D)matrix;
|
||||
- (fml::CFRef<CGPathRef>)getTransformedPath:(CGPathRef)path matrix:(CATransform3D)matrix;
|
||||
|
||||
@end
|
||||
|
||||
@implementation FlutterClippingMaskView {
|
||||
CGMutablePathRef pathSoFar_;
|
||||
std::vector<fml::CFRef<CGPathRef>> paths_;
|
||||
}
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame {
|
||||
@ -252,31 +252,15 @@ static BOOL _preparedOnce = NO;
|
||||
if (self = [super initWithFrame:frame]) {
|
||||
self.backgroundColor = UIColor.clearColor;
|
||||
_reverseScreenScale = CATransform3DMakeScale(1 / screenScale, 1 / screenScale, 1);
|
||||
pathSoFar_ = CGPathCreateMutable();
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
+ (Class)layerClass {
|
||||
return [CAShapeLayer class];
|
||||
}
|
||||
|
||||
- (CAShapeLayer*)shapeLayer {
|
||||
return (CAShapeLayer*)self.layer;
|
||||
}
|
||||
|
||||
- (void)reset {
|
||||
CGPathRelease(pathSoFar_);
|
||||
pathSoFar_ = CGPathCreateMutable();
|
||||
[self shapeLayer].path = nil;
|
||||
paths_.clear();
|
||||
[self setNeedsDisplay];
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
CGPathRelease(pathSoFar_);
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
// 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.
|
||||
@ -286,13 +270,28 @@ static BOOL _preparedOnce = NO;
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (void)drawRect:(CGRect)rect {
|
||||
CGContextRef context = UIGraphicsGetCurrentContext();
|
||||
CGContextSaveGState(context);
|
||||
|
||||
// For mask view, only the alpha channel is used.
|
||||
CGContextSetAlpha(context, 1);
|
||||
|
||||
for (size_t i = 0; i < paths_.size(); i++) {
|
||||
CGContextAddPath(context, paths_.at(i));
|
||||
CGContextClip(context);
|
||||
}
|
||||
CGContextFillRect(context, rect);
|
||||
CGContextRestoreGState(context);
|
||||
}
|
||||
|
||||
- (void)clipRect:(const SkRect&)clipSkRect matrix:(const SkMatrix&)matrix {
|
||||
CGRect clipRect = flutter::GetCGRectFromSkRect(clipSkRect);
|
||||
CGPathRef path = CGPathCreateWithRect(clipRect, nil);
|
||||
// The `matrix` is based on the physical pixels, convert it to UIKit points.
|
||||
CATransform3D matrixInPoints =
|
||||
CATransform3DConcat(flutter::GetCATransform3DFromSkMatrix(matrix), _reverseScreenScale);
|
||||
[self addTransformedPath:path matrix:matrixInPoints];
|
||||
paths_.push_back([self getTransformedPath:path matrix:matrixInPoints]);
|
||||
}
|
||||
|
||||
- (void)clipRRect:(const SkRRect&)clipSkRRect matrix:(const SkMatrix&)matrix {
|
||||
@ -361,7 +360,7 @@ static BOOL _preparedOnce = NO;
|
||||
// TODO(cyanglaz): iOS does not seem to support hard edge on CAShapeLayer. It clearly stated that
|
||||
// the CAShaperLayer will be drawn antialiased. Need to figure out a way to do the hard edge
|
||||
// clipping on iOS.
|
||||
[self addTransformedPath:pathRef matrix:matrixInPoints];
|
||||
paths_.push_back([self getTransformedPath:pathRef matrix:matrixInPoints]);
|
||||
}
|
||||
|
||||
- (void)clipPath:(const SkPath&)path matrix:(const SkMatrix&)matrix {
|
||||
@ -426,15 +425,15 @@ static BOOL _preparedOnce = NO;
|
||||
// The `matrix` is based on the physical pixels, convert it to UIKit points.
|
||||
CATransform3D matrixInPoints =
|
||||
CATransform3DConcat(flutter::GetCATransform3DFromSkMatrix(matrix), _reverseScreenScale);
|
||||
[self addTransformedPath:pathRef matrix:matrixInPoints];
|
||||
paths_.push_back([self getTransformedPath:pathRef matrix:matrixInPoints]);
|
||||
}
|
||||
|
||||
- (void)addTransformedPath:(CGPathRef)path matrix:(CATransform3D)matrix {
|
||||
- (fml::CFRef<CGPathRef>)getTransformedPath:(CGPathRef)path matrix:(CATransform3D)matrix {
|
||||
CGAffineTransform affine =
|
||||
CGAffineTransformMake(matrix.m11, matrix.m12, matrix.m21, matrix.m22, matrix.m41, matrix.m42);
|
||||
CGPathAddPath(pathSoFar_, &affine, path);
|
||||
[self shapeLayer].path = pathSoFar_;
|
||||
CGPathRef transformedPath = CGPathCreateCopyByTransformingPath(path, &affine);
|
||||
CGPathRelease(path);
|
||||
return fml::CFRef<CGPathRef>(transformedPath);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 41 KiB After Width: | Height: | Size: 34 KiB |