Flutter是Google打造的高性能、跨平台的UI框架,不仅支持Android、iOS,还支持Windows、Linux等操作系统。它可以给开发者提供简单、高效的方式来构建和部署跨平台、高性能移动应用;给用户提供漂亮、无平台区分的app体验。

图形库skia是其跨平台的基石,本文会对其渲染原理做剖析,带大家了解flutter平台的渲染机制。本文不会对跨端框架做对比,也不会涉及绘制的三棵树,流水线机制等知识(后续会单独介绍),还有一点最新的flutter2也对web支持进行了提速,其中web render中的html渲染方式底层并没有用skia库。

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图形库在整个渲染过程所处的位置和作用,我大概将移动端的整体UI框架大致分成下面4个层次

1.UI库

无论Android还是ios都有自己的一套UI库(View/UIView),而Flutter则用dart语言实现一整套UI控件。Flutter先将控件树转成渲染树,然后交由skia库绘制界面。

2.图形库

Skia图形库跟iOS平台的CoreAnimation等库功能类似,不仅提供了图形渲染功能,还提供文字绘制和图片显示功能。高级图形图像库将需要绘制的图形转成点、线、三角形等图元,再调用底层图形接口实现绘制。skia本身也依赖了字体库,图片解析库等三方库

3.低级图形接口

OpenGL是使用最广的低级图形接口,兼容性最好,基本上支持市面上的所有GPU。Vulkan是google最近几年新推出的图形API,除了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性能优化提供思路。

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的外部依赖和层级结构讲解完毕,下面重点讲解skia的图形、文字和图片的绘制原理。

图形绘制原理

Skia支持绘制的图形众多,包括圆形、椭圆、矩形、贝塞尔曲线等。下文重点分析图形的CPU和GPU两种渲染模式的原理。

图形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位数据。

图形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/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接口的封为GrGLOpsRenderPass,Metal的封装层为GrMTOpsRenderPass,Vuklan的封装层为 GrVKOpsRenderPass。

5)通过GPU完成剩余绘图操作。

下面以OpenGL为例说明。接口封装层调用OpenGL glDrawArray绘制矩形,之后在渲染流水线中完成顶点变换、光栅化和着色,最后送帧缓冲显示。

字体绘制原理

字体无法直接显示在屏幕上,需要解析成位图或者矢量图才能绘制。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格式。点阵存储的是多张位图,常见的有16*16,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);
}

绘制流程如下:

矢量字体的绘制流程跟点阵字体大部分一样,不同之处在于解析结果为贝塞尔曲线。贝塞尔曲线的渲染算法稍微复杂,参考文章https://www3.cs.stonybrook.edu/~qin/courses/geometry/4.pdf

图片绘制原理

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是一个功能强大的跨平台图形库,能绘制矩形、圆形、贝塞尔曲线等矢量图,绘制点阵字体和矢量字体,显示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-f3a24a0dcb31

2. Core Animation 编程指南:https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/CoreAnimation_guide/Introduction/Introduction.html#//apple_ref/doc/uid/TP40004514-CH1-SW1

3. skia编译方法:https://skia.org/user/build

4. Skia技术路线:https://docs.google.com/document/d/1C9w8qpPpdgNGThqmgNnTToLZ5UYK4TsUGl5X3B_q6oM/edit

5. SKGL说明:https://github.com/google/skia/blob/master/src/sksl/README。

6. Skia源码:https://skia.googlesource.com/skia.git

7. Skia 百科:https://zh.wikipedia.org/zh-cn/Skia_Graphics_Library

8. 字体介绍:http://www.klayge.org/wiki/index.php/%E5%AD%97%E4%BD%93%E7%B3%BB%E7%BB%9F

9. FreeType官网:https://www.freetype.org/

10. png压缩原理:https://www.jianshu.com/p/5ad19825a3d0

11. GPU渲染流水线:https://zhuanlan.zhihu.com/p/61949898

12. Vukan介绍:https://www.khronos.org/assets/uploads/developers/library/overview/Vk_201602_Overview_Feb16.pdf

13. ARM Mali GPU介绍:https://developer.arm.com/solutions/graphics-and-gaming/apis/vulkan

14. Vulkan和OpenGL ES比较:https://community.arm.com/developer/tools-software/graphics/b/blog/posts/initial-comparison-of-vulkan-api-vs-opengl-es-api-on-arm

15. Qualcomm宣布Adreno 530 GPU支持vulkan:https://www.qualcomm.cn/news/releases-2016-02-18-0

16. https://www.adobe.com/content/dam/acom/en/devnet/font/pdfs/5005.BDF_Spec.pdf

17.https://my.oschina.net/u/4273516/blog/4865306

Flutter的绘制剖析——图形库skia相关推荐

  1. 深入理解Flutter的图形图像绘制原理——图形库skia剖析

    Flutter是目前流行的高性能跨平台UI框架,图形库skia是其跨平台的基石.本文将深入分析skia的图形.字体.图片的渲染原理,如何挖掘硬件特性,为UI性能优化提供思路. 1. 引言 Flutte ...

  2. Flutter 画笔绘制二维码扫描框

    文章目录 一.CustomPaint介绍 1. CustomPaint 2. CustomPainter 3. Paint & Canvas 4. 示例(绘制文本背景) 二.计算扫描框四个点坐 ...

  3. flutter 自定义绘制_自定义可绘制

    flutter 自定义绘制 I love our new designs! Recently I've been working on user interactions. One of them i ...

  4. Flutter图像绘制原理深入分析

    题记 -- 执剑天涯,从你的点滴积累开始,所及之处,必精益求精,即是折腾每一天. ** 你可能需要 CSDN 网易云课堂教程 掘金 EDU学院教程 知乎 Flutter系列文章 本文章将讲述 CPU. ...

  5. Flutter 自绘制 Widget

    点击"奇舞移动技术"关注我们! 内容简介 Flutter Team 已经提供了很多的 widget,风格也不同,比如 Android 的 Material Design .iOS ...

  6. Flutter BoxShadow(绘制阴影)+Container+BoxDecoration

    勿以善小而不为,勿以恶小而为之.--刘备      阴影(BoxShadow)+Y轴偏移量(Offset) class _MyHomePageState extends State<MyHome ...

  7. 初识Flutter中的Layer

    初识Flutter中的Layer 开篇 接触Flutter开发一段时间后发现自己对Flutter渲染流程重要的一环Layer的认知比较少,虽然Flutter对Widget的封装非常全面了开发者基本上只 ...

  8. Android Tech Weekly - 45 :再不好好学习,Copilot 就取代你了

    技术文章 写给 Android 开发者的芯片知识 https://juejin.cn/post/7025066082448703525 本文主要讲清了下面几个问题:Android 动态库适配到底在适配 ...

  9. 【Android 内存优化】Android 原生 API 图片压缩原理 ( Bitmap_compress 方法解析 | Skia 二维图形库 | libjpeg 函数库 | libpng 函数库 )

    文章目录 一. 图片质量压缩方法 二. Skia 二维图形库 三. libjpeg.libpng 函数库引入 在博客 [Android 内存优化]图片文件压缩 ( Android 原生 API 提供的 ...

最新文章

  1. vue 中实现异步加载模块
  2. leetcode算法题--扁平化多级双向链表★
  3. PostgreSQL的那点事儿
  4. 如何用50行代码构建情感分类器
  5. 建站如此容易:WordPress3.9建站简明视频教程完成发布啦
  6. PHP修改表格(增删改)
  7. Git:add多个文件或者目录的方式
  8. 【我的相册】一瓶解千愁
  9. 黄聪:PHP 防护XSS,SQL,代码执行,文件包含等多种高危漏洞
  10. linux终端下载vscode,Ubuntu:安装vscode
  11. python使用逐行读取,出现空行,清楚空行方法
  12. 黑莓7100刷机及修改PIN,完美破解超越输入法
  13. C# 反编译-Reflector 反混淆-De4Dot 修改dll/exe代码-reflexil
  14. IOS性能优化 - 分析应用
  15. C#无损高质量压缩图片实现代码
  16. 超燃动态可视化条形图源码及效果图_HTML5大数据可视化效果(一)彩虹爆炸图...
  17. 身在北京,都有故事:九位北漂的心酸故事,只有经历过才有体会!
  18. “前浪”微博财报里的悲喜两极
  19. 利用 telnet 命令测试 SMTP 服务(QQ邮箱发邮件)
  20. 测试术语-bug分类

热门文章

  1. [转载] 杜拉拉升职记——52 如何处置这样的“三期”员工
  2. 基于geotools实现shp图层的合并
  3. css中四大定位模式
  4. springboot+mybatis启动报错:No suitable driver found for xxx
  5. 2023清明放假调休吗 节假日放假信息看日历备忘录
  6. 锂电池串联放电并联充电自动转换电路
  7. 2016猴年春节有感,androidframework开发面试
  8. 2019年,这5个UI设计趋势正在流行!
  9. 生成pdf base64字符串
  10. Puppeteer入门实践