chromium为了优化渲染效率做了很多优化,这些不仅可以用在web渲染,也可以用于一些native图像界面的渲染;这些优化包括:
分成paint和compositing
paint分为recording和光栅化
跨线程渲染,避免和网络请求影响帧率,同时paint和光栅化可以并行,充分利用了CPU多核特性
Tiling
跨进程渲染,提高稳定性
先渲染0.5比例大小的内容,减少texture上传的大小加快渲染速度,同时离屏幕渲染正常比例的内容,提高滑动和缩放体验
特点策略决定cpu/gpu渲染
delegated renderer/direct renderer,将渲染放到最后,减少中间texture
gpu设计用来处理大量图形处理,效率比cpu更快,可能还更省电
有些图像比如视频,一些3d图像本身就在gpu内存,ui框架使用硬件渲染可以避免这些gpu内存写回到主存
cpu和gpu可以并行处理,比如cpu可以做光珊化的同时,gpu完成绘制到屏幕,形成一个流水线,效率更快
impl线程和main线程(webkit线程)
main线程负责网页渲染和执行JS,包括css样式计算,会同步等待主文档的网络请求,依赖于网页内容复杂程度,可能比较耗时,chromium通过impl/main线程机制,把网页的绘制和上面的操作分离,实现动画,滑动的流畅性,实现方式是:在main线程保存了一颗Layer树,Layer是一个有内容和大小的绘图元数据,main线程会把Layer绘制到一个SkPicture上去,没有做实际上的光栅化操作,有些内容比如canvas需要保留绘制状态的就会光栅化到一个texture;impl线程会通过一个commit操作,把main线程的这颗Layer树同步到impl线程,这里是同步操作,但是耗时比较小,同时把layer相关的texture上传到gpu,impl线程就可以直接把内容绘制到屏幕;
commit的步骤
main线程发现网页有变动,异步通知impl我需要一次commit,impl线程收到通知后,根据当前的状态,比如下一次vsync是否已经到了,告诉main线程可以开始beginframe和commit了,beginframe就是把网页内容paint到layer,这里一般是记录绘制命令到SkPicture,有部分需要光栅化比如Canvas,commit是main通知impl来做的,main线程阻塞直到impl线程完成两个Layer树的同步;commit还负责把impl线程用户滑动的offset同步给main线程的layer树
上述两个线程需要一个调度器和一个状态机管理;
在多进程架构中,impl/main线程都运行在Render进程
什么是Compositing,网页内容分成很多层,分层的好处一个是避免重复渲染,另一个就是利用gpu加速compositing;
在chromium里面渲染分成两个步骤,paint和compositing,paint就是把绘制命令记录到SkPicutre然后光栅化成像素数据,compositing就是把这些数据叠在一起显示到屏幕;
相比于绘制,gpu的compositing是很轻量的操作;
chromium compositor的作用就是做compositing操作,还有管理每一帧的生命周期
PicturePile
一般一个Layer并不会对应一个texture,这样会导致Layer有部分更新时,整个texture都要重新光栅化,chromium使用tiling技术,把一个layer分成一些方块,按照方块为单位进行光栅化和分配texture;像一些video/webgl因为内容已经在gpu内存并且一般都会全部刷新的不需要分块
分为软件光栅化和gpu光栅化:
软件光栅化,通过skia画到共享内存的bitmap中,再上传到gpu texture,需要单独开一个raster线程,因为光栅化比较耗时;对于频繁刷洗的页面效率比较低
光栅化时需要的图片也需要单独线程上传;
硬件光栅化,通过skia的ganesh,直接通过gl命令画到texture;有点是节约了内存,对于频繁更新的页面渲染效率更快,但是不适合渲染文字,skia绘制文字的算法不是和gpu的并行计算架构;
gpu光栅化有个优化,就是可以直接光栅化到buffer上,而不是texture,节省了一个texture的内存,缺点是因为没有中间存储的texture,每一帧都要全部重绘(flutter就是这么干的)
如何选择光栅化方法, 一般是文字比较多用cpu,变动频繁用gpu,代码中skia有一个api控制:suitableForGpuRasterization
Render端的impl线程不会将内容渲染到texture,而是把绘制信息(Compositor Frame)传递给Browser端,Browser端有自己的UI和Layer树结构,Render传递过来的绘制信息会作为其中一个Layer和BrowserUI一起渲染,这样减少了一个中间texture,提高效率
Layer Tree创建 => Output Surface创建 => 网页绘制 => Layer Tree与Pending Layer Tree同步 => Tile光栅化 => Pending Layer Tree激活为Active Layer Tree
Dom->RenderObject->RenderLayer->GraphicsLayer->Layer Tree->PendingLayerTree->ActiveLayerTree
Layer Tree与WebKit中的Graphics Layer Tree在结构上是完全同步的,并且这个同步过程是由WebKit控制的
没当每当WebKit修改了Render Object时,都会请求执行一次commit操作,scheduler会判断上一次commit由没由执行,没有的化就忽略这一次,避免commit操作请求得太过频繁,commit的任务就是同步树结构;同步完成后就要对PendingLayer分块,再对分块光栅化
PendingLayerTree光栅化完成后和ActivieLayerTree交换
设备不支持cpu,使用软件渲染
线程化渲染效率更低?使用单线程渲染,impl/main合并为一个线程,去掉recording,把内容直接绘制到缓冲中
impl线程光栅化和绘制到屏幕,使用的是标准的GLES命令,他是通过宏定义的方式把GL命令替换成了和GPU进程间的通信,通过commandbuffer机制把gl命令发给GPU命令发送给GPU进程执行;
commandbuffer是一块共享内存,存储gl命令和参数,以及关联的bitmap,顶点数组;commandbuffer有一个配套的mailbox和syncpoint机制用来共享和同步不同commandbuffer之间的texture使用