Chromium 硬件加速合成
- 一个网页通常可以包?很多层,例如有透明效果的节点,
Canvas
节点等,这些节点都可以是页面中的一层,这些层的内容最后组成一个可视化的网页内容; - 在没有硬件加速的情况下,浏览器通常是依赖于
CPU
来渲染生成网页的内容,大致的做法是遍历这些层,然后按照顺序把这些层的内容依次绘制在一个内部存储空间上,最后把这个内部表示显示出来,这种做法就是软件渲染; WebKit
中对于渲染所做的一些基础设施,包括Render
树和RenderLayer
树,对于每一个RenderLayer
,可以为其单独创建一块内部存储,这些存储会被用来保存该层中的内容,浏览器最后会把这些所有的层通过GPU
合成起来,形成最终网页渲染的内容,这就是硬件加速合成;
Canvas2D及其实现
CanvasRenderingContext2D 介绍
CanvasRenderingContext2D
是2d
图形绘制的上下文对象,其提供了用于绘制2d
图形的 API;该对象由JavaScript
代码创建后,JavaScript
便可以调用它的 API 在画布上绘制图形;这些API
主要的作用就是在画布上绘制点,线,矩形,弧形,图片等等,除此之外,还提供了这些绘制的样式和合成效果等;
WebKit 和 Chromium 的支持
W3C
定义了2D context
标准的草案, 这些接口保存在IDL
文件中;WebKit
根据这IDL
来直接生成相关的C++
类的代码,这些类包括 CanvasRenderingContext2D,CanvasPattern, CanvasGradient, ImageData, TextMetrics
等, 它们 和标准中的接口一一对应;Canvas2D
上下文类的具体绘制动作由实现平台决定, 其由软件或者硬件来完成取决于移植;WebKi
端:
* HTMLCanvasElement
:DOM
中的对应着HTML5
的canvas
元素,该类包?有关为2D
或者3D context
服务的相关接口,主要的作用创建JS
使用的上下文对象,绘图的平台无关的GraphicsContext
对象,后端存储的buffer
;
* GraphicsContext
:WebKit
的平台无关的上下文类,用于绘制工作,具体调用平台相关的上下文类来实现绘制;
* PlatformGraphicsContext
:平台绘制上下文类,不同的平台有不同的实现,在chromium
中是PlatformContextSkia
;
* ImageBuffer
:WebKit
平台无关后端存储类,不同平台会定义不同的结构,在chromium
中会使用SkCanvas
;
* RenderHTMLCanvas
:RenderObject
的子类,为canvas
而设计的;Chromium
端:
* PlatformContextSkia
:Chromium
中的PlatformGraphicsContext
类;
* SkCanvas
:skia
画布,包?所有的绘制状态,使用SkDevice
来绘制图形;
* SkDevice
:设备类,包?一个SkBitmap
作为后端,利用光栅扫描算法来生成像素值保存于后端存储中,用于软件绘制方案;
* SkGpuDevice
:设备类,包?一个绘制的目标对象,通过GrContext
来绘制,其利用硬件加速的GL
库来绘制2D
图形;
* GrContext
:GPU
绘制的上下文类,包?一个平台相关的3D
上下文成员;
* Canvas2DLayerChromium
:LayerChromium
的子类,包?一个硬件加速的Canvas2D
层;Chromium
中的Canvas2D
的绘制操作的实现都是由图形库skia
来完成,这里包括软件和硬件加速实现,chromium
所要做的就是把WebKit
中的调用交给skia
来执行并和自己的绘制模型和硬件加速机制集成起来;
Canvas 2D的软件实现
//例子
<canvas id='mycanvas' width=80 height=100></canvas>
<script type='text/javascript'>
var canvas = docuemnt.getElementById('mycanvas');
var ctx = canvas.getContenxt('2d');
ctx.fillStyle = '#FF0000';
ctx.fillRect(0,0,80,100);
</script>
- 首先,当执行到
JS
代码中的canvas.getContext
时,WebKit
通过V8 JS
绑定会调用HTMLCanvasElement.getContext
;
* 该函数根据传入的参数来决定创建2D
或者3D
的上下文对象;
* 在这里, CanvasRenderingContext2D
对象会被创建;
* 此时其他有关的对象例如ImageBuffer,GraphicsContext
等不会被创建,直到后面使用到时才会被创建,这是WebKit
的做事原则; - 其次,当设置
fillStyle
属性时,WebKit
同样通过V8 JS
绑定调用CanvasRenderingContext2D.setFillStyle
,在这种情形下,2D
上下文对象会开始创建相关对象;
- 当执行
JS
的fillRect
时,CanvasRenderingContext2D
调用GraphicsContext
来完成绘制; - 这两个类是
WebKit
的基础类,GraphicsContext
需要有不同移植来具体实现绘制工作,在chromium
中,这就是PlatformContextSkia
; - 该类是一个转接口,调用
skia
来绘制,SkCanvas
根据之前设定的样式,由SkDevice
利用光栅扫描法来计算生成相应的像素值,结果保存在一个SkBitmap
中;
- 最后,当
fillRect
操作调用完成之后,会安排一个Invalidate
相关区域的命令;
* 而后,当该命令被执行了,WebKit
会遍历RenderLayer
依次绘制RenderObject
的内容,当绘制Canvas
元素时,会把之前Canvas
绘制在SkBitmap
的内容绘制到网页的 Bitmap
;
Canvas 2D的硬件加速实现
- 首先,硬件加速需要创建更多的对象和设施,主要有两点
* 会为Canvas
元素创建一个新的RenderLayer
及其相应GraphicsLayer,Canvas2DLayerChromium,CCLayerImpl
等;
* 因为利用GL
来渲染,所以为skia
的SkCanvas
创建一个SkGpuDevice, GrTexture, GrContext
来使用GL
绘制2D
图形,同时,跟合成器一样, 也会创建3D
的上下文对象- WebGraphicsContext3DCommandBufferImpl
, 将skia
的GrContext, GrTexture
等对gl
调用转发给GPU
进程; - 其次,当调用
fillRect
时,canvas
的内容由SkCanvas
调用SkGpuDevice
将其绘制在Texture
,当这些GL
的操作都通过WebGraphicsContext3DCommandBufferImpl
交给 GPU
进程来完成绘制,会请求一个更新某个区域的任务; - 最后,更新请求会调度合成操作,其首先调用
Canvas2DLayerImpl::paintContentsIfDirty
绘制自己,然后将Canvas
的Texture
合成起来生成网页内容;
WebGL及其实现
- 在
canvas
中同样也可以绘制3D
图形,也就是Canvas3D
或者称为WebGL
;是一个被HTML Canvas
元素创建的用来3D
渲染的上下文接口; 2D
和3D
是互斥的,不能同时在同一个canvas
中操作它们;
概述
//例子
<canvas id='mywebGl' width=100 height=100></canvas>
<script type='text/javascript'>
var canvas = document.getElementById('mywebGl');
var gl = canvas.getContext('experimental-webgl');
gl.clearColor(1.0,0.0,0.0,1.0);
gl.clear(gl.COLOR_BUFFER_BIT);
</script>
- 与
Canvas 2D
不同的是,WebGL
没有软件渲染和硬件加速两种模式, 它仅在硬件加速开启的情况下才能运行,因为它依赖于3D
的图形库;
WebGL 的渲染过程
- 当
JavaScript
的代码通过HTML Canvas
对象创建3D
上下文时,WebGL
模块便为其创建一个绘制3D
图形用的上下文对象,该对象在GPU
进程中会有一个实际的OpenGL ES
的上下文对象对应;同时,DrawingBuffer
对象也会被创建; DrawingBuffer
会同时创建了一个帧缓冲区对象,用于存储该WebGL
绘制的内容,同时获取用于合成的纹理对象,WebGL
模块会获取这些对象的ID
信息;JavaScript
的调用上下文对象的绘制图形代码被V8
解析后,利用V8
的绑定机制,会调用相应的WebGLRenderingContext
对象的Callback函数,这些函数把它们转换成内部的
Commands, 通过
IPC机制传给
GPU进程,
GPU进程在之前创建好的上下文对象,完成实际的绘制工作;在这过程中,有两点值得关注, * 很多命令需要同步,这会有很大的开销; * 很多情况下,
JavaScript需要操作各种图片资源,所以需要将它们共享给
GPU进程,同时将它们上载到
GPU`的内存,因而开销比较大;GPU
进程执行renderer
进程发送过来的命令,调用GL
库函数把计算结果内容更新存储到帧缓冲区中,前面这四步在解析JavaScript
时,同时执行;之后,它会调度一个任务,请求更新一个矩形区域;- 从这步开始,合成器发起合成,当由任何变化或者更新网页的请求时都会触发它;合成器要求每层去更新自己的发生变化的部分,这需要尽可能的快,以避免大幅地影响一次网页的绘制工作;
WebGL
此时会准备绘制内容或者 更新内容到帧缓冲区中,同时把内容拷贝到纹理对象中去,以备合成器所使用; - 因为
WebGL
的纹理对象是在这些GL
的上下文对象中共享的,WebGL
模块切换到合成器的上下文对象环境中,将它的纹理对象根据变换绘制到该环境中的帧缓冲区中去,完成对该层的合成; - 当所有层次的绘制完成后,最后一步就是交换前后端的缓冲区,来显示合成完的内容;