深入理解Flutter的圖形圖像繪製原理——圖形庫skia剖析

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 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章