共计 6628 个字符,预计需要花费 17 分钟才能阅读完成。
本文来自 OPPO 互联网技术团队,转载请注名作者。同时欢送关注咱们的公众号:OPPO_tech,与你分享 OPPO 前沿互联网技术及流动。
Flutter 是目前风行的高性能跨平台 UI 框架,图形库 skia 是其跨平台的基石。本文将深入分析 skia 的图形、字体、图片的渲染原理,如何开掘硬件个性,为 UI 性能优化提供思路。
1. 引言
Flutter 是目前十分风行的跨平台 UI 开发框架,不仅反对 Android、iOS,还反对 Windows、Linux 等操作系统。Flutter 的性能十分高,领有 120fps 的刷新率。那么 flutter 是如何实现在不同平台上高性能绘制图形图像的呢?首先咱们剖析下 Flutter App 和原生 Android App、原生 iOS App 的 UI 绘制原理。
挪动 App 的整体 UI 框架大抵分成上面 4 个档次:
1)UI 库
跟 Android、iOS 原生开发相似,Flutter 用 dart 语言实现一整套 UI 控件。Flutter 先将控件树转成渲染树,而后交由 skia 库绘制界面。
2)图形库
Skia 图形库跟 iOS 平台的 CoreAnimation 等库性能相似,不仅提供了图形渲染性能,还提供文字绘制和图片显示性能。高级图形图像库将须要绘制的图形转成点、线、三角形等图元,再调用底层图形接口实现绘制。
3)低级图形接口
OpenGL 是应用最广的低级图形接口,兼容性最好,基本上反对市面上的所有 GPU。Vulkan 是最近几年新推出的图形 API,除了 iPhone 的 GPU,其余厂家的 GPU 根本都反对。Metal 是苹果新推出的图形 API,只反对自家 GPU。
4)硬件设施层
目前的挪动设施出于性能思考,大部分图形都是通过 GPU 渲染,多数状况也会应用 CPU 渲染,后文会介绍 skia 应用 CPU 和 GPU 渲染的具体场景。
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 性能优化提供思路。
2. skia 框架剖析
2.1 Skia 内部组件依赖
Skia 依赖的第三方库泛滥,包含字体解析库 freeType, 图片编解码库 libjpeg-turbo、libpng、libgifocode、libwebp 和 pdf 文档解决库 fpdfemb 等。Skia 反对多种软硬件平台,既反对 ARM 和 x86 指令集,也反对 OpenGL、Metal 和 Vulkan 低级图形接口。
2.2 Skia 档次剖析
Skia 在结构上大抵分成三层:画布层,渲染设施层和封装适配层。
2.2.1 画布层
画布层能够了解成提供给开发者在一个设施无关的画布,能够在下面绘制各种图形,且不必关怀硬件细节,性能如下:
类别 | 函数名 | 含意 |
---|---|---|
画图形 | drawPoints | 画点 |
画图形 | drawRect | 画矩形 |
画图形 | drawVertices | 画多边形 |
画图形 | drawRoundRect | 画圆角矩形 |
画图形 | drawArc | 画圆弧 |
画图形 | drawOval | 画椭圆 |
画图形 | drawPath | 画矢量图 |
绘制文字 | drawText | 显示文字 |
显示图片 | drawBitmap | 显示位图 |
2.2.2 渲染设施层
渲染设施层负责画布层的硬件实现,skia 将设施封装成上面三个类:
1)SKBitmapDevice
CPU 渲染模式绘图,用于没有显卡或者显卡驱动的设施。此模式下,最初会将须要绘制的图形转成位图数据(RGB)写入指定内存,故称为 BitmapDevice。写内存操作通过 AVX 或者 NEON 指令集实现。
2)SKGPUDevice
GPU 渲染形式绘图。目前大部分挪动设施和个人电脑都有 GPU,GPU 比 CPU 的运算单元多,并行计算能力强,通过 GPU 绘图可升高 CPU 占用,性能更好。Flutter、最新版本的 chrome 和 android 零碎默认设置为 GPU 渲染模式。Chrome 中的配置截图如下,可看到默认采纳 GPU 渲染。
3)SKPDFDevice
选用此设施时,渲染后果不是输入到显示器的画面,而是输入为 pdf 文件。
能够通过 skia 官网在线体验不同设施的渲染后果:https://fiddle.skia.org/c/@sh…
2.2.3 封装适配层
Skia 为了屏蔽不同依赖库的接口差别,对依赖库进行了封装和适配。例如基于图片编解码库 libjpeg-turbo、libpng、libwebp 封装了类 SKJpegCodec、SKPngCodec、SKWebpCodec。基于底层图形库 OpenGL、Metal、Vulkan 封装了 GrGLOpsRenderPass,GrMTOpsRenderPass, GrVKOpsRenderPass 三个类。基于苹果平台 CoreText 字体库和开源字体 FreeType 封装了类 SkScalerContext_Mac 和 SkScalerContext_FreeType。
Skia 的内部依赖和层级构造解说结束,上面重点解说 skia 的图形、文字和图片的绘制原理。
3. 图形绘制原理
Skia 反对绘制的图形泛滥,包含圆形、椭圆、矩形、贝塞尔曲线等。下文重点剖析图形的 CPU 和 GPU 两种渲染模式的原理。
3.1 图形 CPU 渲染原理
曲线的绘制波及的数学知识较多,本文不再开展,上面以绘制实心矩形为例阐明原理进行分析。
1)调用画布的绘图 API
应用层调用画布 SKCanvas 的 drawRect 函数,传入左上角和右下角顶点坐标。
2)选用对应的设施的绘图 API
因为抉择的是 CPU 渲染模式,故调用 SKBitmapDevice 的矩形绘图函数 drawRect 实现。
3)图形示意
所有的图形可分解成上面几种根本矢量图形的组合,矩形可示意成四条直线的组合。
曲线类型 | 参数 | 用处 |
---|---|---|
直线(一次贝塞尔曲线) | 终点坐标,起点坐标 | 可示意绘制三角形、四边形等多边形 |
圆锥曲线 | 终点坐标,起点坐标,椭圆参数 | 示意椭圆、圆弧、圆形 |
二次贝塞尔曲线 | 三个控制点 | 示意 TrueType 字体、抛物线等曲线 |
三次贝塞尔曲线 | 四个控制点 | 示意 OpenType 字体和其余曲线 |
4)绘制算法实现
矢量图转成位图的过程称为光栅化。带填充的矩形光栅化过程比较简单,能够分解成绘制多条横线。
5)横线线绘制算法
每条横向的画法通过 SKBlitter:: blitH 实现。接口定义如下:
virtual void blitH(int x, int y, int width);
性能:从坐标 x,y 开始,间断写入宽度为 width 的 RGB 色彩值。
6)内存中写色彩数据
通过追踪代码,发现上文中的横线绘制函数调用的是 memsetT 函数(内存赋值)实现。参数如下:
static void memsetT(T buffer[], T value, int count)
目前 x86 和 ARM 处理器是 32 或者 64 位,一般的指令一次最多写入 32 位 或者 64 位数据,一个带通明通道的点通常占 4 个字节,相当于一次只能绘制 1 到 2 个点,效率比拟低。Skia 从性能角度思考,采纳的 SIMD 指令集来减速内存操作。
在 X86 平台,调用 SSE、AVX、AVX2 等指令集实现内存赋值,SSE 反对一次操作 128 位操作,AVX/AVX2 反对一次操作 256 位数据,ARM 处理器的 NEON 指令集反对一次操作 128 位数据。
3.2 图形 GPU 渲染原理
GPU 的并行运算能力强,目前大部分挪动设施都采纳的是 GPU 渲染。
skia GPU 渲染流程如下:
1)发动绘图,先调用 SKCanvas 的绘图函数 drawRect,传入左上角和右下角顶点坐标。
2)调用 GPU 设施的绘图函数 SKGPUDevice::drawRect。
3)采纳命令模式,将 GPU 绘图操作封装成类 GrOpsTask 的实例。
4)依据软硬件平台的不同选用不同的底层 API。
OpenGL(Open Graphics Library”)是目前应用最宽泛的跨平台图形变成接口,跨平台个性好,大部分操作系统和 GPU。Skia 在大部分平台采纳 OpenGL 实现 GPU 绘图,少部分平台调用 Metal 和 vulkan。
Metal 是苹果公司 2014 年推出的和 OpenGL 相似的面向底层的图形编程接口,只反对 iOS。对软硬件有要求,要求硬件苹果 A7 及当前,操作系统 iOS 10 及以上。Metal 实践上性能比 OpenGL 性能强,故新设施中开启 Metal 可进步性能。例如 Flutter 中已启用了 metal 反对,详情参考 https://github.com/flutter/fl…。
Vulkan 是新一代跨平台的 2D 和 3D 绘图利用程序接口(API), 旨在取代 OpenGL,实践上性能强于 OpenGL。自 Android 7.0 开发者预览版开始,Google 便在零碎平台中增加了对 Vulkan 的 API 反对。目前 Skia 的 GPU 渲染模式已用 vulkan 实现了一套,但存在一些 bug。具体参考 https://skia.org/user/special…。
Skia 对上述三种图形接口进行了封装,屏蔽了不同底层图形 API 接口的差别。OpenGL 接口的封为 GrGLOpsRenderPass,Metal 的封装层为 GrMTOpsRenderPass,Vuklan 的封装层为 GrVKOpsRenderPass。
5)通过 GPU 实现残余绘图操作。
上面以 OpenGL 为例阐明。接口封装层调用 OpenGL glDrawArray 绘制矩形,之后在渲染流水线中实现顶点变换、光栅化和着色,最初送帧缓冲显示。渲染流水线如下图所示:
Metal、vulkan 的渲染流水线这里不再开展。
4. 字体绘制原理
字体无奈间接显示在屏幕上,须要解析成位图或者矢量图能力绘制。Skia 的字体解析实现跟进平台差别有所不同,mac 和 iOS 平台调用 coreText 库, 安卓平台调用开源库 freeType。
FreeType 是一个用 C 语言实现的,收费的高质量可移植字体引擎,反对点阵字体 PCF、BDF 和矢量字体 TrueType、freeType 等字体。
4.1 skia 点阵字体绘制原理
Skia 反对的点阵字体有 PCF、BDF 格局。点阵存储的是多张位图,常见的有 1616,2424,32*32 等尺寸,解码和显示简略,毛病是放大后有锯齿。
1) 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());
文字绘制流程如下:
点阵字体最初解析成了位图,而后依据平台不同选用 CPU 或者 GPU 渲染进去。Skia 为了进步字体显示速度,对字体的解析后果做了内存缓存。
4.2 矢量字体绘制原理
矢量字体次要通过贝塞尔曲线形容字体,存储空间小,但渲染简单,还须要导入字体库文件。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);
}
绘制流程如下:
矢量字体的绘制流程跟点阵字体大部分一样,不同之处在于解析后果为贝塞尔曲线。贝塞尔曲线的渲染算法略微简单,参考文章 https://www3.cs.stonybrook.ed…
5. 图片绘制原理
5.1 Skia 位图绘制原理
skia 提供了 showBitmap 函数可间接显示位图。位图渲染模式跟矢量图形相似,分为 CPU 渲染和 GPU 渲染。位图的 CPU 渲染跟实心矩形的渲染原理相似,通过 SIMD 指令集将位图内存一行一行拷贝到指定内存缓存中。GPU 渲染模式通过调用 OpenGL、Metal、vulkan 的纹理贴图函数实现。
5.2 Skia 压缩格局图片绘制原理
位图因为占用空间大,应用频率低,大部分状况下应用压缩格局图片。Skia 反对的压缩格局图片如下:
格局 | 长处 | 毛病 | 场景 | 依赖解码库 |
---|---|---|---|---|
gif | 文件小,反对动画、通明,无兼容性问题 | 只反对种颜色,且透明度只有 1 位,有白边和锯齿 | 简略的动图 | libgifcodec |
jpg | 反对位真彩色,压缩率高 | 有损压缩,不反对通明通道 | 色调丰盛的图片 | libjpeg-turbo |
png | 无损压缩,反对通明,简略图片尺寸小 | 不反对动画,压缩率低 | logo/icon/ 透明图 | libpng |
webp | 比 jpeg 压缩率更高,反对有损和无损压缩,反对动画、通明通道 | 谷歌自研格局,局部平台不反对。 | 反对有损和无损压缩格局,反对动画 | libwebp |
压缩格局图片应用代码如下:
SkCanvas c(dst);
SkBitmap src;
SkImageDecoder::DecodeFile(“test.jpg”, &src);// 图片解码
c.drawBitmap(src, 0, 0, NULL); // 图片显示
显示流程如下图所示:
读取文件后,先通过文件头判断图片类型,而后送相应的图片库解码成位图图像后,再通过 CPU 或者 GPU 渲染。
6. skia 小结
Skia 是一个功能强大的跨平台图形库,能绘制矩形、圆形、贝塞尔曲线等矢量图,绘制点阵字体和矢量字体,显示 jpeg、png、gif、webp 等图片,同时性能好,从算法和硬件两个层面进行了优化。skia 反对多种软硬件平台,除了 Android、chrome、Flutter 等产品间接将其作为图形引擎,也反对 iOS、windows 等操作系统。Skia 性能较多,还反对 lottie 动画,图像特效,还引入了两头语言 SKGL,限于篇幅,这里不再开展。
参考文档:
iOS 高性能绘图:https://medium.com/@almalehde…
Core Animation 编程指南:https://developer.apple.com/l…
skia 编译办法:https://skia.org/user/build
Skia 技术路线:
https://docs.google.com/docum…
SKGL 阐明:https://github.com/google/ski…。
Skia 源码:https://skia.googlesource.com…
Skia 百科:https://zh.wikipedia.org/zh-c…
字体介绍:http://www.klayge.org/wiki/index.php/%E5%AD%97%E4%BD%93%E7%B3%BB%E7%BB%9F
FreeType 官网:https://www.freetype.org/
png 压缩原理:https://www.jianshu.com/p/5ad…
GPU 渲染流水线:https://zhuanlan.zhihu.com/p/…
Vukan 介绍:https://www.khronos.org/asset…
ARM Mali GPU 介绍:https://developer.arm.com/sol…
Vulkan 和 OpenGL ES 比拟:https://community.arm.com/dev…
Qualcomm 发表 Adreno 530 GPU 反对 vulkan:https://www.qualcomm.cn/news/…
https://www.adobe.com/content…