iOS技术iOS 面试iOS 底层

core animation pipeline

2015-10-05  本文已影响428人  纵横而乐

本文是苹果的开发视频里面学习core animation流水线的一些笔记

动画实现的硬件流程

首先,应用间接通过UIKit或者直接通过core animation构建view hierarchy,应用进程本身并不进行实际的绘制, 应用将view hierarchy提交给render server,实际绘制控制操作是由render 示

把绘制交给绘制服务器的好处是怎样体现的呢,我们来看下完整的animation从触发到流畅的动画展示出来的过程:

1 事件处理(比如触摸点击,以致需要引发界面修改) 

2 提交处理(commit transaction) 发生在应用之内的,在这步过程的最后会将view hierarchy编码好并发送给绘制服务器 

3 绘制服务器这时候需要解码这个view层级 

4 render server等到下一次的显示缓存空出之后才可以发起GPU绘制操作,然后,开始调用绘制的操作API (openGL 或者metal API)

5 在上述绘制资源准备好之后,GPU开始进行渲染,这个渲染过程最好在在下一次同步时间结束之前完成(16.67ms的刷新时间,以确保60帧的帧频)以便将frame buffer中的画面呈现给用户,并将缓存交给下次绘制。

在application和render server进行并行化处理之后,就可以在就可以实现60赫兹的绘制频率了

core animation pipeline

commit transaction

这个阶段是影响开发者最深的,它本身是由4个阶段组成的:

layout: 建立(set up)views,会调用重载的layoutSubviews方法,这里会发生view的创建,以及通过addSubview将layers添加进view层级中,将内容聚集起来,并做一些轻量的数据库查找(因为不能在这里停留太久,轻量级的操作可以是本地化字符串的查找以供应label的layout),这个过程通常是CPU密集型或者I/O密集型,关键词 layoutSubviews, view creation,addSubview,populate content, database lookups

display: 绘制views,这个阶段是如果drawRect有重载的话会通过drawRect绘制内容或者做字符串绘制,需要注意的是这个阶段实际上是CPU或者内存密集型的,由于这里是用core graphics渲染,所以通常用CGContext来渲染,也要避免在这里有过多的耗时  关键词 drawRect, string drawing

prepare commit:做一些额外的Core Animation工作,比如图像解码和图像转换,图像解码很容易理解,如果view层级中有图片,则会在这个阶段进行JPEG/PNG的解码,图像转换只是在存在有GPU不支持的图像时才会发生,典型的场景是对位图进行索引以避免特定的图像类型     关键词是image decoding,image conversion

commit:打包layers并提交给render server,这个过程是递归的,所以需要确保view树的平整以确保高效  关键词:package up layers and send to render server, recursive,expensive is layer tree is complex

how animations are produced

animation process

动画本身是一个三阶段的处理过程 ,两个在应用内,最后一个阶段在render server中,第一个阶段是建立动画,更新view层级 通常是animateWithDuration:animaitons: ;第二个阶段是准备及提交动画的阶段,即如上所述的4阶段的commit transaction,唯一不同的地方在于提交阶段,不仅需要提交view层级,也需要提交animation,因为我们需要将animation提交出去给render server以期以后的animation的更新不需要通过IPC与应用进行再次的沟通,这样更高效。

理解iOS8中新的视觉效果需要的rendering concepts

会有3个方面的内容:第一,基于tile的渲染,是GPU工作的方式,第二,渲染过程的概念,第三是渲染过程中mask的影响。

基于tile的渲染

screen is split into tiles of N*N pixels,  Each tile fits into the SoC cache, Geometry is split in tile buckets, Rasterization can begin after all geometry is submitted

在基于tile的渲染中,屏幕是划分成N*N像素的tiles,tile尺寸(通常是16*16像素或者32*32像素)是精心选择的,以便可以适配SoC(system on a chip)的缓存。且几何信息是划分进tile buckets中的,以电话图标为例

划分成三角tile的CALayer

在core animation中,CALayer是两个三角形,而且继续观察这两个三角形,它们还会被分成更多个细小的三角形,也就是多个tiles。现在GPU会怎么做呢,首先它会先开始划分出这些三角形,以便每个三角形都可以单独渲染。这样做的目的是为了在每个时间点都可以获取每个tile中各像素集齐之后色彩总体的几何信息,并决定哪些像素可见,并决定运行哪个pixel shader(注),确保每像素只运行每个pixel shader一次。但如果需要做blending的话,就不止一次了,那么会有overdraw的问题。

渲染过程

所以我们来看一下渲染的过程是怎样,假定应用已经通过core animation建立了一个view 层次,然后提交给了render server且core animation已经解码了,那么现在就需要将其渲染且这时候就需要用到OpenGL或者metal了,下面为方便,统一用metal这个说法。随后,它会产生OpenGL 命令缓存然后提交到GPU中,然后GPU会接收到这个缓存并开始工作。

这个阶段叫做tiler stage,GPU做的第一件事是通过vertex shader做顶点(vertex,多边形或三角锥等的顶点)处理。这里的设计思路是将所有顶点变换进屏幕空间以便可以进行第二个阶段即实际tiling stage,这个阶段我们为tile buckets进行实际的tile整个几何空间的工作,即将几何空间划分进不同的tile buckets所对应的tile区域。可以在instruments中可以找到OpenGL ES Analysis ->GPU driver 中有一项统计 tiler utilization。tiler阶段的输出是存放在称为parameter buffer中的,且下一个阶段并不会马上开始。

相反我们会等到所有几何位置都处理好之后或者parameter buffer满了之后,因为满了之后必须flush。这样的话,就会实际上执行它因为接下来需要开始vertex processing并获取pixel shader开始工作。

这个阶段即是pixel shader stage,它其实是称为render stage, 可以在instruments OpenGL ES  Analysis ->GPU driver 中有一项统计 render utilization,这个阶段的输出是写入到render buffer中的。

渲染过程

渲染实例展示

例子是关于masking的,假定view层次已经准备好了,command buffer也已经OK。在第一个过程中发生的第一件事情是将layer mask渲染到一个texture中,这个例子中是这个相机icon,第二个过程是将layer content渲染到一个texture中,这个情况下是这个蓝色素材。最后一个过程中调用组合过程将mask应用到content texture中,并最终合成为蓝色相机icon。

masking的渲染过程

UIVisualEffectView的UIBlurEffect效果

UIBlurEffect渲染过程

第一步是渲染将要被模糊的内容,第二步是捕获内容并将其缩小,缩小取决于硬件,在图中选取的特定size是为了确保读者可以辨认出来。接下来的两步是应用实际的blur算法,是分开来做的,先做水平blur,再做垂直方向的blur,虽然可以在一个步骤中完成,但它的计算量却要大很多。假设blur corner是11*11,则两步合成一步的结果是每个像素需要121个采样,而分成两步每步只需要每个像素11个采样。第五步是将模糊图像放大并tint。

看一下这个过程在性能上的表现是怎样

UIBlurEffect渲染过程的分析

可以看出整个UIBlurEffect的tiler和render阶段是完全流水线化的,tiler部件负责处理所有tiler任务,同时render部件负责所有的像素处理过程。上图中的VBlank是场消隐间隔时间

聚集于单一帧,可以看到第一个tiler过程是发生在第一个渲染过程之前的,因为需要计算整个几何位置的信息

第一个过程(包括tile和render)是content pass, 这个过程的时候取决于view层次,在这个场景中只有一个简单的图像,如果包含了UI会更耗时

第二个过程(包括第二个tile和第二个render)很快,常数消耗。

第三个过程也很快,因为只在一个很小的区域操作,第四个过程也是一样

第五个过程是放大并tint

可能你已经注意到了render 过程中的这些gap,它们实际上是GPU idle time,这里是在做GPU的上下文切换。这些gap实际时间也很短,每个大概是0.1到0.2毫秒。

由于在iPad2和3上面blur耗时太大,所以在这两个设备上的blur效果被禁用,而只是应用tint。

UIBlurEffect on various devices

总结来说,UIVisualEffectView的UIBlurEffect根据style的不同添加了多个离屏的过程,只有脏区域会重绘。所以如果你一大块blur区域,且在其后没有内容,实际上还好,因为只会应用blur一次。但这个效果很耗时,所以UI很容易受限于GPU,尽量保持view bounds尽可能地小,且需要对效果有清晰的把控。

UIVibrancyEffect

UIVibrancyEffect是应用在blur的顶部,且用于确保会明晰地显示出来,不会跟blur一起模糊的内容。其渲染过程是在blur的基础上添加了两个过程,可以在需要的时候参考2014 419session,此处不做过多的展开

UIVibrancyEffect

Rasterization

Rasterization

光栅化可以让CALayer构造成image以取代每次都要将CALayer合成到显示的做法。要点在这里

需要注意的是,如果光栅化的区域过大,如果超过了2.5倍屏幕size的缓存,则可能会不断引发离屏过程,缓存会一遍一遍被更新。光栅化的作用也是很明确的:

1 避免静态内容的昂贵重绘

2 避免复杂view层次的重绘

Group Opacity

Group Opacity

Tools

注1 tiled rendering

tiled rendering 是为了利用本地空间相干性,或者利用图形流水线中有限的硬件渲染资源,而在图像空间中使用规则的网格对计算机图形图像进行划分(或者tiling)的处理过程。在典型的tiled renderer中,几何位置必须先被转换进屏幕空间中,并分配屏幕空间tile。这需要一些存储空间来存放每个tile的几何位置信息的列表,在早期这些是由CPU完成的,但现代一般都有硬件加速。一旦几何位置分配给tiles,GPU会单独渲染每个tile到一个小型的芯片上的缓存中。这样做的好处是合成composition操作是时间和power代价都很低廉。一旦特定tile的渲染结束,整个tile最终的像素值都会一次性写进外部内存中。所以基于tile可以独立渲染的特性,多tile渲染引擎的并行像素处理架构可以很轻松地实现。

tile通常是16*16或者32*32像素

注2 pixel shader

shader是指计算机图形学里面用来做shading的程序,用来生成图像对应的各点的合适的(层次的)颜色,在现实的应用场景中,也可以用来做特殊效果的颜色生成或者视频后期处理。shader在图形硬件上计算渲染效果的方式上是有很大弹性的,大多数shader都是针对GPU的,虽然并没有强制要求。shading语言通常用于对可编程的GPU渲染流水线进行编程,用来取代只能基础的几何位置变换和像素生成的固定功能的流水线,因此可以使用shader完成自定义效果。所有像素,顶点或者texture的位置,色度,饱和度,亮度和对比度都可以使用定义在shader中的算法进行修改,而且可以通过外部变量和程序调用shader引进的texture修改。opengl es的shading language是opengl shading language(glsl),官方direct3d的sl是high level shader language也叫做HLSL。Cg是nvidia开发的第三方shading language,可以输出opengl和direct3d的shader。Apple也发行了自己的shading language称为metal shading language,它是metal framework的一部分。

上一篇下一篇

猜你喜欢

热点阅读