Skip to content

Commit b970c2f

Browse files
joevilchesuffoltzl
authored and
uffoltzl
committed
Use CA shadow apis for box shadow (facebook#50636)
Summary: Pull Request resolved: facebook#50636 A while ago someone on GitHub reported that box shadow on iOS was causing frame drops when animating a large, pretty blurry shadow: facebook#49128. This week I finally got around to fixing this! The slowness was happening since we were using CG to draw this shadow, which is very CPU intensive and, to my knowledge, does not take advantage of GPUs to do anything. Couple that with an animating, large, blurry shadow and we have frame issues. These shadows were taking very long to draw, to get the image of the shadow (which then needs to be copied into some texture 3 times as big, composited, put on the screen etc) took 12-14ms :o, thats very slow. To fix this I figured out how to get CA's shadow APIs working, which take advantage of the GPU. The enable inset shadows and spread you have to get creative with a mix of `shadowPath` and `mask` with a `CAShapeLayer`, but we got it done! Things are much faster, I am not sure how to time this but using a real device shows no frame drops :D Changelog: [iOS][Fixed] - Box shadows on iOS are faster Reviewed By: lenaic Differential Revision: D72823334 fbshipit-source-id: 460339c9d77e7423ce59a1a9178b6b3ad527e4b0
1 parent 0fe8872 commit b970c2f

File tree

6 files changed

+154
-265
lines changed

6 files changed

+154
-265
lines changed

packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm

+22-19
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ @implementation RCTViewComponentView {
3838
CALayer *_backgroundColorLayer;
3939
__weak CALayer *_borderLayer;
4040
CALayer *_outlineLayer;
41-
CALayer *_boxShadowLayer;
41+
NSMutableArray<CALayer *> *_boxShadowLayers;
4242
CALayer *_filterLayer;
4343
NSMutableArray<CALayer *> *_backgroundImageLayers;
4444
BOOL _needsInvalidateLayer;
@@ -828,7 +828,7 @@ - (void)invalidateLayer
828828
// If view has a solid background color, calculate shadow path from border.
829829
const RCTCornerInsets cornerInsets =
830830
RCTGetCornerInsets(RCTCornerRadiiFromBorderRadii(borderMetrics.borderRadii), UIEdgeInsetsZero);
831-
CGPathRef shadowPath = RCTPathCreateWithRoundedRect(self.bounds, cornerInsets, nil);
831+
CGPathRef shadowPath = RCTPathCreateWithRoundedRect(self.bounds, cornerInsets, nil, NO);
832832
layer.shadowPath = shadowPath;
833833
CGPathRelease(shadowPath);
834834
} else {
@@ -852,9 +852,9 @@ - (void)invalidateLayer
852852
// rendering incorrectly on iOS, iOS apps in compatibility mode on visionOS, but not on visionOS.
853853
// To work around this, for iOS, we can calculate the border path based on `view.frame` (the
854854
// superview's coordinate space) instead of view.bounds.
855-
CGPathRef borderPath = RCTPathCreateWithRoundedRect(self.frame, cornerInsets, NULL);
855+
CGPathRef borderPath = RCTPathCreateWithRoundedRect(self.frame, cornerInsets, NULL, NO);
856856
#else // TARGET_OS_VISION
857-
CGPathRef borderPath = RCTPathCreateWithRoundedRect(self.bounds, cornerInsets, NULL);
857+
CGPathRef borderPath = RCTPathCreateWithRoundedRect(self.bounds, cornerInsets, NULL, NO);
858858
#endif
859859
UIBezierPath *bezierPath = [UIBezierPath bezierPathWithCGPath:borderPath];
860860
CGPathRelease(borderPath);
@@ -1016,21 +1016,24 @@ - (void)invalidateLayer
10161016
}
10171017

10181018
// box shadow
1019-
[_boxShadowLayer removeFromSuperlayer];
1020-
_boxShadowLayer = nil;
1019+
for (CALayer *boxShadowLayer in _boxShadowLayers) {
1020+
[boxShadowLayer removeFromSuperlayer];
1021+
}
1022+
[_boxShadowLayers removeAllObjects];
10211023
if (!_props->boxShadow.empty()) {
1022-
_boxShadowLayer = [CALayer layer];
1023-
[self.layer addSublayer:_boxShadowLayer];
1024-
_boxShadowLayer.zPosition = _borderLayer.zPosition;
1025-
_boxShadowLayer.frame = RCTGetBoundingRect(_props->boxShadow, self.layer.bounds.size);
1026-
1027-
UIImage *boxShadowImage = RCTGetBoxShadowImage(
1028-
_props->boxShadow,
1029-
RCTCornerRadiiFromBorderRadii(borderMetrics.borderRadii),
1030-
RCTUIEdgeInsetsFromEdgeInsets(borderMetrics.borderWidths),
1031-
self.layer.bounds.size);
1032-
1033-
_boxShadowLayer.contents = (id)boxShadowImage.CGImage;
1024+
if (!_boxShadowLayers) {
1025+
_boxShadowLayers = [NSMutableArray new];
1026+
}
1027+
for (auto it = _props->boxShadow.rbegin(); it != _props->boxShadow.rend(); ++it) {
1028+
CALayer *shadowLayer = RCTGetBoxShadowLayer(
1029+
*it,
1030+
RCTCornerRadiiFromBorderRadii(borderMetrics.borderRadii),
1031+
RCTUIEdgeInsetsFromEdgeInsets(borderMetrics.borderWidths),
1032+
self.layer.bounds.size);
1033+
shadowLayer.zPosition = _borderLayer.zPosition;
1034+
[self.layer addSublayer:shadowLayer];
1035+
[_boxShadowLayers addObject:shadowLayer];
1036+
}
10341037
}
10351038

10361039
// clipping
@@ -1095,7 +1098,7 @@ - (void)shapeLayerToMatchView:(CALayer *)layer borderMetrics:(BorderMetrics)bord
10951098

10961099
- (CAShapeLayer *)createMaskLayer:(CGRect)bounds cornerInsets:(RCTCornerInsets)cornerInsets
10971100
{
1098-
CGPathRef path = RCTPathCreateWithRoundedRect(bounds, cornerInsets, nil);
1101+
CGPathRef path = RCTPathCreateWithRoundedRect(bounds, cornerInsets, nil, NO);
10991102
CAShapeLayer *maskLayer = [CAShapeLayer layer];
11001103
maskLayer.path = path;
11011104
CGPathRelease(path);

packages/react-native/React/Fabric/Utils/RCTBoxShadow.h

+2-4
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,8 @@
1212
#import <UIKit/UIKit.h>
1313
#import <react/renderer/graphics/BoxShadow.h>
1414

15-
RCT_EXTERN UIImage *RCTGetBoxShadowImage(
16-
const std::vector<facebook::react::BoxShadow> &shadows,
15+
RCT_EXTERN CALayer *RCTGetBoxShadowLayer(
16+
const facebook::react::BoxShadow &shadow,
1717
RCTCornerRadii cornerRadii,
1818
UIEdgeInsets edgeInsets,
1919
CGSize layerSize);
20-
21-
RCT_EXTERN CGRect RCTGetBoundingRect(const std::vector<facebook::react::BoxShadow> &boxShadows, CGSize layerSize);

0 commit comments

Comments
 (0)