iOS的屏幕渲染原理

2,305 阅读4分钟

屏幕显示的原理-系统层面

首先,我们看无聊看动态的视频看静态的画面,手机都会以特定的频率刷新当前界面,120HZ就是每秒120帧画面,60HZ就是每秒60帧画面,画面是由点组成的,分辨率就是具体的点数。而显示设备要做的就是计算各个像素点的显示数值,并把它显示在屏幕上。那么大致的流程是怎么样的呢?

0001.png

上图是iOS 渲染框架组成部分,其中UIKit位于最上层,组成界面的各个元素基本上都来自UIKit,我们可以给它设置布局,可以通过绘制改变它的显示内容,除此之外还负责事件的接收,其实界面的显示是由它的一个被称为图层的属性CALayer来完成的。这个放在后面详细介绍。UIKit的下一层是Core Animation,最开始接触iOS的时候,我一直以为Core Animation只是用于生成动画的,实际上动画的生成只是Core Animation的冰山一角,可以说在iOS上绝大多数的原生控件都是通过Core Animation绘制出来的,Core Animation在这里最重要的任务是尽可能快地合成图层送到下一级。位于Core Animation之下是Open GL 以及 Core Graphic,其中Open GL 使用GPU进行渲染,而Core Graphic则是使用Qurtaz 2D引擎使用CPU进行渲染,这里个人理解Core Graphic不单单只有CPU参与,最终渲染到屏幕上还是需要GPU参与,这部分在下个小结中将会进行详细介绍,各个GPU厂商的实现是不同的,为了隔离这个不同,在GPU的上层添加了GPU驱动层,经过GPU处理后的数据会放到帧缓冲区中,最终显示到显示器上。

屏幕显示的原理-iOS开发角度

0002.png

我们都知道,iOS开发中,有个UIKit框架,而其中的UIView及其子类,我们开发者过程中基本会用这些来做我们的业务画面。UIView中有个属性,叫layer,对象类型为CALayer,也称之为纹理。layer有个id类型的属性contents,它指向内存中的一个成为backing storage的存储空间。往contents上赋值的时候就会将图片存储到这个backing storage中,这里虽然是id类型,但是如果传递其他类型进去会不显示,这里为什么使用id类型而不是明确的CGImageRef,也还是为了兼容,因为图像类型在Mac OS中是NSImage类型而在iOS上却是CGImageRef类型。

隐式动画是Core Animation框架自动完成的动画,不需要我们手动控制。支持隐式动画的CALayer属性叫Animatable Properties

显式动画允许你对一些属性做指定的自定义动画,或创建非线性动画,比如沿着任意一条曲线移动

业务上,你总是要绘制一些图形,自定义一些视图,一般我们使用CoreAnimation框架和CoreGraphics框架。 CoreAnimation框架以及API CoreGraphics框架以及API

离屏渲染: 上面介绍的渲染为当前屏幕渲染(On-Screen Rendering),也就是GPU的操作是在当前用于显示的屏幕缓冲区中进行的,但是还有一种渲染模式为离屏渲染,它发生在某些图层元素未预先合成之前不能直接在当前屏幕上绘制的情况下,这种情况下系统会新开一个缓冲区,在这里进行渲染操作。

v2-c448aaebe3cf19e37101ce16a799cdd2_r.png

离屏渲染一般发生在如下几种情况:

  • shouldRasterize = YES(开启光栅化)

  • edge antialiasing(抗锯齿)

  • group opacity(透明度)

  • circleRadius (圆角)

  • masks(遮罩)

  • shadows(阴影)

知道原理和用法之后,平时开发中应该注意什么呢?

1.自定义视图的时候,尽量别写drawRect方法,因为drawRect: 方法没有默认的实现,如果我们在UIView中实现了drawRect方法,就算它的函数体内部实际没有代码,系统也会为这个view申请一块内存区域,等待CoreGraphics可能的绘画操作。 只有当自定义控件中有特殊的图案时,才建议实现drawRect:进行绘制。

2.为了避免离屏缓存,图片的圆角不要直接用layer.cornerRadius设置,用CoreGraphics进行设置图片的圆角。

@interface UIImage(YYPClip)

*   (UIImage \*)drawCircleImage;

@end

@implementation UIImage(YYPClip)

*   (UIImage \*)drawCircleImage {
    CGFloat side = MIN(self.size.width, self.size.height);
    UIGraphicsBeginImageContextWithOptions(CGSizeMake(side, side), false, \[UIScreen mainScreen].scale);
    CGContextAddPath(UIGraphicsGetCurrentContext(),
    \[UIBezierPath bezierPathWithOvalInRect:CGRectMake(0, 0, side, side)].CGPath);
    CGContextClip(UIGraphicsGetCurrentContext());
    CGFloat X = -(self.size.width - side) / 2.f;
    CGFloat Y = -(self.size.height - side) / 2.f;
    \[self drawInRect:CGRectMake(X, Y, self.size.width, self.size.height)];
    CGContextDrawPath(UIGraphicsGetCurrentContext(), kCGPathFillStroke);
    UIImage \*output = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return output;
    }

@end

//在需要圆角时调用如下
dispatch\_async(dispatch\_get\_global\_queue(DISPATCH\_QUEUE\_PRIORITY\_DEFAULT, 0), ^{
UIImage \*img = \[\[UIImage imageNamed:@"order"] drawCircleImage];
dispatch\_async(dispatch\_get\_main\_queue(), ^{
view\.image = img;
});
});

3.避免离屏渲染

延伸阅读与参考链接

iOS-渲染系统工作原理介绍

绘制像素到屏幕上

关于iOS离屏渲染的深入研究