Flutter是Google打造的高性能、跨平台的UI框架,不仅支持Android、iOS,还支持Windows、Linux等操作系统。它可以给开发者提供简单、高效的方式来构建和部署跨平台、高性能移动应用;给用户提供漂亮、无平台区分的app体验。
图形库Skia是其跨平台的基石,本文会对其渲染原理做剖析,带大家了解flutter平台的渲染机制。
本文不会对跨端框架做对比,也不会涉及绘制的三棵树,流水线机制等知识(后续会单独介绍),还有一点最新的flutter2也对web支持进行了提速,其中web render中的html渲染方式底层并没有用Skia库。
移动端渲染
为了方便更清晰的知道Skia图形库在整个渲染过程所处的位置和作用,我大概将移动端的整体UI框架大致分成下面4个层次
1.UI库
无论Android还是ios都有自己的一套UI库(View/UIView),而Flutter则用dart语言实现一整套UI控件。Flutter先将控件树转成渲染树,然后交由Skia库绘制界面。
2.图形库
Skia图形库跟iOS平台的CoreAnimation等库功能类似,不仅提供了图形渲染功能,还提供文字绘制和图片显示功能。高级图形图像库将需要绘制的图形转成点、线、三角形等图元,再调用底层图形接口实现绘制。Skia本身也依赖了字体库,图片解析库等三方库
3.低级图形接口
OpenGL是使用最广的低级图形接口,兼容性最好,基本上支持市面上的所有GPU。Vulkan是Khronos Group推出的跨平台图形API(google近些年也在各业务线大力支持),除了iPhone的GPU,其他厂家的GPU基本都支持,旨在替代opengl,提供更细粒度的gpu控制。Metal是苹果推出的图形API,只支持自家GPU。
4.硬件层
目前的移动设备出于性能考虑,大部分图形都是通过GPU渲染,少数情况也会使用CPU渲染。
iPhone 在A11芯片以前使用power vr系列GPU,之后采用自研GPU。安卓手机大部分采用高通Adreno GPU或ARM mail GPU。GPU渲染完一帧图像后送FrameBuffer,最后在合适的时机展示在屏幕上。
Skia应用广泛并且跨平台,不仅用于Flutter和Android操作系统,还用于Google Chrome浏览器,同时支持windows、Mac、iOS操作系统。Skia由C++编写,代码开源,通过研究Skia有助于理解图形图像的绘制原理,为UI性能优化提供思路。
Flutter架构概览
首先我们来了解下Flutter的整体架构设计,Flutter被设计成一个可扩展,分层的系统。它包含了一系列依赖其下层的独立库。
-
框架(Framework):框架层是纯dart语言实现的一个响应式框架,开发者平常需要通过该层和Flutter系统交互。
-
引擎(Engine):引擎层绝大部分是用C++实现的,其为Flutter系统的核心。引擎提供了一系列Flutter核心API的底层实现,图形库,文字布局,文件等,是连接框架和系统(Andoird/iOS)的桥梁。
-
嵌入层(Embedder):嵌入层基本是由平台对应的语言实现的,例如:在Android上是由Java和C++实现;在iOS是由Objective-C/Objective-C++实现。嵌入层为Flutter系统提供了一个入口,Flutter系统通过该入口访问底层系统提供的服务,例如输入法,绘制surface等。
Skia框架探究
外部依赖
Skia依赖的三方库众多,包括字体解析库freeType,图片编解码库libjpeg-turbo、libpng、libgifcodec、libwebp和pdf文档处理库fpdfemb等。Skia支持多种软硬件平台,既支持ARM和x86指令集,也支持OpenGL、Metal和Vulkan低级图形接口。
Skia框架分层
Skia大致可分为:画布层,渲染设备层和封装适配层
画布层
画布层可以理解成提供给开发者在一个设备无关的画布,是一个高级抽象,提供了各种图形相关的api接口,类似于Android的Canvas。开发者可以在上面绘制各种图形,且不用关心硬件细节。
图形:drawPoints,drawRect。绘制文字:drawText。显示图片:drawBitmap。
渲染设备层
渲染设备层负责画布层的硬件实现,Skia将设备封装成下面三个类:
1.SKBitmapDevice
CPU渲染模式绘图,用于没有显卡或者显卡驱动的设备。此模式下,最后会将需要绘制的图形转成位图数据(RGB)写入指定内存,故称为BitmapDevice。Skia从性能角度考虑写内存操作,采用的SIMD指令集来加速内存操作。通过AVX或者NEON指令集实现。
2.SKGPUDevice
GPU渲染方式绘图。目前大部分移动设备和个人电脑都有GPU,GPU比CPU的运算单元多,并行计算能力强,通过GPU绘图可降低CPU占用,性能更好。Flutter、最新版本的chrome和android系统默认设置为GPU渲染模式。
3.SKPDFDevice
选用此设备时,渲染结果不是输出到显示器的画面,而是输出为pdf文件。
封装适配层
Skia为了屏蔽不同依赖库的接口差异,对依赖库进行了封装和适配。例如基于图片编解码库libjpeg-turbo、libpng、libwebp 封装了类SKJpegCodec、SKPngCodec、SKWebpCodec。基于底层图形库OpenGL、Metal、Vulkan封装了GrGLOpsRenderPass, GrMTOpsRenderPass, GrVKOpsRenderPass三个类。基于苹果平台CoreText字体库和开源字体FreeType封装了SkScalerContext_Mac和SkScalerContext_FreeType。
图形绘制原理
Skia支持绘制的图形众多,包括圆形、贝塞尔曲线等。下文重点分析图形的CPU和GPU两种渲染模式的原理。
CPU模式渲染
其中,各种复杂的图形可拆解成基本矢量图形的组合
提性能
目前32或者64位的处理中,普通的指令一次最多写入32位 或者64位数据,一个带alpha通道的点占4byte,也就是一次只能绘制1-2个点,效率较低。Skia为了提升性能,采用SIMD指令集来加速内存操作。x86下调用SSE、AVX等指令集实现内存赋值,SSE一次操作128位,AVX/AVX2一次操作256位数据,ARM下利用NEON指令集一次操作128位数据。
GPU渲染
渲染流程如下:
调用底层api的时候因平台不同选择不同的api,OpenGL是目前使用最广泛的跨平台图形接口,跨平台特性好。
Metal 是一个和 OpenGL ES 类似的面向底层的图形编程接口,可以直接操作GPU;支持iOS和OS X,提供图形渲染和通用计算能力。
Vulkan是Opengl的重写版提供高性能和低CPU负担,天然支持多线程,能较好发挥多核CPU的性能。
Skia在大部分平台采用OpenGL,少部分平台调用Metal和vulkan。Skia对以上接口做了封装,屏蔽了不同底层图形接口的差异。分别为GrGLOpsRenderPass,GrMTOpsRenderPass,GrVKOpsRenderPass。
图片绘制原理
Skia位图绘制原理
Skia提供了showBitmap函数可直接显示位图。位图渲染模式跟矢量图形类似,分为CPU渲染和GPU渲染。位图的CPU渲染跟实心矩形的渲染原理类似,通过SIMD指令集将位图内存一行一行拷贝到指定内存缓存中。GPU渲染模式通过调用OpenGL、Metal、vulkan的纹理贴图函数实现。
Skia压缩格式图片绘制原理
位图由于占用空间大,使用频率低,大部分情况下使用压缩格式图片。Skia支持的压缩格式图片如下:
格式 | 优点 | 缺点 | 场景 | 依赖解码库 |
---|---|---|---|---|
gif | 文件小,支持动画、透明,无兼容性问题 | 透明度只有1位,有白边和锯齿 | 简单的动图 | libgifcodec |
jpg | 支持24位真彩色,压缩率高 | 有损压缩,不支持透明通道 | 色彩丰富的图片 | libjpeg-turbo |
png | 无损压缩,支持透明,简单图片尺寸小 | 不支持动画,压缩率低 | logo/icon/透明图 | libpng |
webp | 比jpeg压缩率更高,支持有损和无损压缩,支持动画、透明通道 | 谷歌自研格式,部分平台不支持。Android 4.3后支持更好 | 支持有损和无损压缩格式,支持动画 | libwebp |
SkCanvas c(dst);
SkBitmap src;
SkImageDecoder::DecodeFile(“test.jpg”, &src);
// 图片解码
c.drawBitmap(src, 0, 0, NULL); //图片显示
显示流程如下图所示:
读取文件后,先通过文件头判断图片类型,然后通过相应的图片库解码成位图图像后,再通过CPU或者GPU渲染。
字体绘制原理
字体无法直接显示在屏幕上,需要解析成位图或者矢量图才能绘制。Skia的字体解析实现根据平台差异有所不同,mac和iOS平台调用coreText库,安卓平台调用开源库freeType。
FreeType是一个用C语言实现的,免费的高质量可移植字体引擎,支持点阵字体PCF、BDF和矢量字体TrueType、freeType等字体。
Skia点阵字体绘制原理
Skia的点阵字体显示代码:
SkFont font;
font.setEdging(SkFont::Edging::kAlias);
font.setSize(40);
const char text[] = "Click this link!";
size_t byteLength = strlen(static_cast<const char*>(text));
canvas->drawSimpleText(text, byteLength, SkTextEncoding::kUTF8, x, y, font, SkPaint());
Skia支持的点阵字体有PCF、BDF格式。点阵存储的是多张位图,常见的有1616,24 24,32*32等尺寸,解码和显示简单,缺点是放大后有锯齿。
文字绘制流程如下:
点阵字体最后解析成了位图,然后根据平台不同选用CPU或者GPU渲染出来。Skia为了提高字体显示速度,对字体的解析结果做了内存缓存。
矢量字体绘制原理
矢量字体主要通过贝塞尔曲线描述字体,存储空间小,但渲染复杂,还需要导入字体库文件。Skia支持的矢量字体有tff(true type font)和otf(open true type)格式。前者采用二次贝塞尔曲线表示,后者采用三次贝塞尔曲线表示。Skia中矢量文字绘制代码如下:
SkPaint p;p.setStyle(SkPaint::kStroke_Style);
// 设置样式
p.setStrokeWidth(10);
// 设置宽度
p.setARGB(0xff, 0xbb, 0x00, 0x00);
// 设置颜色
sk_sp<SkTypeface> ttf = MakeResourceAsTypeface("fonts/Stroking.ttf");
SkFont font(ttf, 100);
if (ttf)
{
SkFont font(ttf, 100); canvas->drawString("○◉ ⁻₋⁺₊", 10, 100, font, p);
}
绘制流程如下:
总结
Skia是一个功能强大的跨平台图形库,能绘制矩形、圆形、贝塞尔曲线等矢量图,绘制点阵字体和矢量字体,显示jpeg、png、gif、webp等图片,同时性能好,从算法和硬件两个层面进行了优化。Skia支持多种软硬件平台,除了Android、chrome、Flutter等产品直接将其作为图形引擎,也支持iOS、windows等操作系统。Skia功能较多,还支持lottie动画,图像特效,还引入了中间语言SKGL,限于篇幅,这里不再展开。
微信首发:欢迎各位coder,关注 江湖修行 公众号。
参考文档:
1.iOS高性能绘图:medium.com/@almalehdev…
2.CoreAnimation:developer.apple.com/library/arc…
3.skia编译:skia.org/user/build
4.Skia技术指南:docs.google.com/document/d/…
6.Skia源码:skia.googlesource.com/skia.git
7.Skia 百科:zh.wikipedia.org/zh-cn/Skia_…
8.GPU渲染流水线:zhuanlan.zhihu.com/p/61949898
9.Vukan介绍:www.khronos.org/assets/uplo…
10.ARM Mali GPU介绍:developer.arm.com/solutions/g…