Why understand off-screen rendering
Offscreen rendering is not a new concept for iOS developers. It is more or less common in projects and is often examined in interviews. In general, most people know that setting rounded corners, masks, shadows, etc., triggers off-screen rendering, but if we dig a little deeper, can we clearly understand the following questions?
- Did the off-screen rendering take place at any point?
- What causes off-screen rendering?
- Does setting rounded corners necessarily trigger off-screen rendering?
- Why do we use off-screen rendering when it affects performance? What does the optimization plan have?
Today I’m going to explore off-screen rendering with these questions.
Rendering framework for ios
Core Animation pipeline:
In the Animations for iOS Apps section of WWDC (WWDC14 419), we can see that the CPU is doing the work in the Application layer. When it comes to the Render Server layer, CoreAnimation will convert specific operations into draw calls sent to GPU (formerly call OpenGL ES, now slowly transferred to Metal). Obviously, BOTH CPU and GPU are in the same pipeline. Collaborate on the entire render. We can also think of Core Animation in iOS as a composite engine, which is mainly responsible for rendering, building and implementing animations.
Definition of off-screen rendering
- In OpenGL, the GPU screen can be rendered in the following two waysOn-screen Rendering: Normally, we display on-screen data rendered by the GPU reading the Frame Buffer and displaying it On the Screen. The process is shown as follows:
- Off-screen Rendering is called when Rendering results cannot be directly written to the frame buffer due to limitations, but are temporarily stored in another area of memory before being written to the frame Buffer. That is to say, the GPU needs to create a new buffer for rendering in addition to the current screen buffer. The process is shown as follows:
In the diagram of CoreAnimation pipeline above, we can know that the main rendering operation is performed by the Render Server module of CoreAnimation by calling the OpenGL or Metal interface provided by the graphics card driver.
For each layer, Render Server follows”
The painter algorithm
“(from far to near), output to frame Buffer in sequence,
It is then drawn to the screen in sequence, and when a layer is drawn, it is removed from the frame cache (to save space)
As shown below, output from left to right,
Get the final display result.
However, in some scenes, although the “painter algorithm” can output layer by layer, it cannot go back to erase/modify a certain part after a certain layer is rendered, because the layer pixel data before this layer has been permanently overwritten. This means that each layer can either be rendered in a single pass, or a chunk of memory can be set aside as a temporary transit zone for complex modifications/clipping operations.
For example:
Figure 3 for rounded corners and tailoring: imageView. ClipsToBounds = YES, imageView. Layer. The cornerRadius = 10, this is not a simple layer overlay,
FIG. 1, FIG. 2, FIG. 3 After rendering,
There are cuts to be made, and
The layer subview, because the parent view has rounded corners, needs to be clipped,
There is no way to go back and erase/change parts of a layer after it has been rendered. So you can’t follow the normal process, so apple will render each layer and store it in a buffer, i.e
Off-screen buffer
, then layered and processed, stored in the frame cache, and then drawn to the screen, a process called
Off-screen rendering
Common off-screen rendering scene analysis
Use the Simulator to test the layer that triggers the off-screen rendering in the project, as shown below:
Turn on the Color off-screen Rendered, and with Xcode or Reveal we can clearly see which layers have triggered the off-screen rendering.
Examples of common Settings for fillet trigger off-screen render description:
As shown in the sample code above (btn.png is a 200×300 local image),
- Btn1 sets images, sets rounded corners, turns clipsToBounds = YES, triggers off-screen render,
- Btn2 sets the background color, sets rounded corners, turns clipsToBounds = YES, does not trigger off-screen render,
- Img1 set image, set rounded corners, turn on masksToBounds = YES, trigger off screen render,
- Img2 set the background color, set rounded corners, turned on masksToBounds = YES, did not trigger the off-screen render
Explanation: BTN1 and IMg1 trigger off-screen rendering, because BTN1 is a blend of its layer and UIImageView layer (UIButton has imageView), so rounded corners trigger off-screen rendering. Img1 setting the cornerRadius and masksToBounds will not trigger off-screen rendering, and setting the background color for IMg1 will trigger off-screen rendering.
According to the example, it can be concluded that the off-screen rendering will not be triggered only when the control is set with rounded corners or (rounded corners + clipping), and the off-screen rendering will be triggered only when the parent layer needs to be clipped and the child layer also needs to be clipped because the parent layer has rounded corners (i.e. the view contents have contents and multiple image layers are clipped).
The Official Apple documentation describes the cornerRadius:
Setting the radius to a value greater than 0.0 causes the layer to begin drawing rounded corners on its background.by Default, the corner radius does not apply to the image in the layer’s contents property; it applies only to the background color and border of the layer. However, setting the masksToBounds property to true causes the content to be clipped to the rounded corners.
When the cornerRadius is greater than 0, only the layer backgroundColor and border are rounded. Layer contents will not be rounded unless layer.masksToBounds is set to true (corresponding to UIView clipsToBounds).
Rounded corners trigger off-screen rendering sketches
Once we have
Set the contents for contents, whether it is a picture, drawing content, subviews with image information, etc., plus rounded corners + cropping, it will trigger off-screen rendering.
Other scenarios that trigger off-screen rendering:
- ShouldRasterize (layer. ShouldRasterize)
- Layer with mask (layer.mask)
- Layer (layer.maskstobounds/view.clipstobounds)
- Set up a group to YES, transparency and transparency of 1 layer (layer. AllowsGroupOpacity/layer. Opacity)
- Gaussian blur is used
- Added shadow layer (layer.shadow*)
- Layer with Text drawn (UILabel, CATextLayer, Core Text, etc.)
ShouldRasterize rasterizer
ShouldRasterize, when turned on, saves the layer as a bitmap and mixes it directly with other content next time. The save location is in OffscreenBuffer. The next time you need to render it again, you can use it directly.
ShouldRasterize shouldRasterize
- Layer is not reusable, there is no need to turn shouldRasterize on
- Layer is not static, that is to say, it needs to be changed frequently. ShouldRasterize is not necessary
- There is a 100ms time limit for off-screen rendering cached content. Content beyond this time is discarded and cannot be reused
- The off-screen rendering space is 2.5 times that of the screen pixels and cannot be reused if it exceeds that
The pros and cons of off-screen rendering
disadvantage
Off-screen rendering increases the burden of the system and improves App performance. Mainly in the following aspects:
- Off-screen rendering requires additional storage space, with an upper limit of 2.5 times the screen pixel size, beyond which off-screen rendering cannot be used
- Easy to drop frames: Once the final frame cache is saved due to off-screen rendering, and it has exceeded 16.67ms, the frame will drop, resulting in lag
advantage
While off-screen rendering requires a new temporary cache to store intermediate state, data that appears multiple times on the screen can be rendered in advance for reuse so that the CPU/GPU does not have to do repeated calculations.
Special product requirements, in order to achieve some special dynamic effects, need multiple layers and off-screen cache to preserve the intermediate state, in this case, off-screen rendering is necessary. For example, if the product needs to implement Gaussian blur, either custom Gaussian blur or call system API will trigger off-screen rendering.
Off-screen rendering optimization scheme (about realizing off-screen rendering optimization caused by rounded corners)
Plan a
self.view.layer.clipsToBounds = YES; self.view.layer.cornerRadius = 4.f;Copy the code
- ClipsToBounds: property in UIView whose value determines whether the portions of a subview that exceed the parent view are clipped. The default is NO.
- MasksToBounds: CALayer property that determines whether or not the view’s sublayers should be clippedbeyond the parent layer. NO by default.
Scheme 2
If the product designs cards with rounded corners and shadows, you can use cut diagrams to achieve rounded corners and shadows to avoid triggering off-screen rendering
Plan 3
Bezier curves draw rounded corners
- (UIImage *)imageWithCornerRadius:(CGFloat)radius ofSize:(CGSize)size{/* the visible drawing area of the current UIImage */ CGRect rect = (CGRect){0.f,0.f,size}; / * create context based on bitmap * / UIGraphicsBeginImageContextWithOptions (size, NO, UIScreen. MainScreen. Scale); / * * context to add rounded corners in the current bitmap rendering path/CGContextAddPath (UIGraphicsGetCurrentContext (), [UIBezierPath bezierPathWithRoundedRect:rect cornerRadius:radius].CGPath); / * path of the current drawing and original drawing intersecting final cutting drawing path * / CGContextClip (UIGraphicsGetCurrentContext ()); /* Draw */ [self drawInRect:rect]; / * image after cutting * / UIImage * image = UIGraphicsGetImageFromCurrentImageContext (); Context * / / * close the current bitmap UIGraphicsEndImageContext ();return image;
}Copy the code
Plan 4
CAShapeLayer + UIBezierPath rounders the UITableViewCell and draws the border color (this is better than rounder, but also triggers an off-screen rendering).
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath{
CAShapeLayer *maskLayer = [CAShapeLayer layer];
maskLayer.frame = CGRectMake(0, 0, cell.width, cell.height);
CAShapeLayer *borderLayer = [CAShapeLayer layer];
borderLayer.frame = CGRectMake(0, 0, cell.width, cell.height);
borderLayer.lineWidth = 1.f;
borderLayer.strokeColor = COLOR_LINE.CGColor;
borderLayer.fillColor = [UIColor clearColor].CGColor;
UIBezierPath *bezierPath = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(0, 0, cell.width, cell.height) cornerRadius:kRadiusCard];
maskLayer.path = bezierPath.CGPath;
borderLayer.path = bezierPath.CGPath;
[cell.contentView.layer insertSublayer:borderLayer atIndex:0];
[cell.layer setMask:maskLayer];
}
Copy the code
Interpretation of option 4:
- CAShapeLayer inherits from CALayer, so it can use all of CALayer’s attribute values.
- CAShapeLayer needs to be used together with Bessel curve to achieve the effect;
- CAShapeLayer(belonging to CoreAnimation) and Bezier curve can be used together to draw the desired graph without using the drawRect method of view (which is derived from CoreGraphics, and consumes more performance).
- CAShapeLayer animation rendering is driven by the GPU, while view’s drawRect method uses CPU rendering, which is more efficient and consumes less memory.
Overall, using CAShapeLayer consumes less memory and renders faster.
YYKit is a commonly used tripartite library in the development of YYImage. The processing method of picture rounded corners is recommended. The implementation source code is attached:
- (UIImage *)imageByRoundCornerRadius:(CGFloat)radius
corners:(UIRectCorner)corners
borderWidth:(CGFloat)borderWidth
borderColor:(UIColor *)borderColor
borderLineJoin:(CGLineJoin)borderLineJoin {
if(corners ! = UIRectCornerAllCorners) { UIRectCorner tmp = 0;if (corners & UIRectCornerTopLeft) tmp |= UIRectCornerBottomLeft;
if (corners & UIRectCornerTopRight) tmp |= UIRectCornerBottomRight;
if (corners & UIRectCornerBottomLeft) tmp |= UIRectCornerTopLeft;
if (corners & UIRectCornerBottomRight) tmp |= UIRectCornerTopRight;
corners = tmp;
}
UIGraphicsBeginImageContextWithOptions(self.size, NO, self.scale);
CGContextRef context = UIGraphicsGetCurrentContext();
CGRect rect = CGRectMake(0, 0, self.size.width, self.size.height);
CGContextScaleCTM(context, 1, -1);
CGContextTranslateCTM(context, 0, -rect.size.height);
CGFloat minSize = MIN(self.size.width, self.size.height);
if (borderWidth < minSize / 2) {
UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:CGRectInset(rect, borderWidth, borderWidth) byRoundingCorners:corners cornerRadii:CGSizeMake(radius, borderWidth)];
[path closePath];
CGContextSaveGState(context);
[path addClip];
CGContextDrawImage(context, rect, self.CGImage);
CGContextRestoreGState(context);
}
if(borderColor && borderWidth < minSize / 2 && borderWidth > 0) { CGFloat strokeInset = (floor(borderWidth * self.scale) + 0.5)/self. Scale; CGRect strokeRect = CGRectInset(rect, strokeInset, strokeInset); CGFloat strokeRadius = radius > self.scale / 2 ? radius - self.scale / 2 : 0; UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:strokeRect byRoundingCorners:corners cornerRadii:CGSizeMake(strokeRadius, borderWidth)]; [path closePath]; path.lineWidth = borderWidth; path.lineJoinStyle = borderLineJoin; [borderColorsetStroke];
[path stroke];
}
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}
Copy the code
Read more:
IOS Rounded corners trigger off-screen rendering: A more detailed analysis of rounded corners trigger off-screen rendering
An in-depth look at iOS off-screen rendering: The Instant Technology team’s analysis of off-screen rendering