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/@shapes
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/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繪製矩形,之後在渲染流水線中完成頂點變換、光柵化和着色,最後送幀緩衝顯示。渲染流水線如下圖所示:
Metal、vulkan的渲染流水線這裏不再展開。
4. 字體繪製原理
字體無法直接顯示在屏幕上,需要解析成位圖或者矢量圖才能繪製。Skia的字體解析實現跟進平臺差異有所不同,mac和iOS平臺調用coreText庫,安卓平臺調用開源庫freeType。
FreeType是一個用C語言實現的,免費的高質量可移植字體引擎,支持點陣字體PCF、BDF和矢量字體TrueType、freeType等字體。
4.1 skia點陣字體繪製原理
Skia支持的點陣字體有PCF、BDF格式。點陣存儲的是多張位圖,常見的有16 16,24 24,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.edu/~qin/courses/geometry/4.pdf
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,限於篇幅,這裏不再展開。
參考文檔:
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
☆ END ☆
招聘信息
OPPO互聯網基礎技術團隊招聘一大波崗位,涵蓋 C++、Go、OpenJDK、Java、DevOps、Android、ElasticSearch 等多個方向, 請點擊這裏查看詳細信息及JD 。