
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个层次1UI库跟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将设备封装成下面三个类1SKBitmapDeviceCPU渲染模式绘图用于没有显卡或者显卡驱动的设备。此模式下最后会将需要绘制的图形转成位图数据RGB写入指定内存故称为BitmapDevice。写内存操作通过AVX或者NEON指令集实现。2SKGPUDeviceGPU渲染方式绘图。目前大部分移动设备和个人电脑都有GPUGPU比CPU的运算单元多并行计算能力强通过GPU绘图可降低CPU占用性能更好。Flutter、最新版本的chrome和android系统默认设置为GPU渲染模式。Chrome中的配置截图如下可看到默认采用GPU渲染。3SKPDFDevice选用此设备时渲染结果不是输出到显示器的画面而是输出为pdf文件。可以通过skia官网在线体验不同设备的渲染结果https://fiddle.skia.org/c/shapes2.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。OpenGLOpen Graphics Library”是目前使用最广泛的跨平台图形变成接口跨平台特性好大部分操作系统和GPU。Skia在大部分平台采用OpenGL实现GPU绘图少部分平台调用Metal和vulkan。Metal是苹果公司2014年推出的和 OpenGL类似的面向底层的图形编程接口只支持iOS。对软硬件有要求要求硬件苹果A7及以后操作系统iOS 10及以上。Metal理论上性能比OpenGL性能强故新设备中开启Metal可提高性能。例如Flutter中已启用了metal支持详情参考https://github.com/flutter/flutter/wiki/Metal-on-iOS-FAQ。Vulkan是新一代跨平台的2D和3D绘图应用程序接口API,旨在取代OpenGL理论上性能强于OpenGL。自 Android 7.0 开发者预览版开始Google便在系统平台中添加了对Vulkan的API支持。目前Skia的GPU渲染模式已用vulkan实现了一套但存在一些bug。具体参考https://skia.org/user/special/vulkan。Skia对上述三种图形接口进行了封装屏蔽了不同底层图形API接口的差异。OpenGL接口的封为GrGLOpsRenderPassMetal的封装层为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格式。点阵存储的是多张位图常见的有1616242432*32等尺寸解码和显示简单缺点是放大后有锯齿。1) skia点阵文字显示代码SkFont font;font.setEdging(SkFont::Edging::kAlias);font.setSize(40);const char text[] Click this link!;size_t byteLength strlen(static_castconst 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_spSkTypeface 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.edu/~qin/courses/geometry/4.pdf5. 图片绘制原理5.1 Skia位图绘制原理skia提供了showBitmap函数可直接显示位图。位图渲染模式跟矢量图形类似分为CPU渲染和GPU渲染。位图的CPU渲染跟实心矩形的渲染原理类似通过SIMD指令集将位图内存一行一行拷贝到指定内存缓存中。GPU渲染模式通过调用OpenGL、Metal、vulkan的纹理贴图函数实现。5.2 Skia压缩格式图片绘制原理位图由于占用空间大使用频率低大部分情况下使用压缩格式图片。Skia支持的压缩格式图片如下格式优点缺点场景依赖解码库gif文件小支持动画、透明无兼容性问题只支持种颜色且透明度只有1位有白边和锯齿简单的动图libgifcodecjpg支持位真彩色压缩率高有损压缩不支持透明通道色彩丰富的图片libjpeg-turbopng无损压缩支持透明简单图片尺寸小不支持动画压缩率低logo/icon/透明图libpngwebp比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限于篇幅这里不再展开。参考文档1. iOS高性能绘图https://medium.com/almalehdev/high-performance-drawing-on-ios-part-1-f3a24a0dcb312. Core Animation 编程指南https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/CoreAnimation_guide/Introduction/Introduction.html#//apple_ref/doc/uid/TP40004514-CH1-SW13. skia编译方法https://skia.org/user/build4. Skia技术路线https://docs.google.com/document/d/1C9w8qpPpdgNGThqmgNnTToLZ5UYK4TsUGl5X3B_q6oM/edit5. SKGL说明https://github.com/google/skia/blob/master/src/sksl/README。6. Skia源码https://skia.googlesource.com/skia.git7. Skia 百科https://zh.wikipedia.org/zh-cn/Skia_Graphics_Library8. 字体介绍http://www.klayge.org/wiki/index.php/%E5%AD%97%E4%BD%93%E7%B3%BB%E7%BB%9F9. FreeType官网https://www.freetype.org/10. png压缩原理https://www.jianshu.com/p/5ad19825a3d011. GPU渲染流水线https://zhuanlan.zhihu.com/p/6194989812. Vukan介绍https://www.khronos.org/assets/uploads/developers/library/overview/Vk_201602_Overview_Feb16.pdf13. ARM Mali GPU介绍https://developer.arm.com/solutions/graphics-and-gaming/apis/vulkan14. Vulkan和OpenGL ES比较https://community.arm.com/developer/tools-software/graphics/b/blog/posts/initial-comparison-of-vulkan-api-vs-opengl-es-api-on-arm15. Qualcomm宣布Adreno 530 GPU支持vulkanhttps://www.qualcomm.cn/news/releases-2016-02-18-016. https://www.adobe.com/content/dam/acom/en/devnet/font/pdfs/5005.BDF_Spec.pdf