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
This commit is contained in:
auto-submit[bot] 2024-06-05 14:26:47 +00:00 committed by GitHub
parent d1f28a74ae
commit 4c30aee50a
13 changed files with 38 additions and 150 deletions

View File

@ -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.
//

View File

@ -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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

After

Width:  |  Height:  |  Size: 34 KiB