成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

Android字體渲染器:使用OpenGL ES進行高效文字渲染

移動開發(fā) Android
任何有多年客戶端開發(fā)經(jīng)驗的開發(fā)者都應該知道復雜的文字渲染是怎么工作的。至少在2010年以前,我剛開始寫libhwui的時候,我就意識到處理文字有時會比其他方面更復雜,特別是當你嘗試用GPU在屏幕上進行繪制的時候。本文只是對Android的字體渲染器進行簡單介紹,還有很多實現(xiàn)的細節(jié)沒有考慮到,或者很多問題以后會說明。

任何有多年客戶端開發(fā)經(jīng)驗的開發(fā)者都應該知道復雜的文字渲染是怎么工作的。至少在2010年以前,我剛開始寫libhwui的時候(這是一個基于 Android2.0的2D繪畫庫),我就意識到處理文字有時會比其他方面更復雜,特別是當你嘗試用GPU在屏幕上進行繪制的時候。

文字與Android

Android上的文字渲染加速器硬件最初是由Renderscript團隊寫的,然后被很多工程師改進和優(yōu)化,包括我和好友Chet Haase。在網(wǎng)絡上,可以很容易找到很多關于怎么使用OpenGL ES渲染文字的教程。如果覺得還不夠,可以看看關于游戲的文章,只看關于文字渲染部分就行。

本文說不是很新奇的知識,只是對于很多開發(fā)者來說,通過本文可以從深層次上了解如何實現(xiàn)一個基于GPU的文字渲染系統(tǒng),文章***還介紹了一些比較容易實現(xiàn)的優(yōu)化方法。

用OpenGL渲染文字的常用方法是計算包含所需字形的所有紋理集。這個操作通常是使用一些相當復雜的算法進行離線操作,這樣可以在構造字形的時候更加高效。在創(chuàng)建這樣一個紋理集之前,首先需要知道應用程序在運行時要使用的字體,包括字體樣式、大小以及其它屬性。

在Android上,提前進行字體紋理生成不是一個實用的方案。Android上的UI工具并不能知道應用系統(tǒng)會使用什么字體和字形,并且應用還可以在運行時載入自定義的字體,這是主要的限制。Android字體渲染還必須遵循以下條例:

  • 它必須在運行時建立字體緩存;
  • 它必須能夠處理大量的字體;
  • 它必須可以處理大量的符號;
  • 它必須要盡可能減少字體上的資源消耗;
  • 必須運行要快速;
  • 在低端和高端機器上也能夠良好運行;
  • 能***與其它組件結合(驅動程序或GPU)。

字體渲染器的實現(xiàn)

在進入底層OpenGL字體渲染器工作原理之前,我們先從應用層使用的高級別的API開始。這些API對于理解libhwui很重要。

文字API

用于布局和繪制文字主要有4個API:

  • android.widget.TextView:一個可以處理文字布局和渲染的視圖組件。
  • android.text.*:一個可以創(chuàng)建風格化文字和布局的類集合。
  • android.graphics.Paint:用于測量文字。
  • android.graphics.Canvas:用于渲染文字。

TextView和android.text的都是在Paint和Canvas上的高級API。Android3.0以后,Paint和Canvas直接被實現(xiàn)在Skia之上,這是一個開源的渲染庫。SKia提供了一個很好的Freetype抽象實現(xiàn),這是一個很熱門的開源字體柵格化程序。

1-BitH26buboQae4iO-FpSyg

對于Android4.4,情況變得有些復雜。Paint和Canvas都使用了一個內部的JNI API,叫做TextLayoutCache。它可以處理復雜的文字布局(CTL)。這個API依賴Harfbuzz,一個空間開源的字形引擎。TextLayoutCache的輸入是一個字體和一個Java的UTF-16的字符串,輸出是一個帶有x/y坐標的字形列表。

TextLayoutCache是支持非拉丁語言的要點,比如阿拉伯語言、希伯來語、泰國語等,本文不會解釋TextLayoutCache和 Harfbuzz的工作原理,但本人強烈建議讀者去學習學習CTL。如果在開發(fā)應用的時候需要支持非拉丁語言環(huán)境,那么就要學習它了。如果你曾經(jīng)參與過 OpenGL渲染文字的文章中的討論,就會發(fā)現(xiàn)這種特殊的問題是很少見的。繪制文字比簡單排布字形更復雜。某些語言中,比如阿拉伯語是從右到左的,還有泰 語甚至需要把字形排布在前一個字形的上面或者下面。

1-VvVj04gAzuTsMC_AN9RGRA

也就是說,當直接或間接調用Canvas.drawText()函數(shù)的時候,OpenGL 渲染器不會收到你發(fā)送的參數(shù),而是收到一串數(shù)字、符號標識,還有x/y 坐標集合。

點陣化和緩存

字體渲染器的每一個繪制方法都是和字體相關的。字體用于緩存?zhèn)€別字形符號,而字形符號又被存儲在緩存結構中(緩存結構可以包含不同字體的字形符 號)。緩存結構是持有多個緩沖區(qū)的一個重要的對象,有block集合、pixel緩沖區(qū)、OpenGL結構處理器,還有點陣緩沖區(qū)(也就是網(wǎng)格)。

1-qK4rIi_HDsEYPQQxFK5uPg

這個對象存儲的數(shù)據(jù)結構比較簡單:

  • 在字體渲染器中字體是存儲在一個LRU緩存中的;
  • 字形符號分別存儲在對應的map字體集合中(key就是字形文件的identifier);
  • 緩存結構使用一個塊鏈表集合來記錄空間的大小;
  • 像素緩沖區(qū)是一個uint8_t或者uint32_t類型的數(shù)組(作alpha值和RGBA的緩存);
  • 網(wǎng)格其實就是一個頂點數(shù)組,帶有兩個屬性:x/y位置和u/v坐標;
  • 一個GLuint的處理器。

字體渲染器對不同類型的緩存結構提供了幾種緩存紋理實例,也就是根據(jù)不同的大小區(qū)分,這個大小可能會根據(jù)不同設備而有所不同,這里這里說的是默認的大小(緩存的數(shù)量是硬編碼的):

  • 1024*512 alpha緩存。
  • 2048*256 alpha緩存。
  • 2028*512alpha緩存。
  • 1024*512alpha緩存。
  • 2048*256alpha緩存。

當緩存紋理對象創(chuàng)建之后,其對應的緩沖區(qū)不會自動分配空間,除了1024*512的alpha緩存總是自動分配外,其它的都是根據(jù)需要來分配空間。

字形符號以列的形式打包在紋理中,只要字體渲染器遇到?jīng)]有緩存的符號,它就會向緩存紋理請求響應的類型(存儲在以上的有序列表中),然后緩存該符號。

這是上述的blocks列表使用到的地方,這個列表包含了當前已分配的列和所有未分配的空間。如果字形符號和已經(jīng)存在的列匹配,那該字形符號就會被加到該列的底部。

如果所有列都被占用,從左邊的剩余空間開辟新列。因為所有字體都是等寬的,渲染器會把每個字形的寬度弄成4像素的倍數(shù)(默認是4像素)。這是對列的重利用和字形打包的一個折衷,這個打包目前還不是很好,但是實現(xiàn)起來比較快。

所有的字形符號都存儲在一個含有1個像素邊框的結構中,這樣在雙線過濾采樣的時候可以避免偽跡的產生。

在文字帶有縮放變形操作的渲染中,了解文字何時被渲染也是非常重要的。這個變形操作直接到Skia/Freetype來處理,這就意味著字形符號是 在緩存結構中變形存儲的。這樣可以改善渲染的質量。幸運的是,文字一般很少做縮放動畫效果,就算是使用了,也只是設計很少的字形符號。本人做過很多實驗, 也沒有找到一個實際使用的場景。

還有其它關于paint的屬性會影響字形符號的柵格化和存儲的:粗體、斜體、還有X縮放(在Canvas上做矩陣變換)、字體風格以及線條寬度等。

柵格化的可選方案

事實上,還有其它的方式去在GPU上處理文字字形符號。可以直接被渲染程向量,但是這樣做開銷很大。我調查過標記距離字段的方法,但是簡單實現(xiàn)的時候遇到了精度的問題(創(chuàng)建曲線的時候會不穩(wěn)定)。

本人建議讀者可以看看Glyphy這個項目。這是一個開源庫,作者是Harfbuzz。項目在標記距離字段技術上進行延伸,同時也解決了精度的問題。我暫時沒有花太多時間看這個項目。但是上一次在做著色器的時候,發(fā)現(xiàn)這種技術在Android上是被禁止使用的。

預緩存技術

字形符號緩存是一定要做的。如果做預緩存的話,效果會更好。因為libhwui是一個延遲的渲染器(和Skia的快速模式正好相反),所有屏幕上出現(xiàn)的字形都是一幀一幀開始的。在一系列的顯示操作(批處理和合并操作)中,字體渲染器需要盡可能多地緩存字形符號。

使用預緩存技術的主要優(yōu)勢在于,可以完全或者最小化紋理加載的時間。紋理加載操作是消耗非常大的,它會推延CPU或者GPU。甚至在幀渲染過程中,改變紋理還會在GPU體系結構帶來更多內存的壓力。

ImaginationTech的PowerVRml SGX GPUs使用了延遲疊加技術架構,可以提供很多有趣的特性。但如果在渲染幀時需要修改紋理,會強制要求驅動程序對紋理進行復制。因為字體結構相當大,如果不好好處理紋理加載的話,很容易就內存耗盡了。

這樣的場景確實發(fā)生在Google Play的一個應用中。這個APP是一個簡單的計算器,僅使用一些數(shù)學符號和數(shù)字進行簡單的繪制按鈕。字體渲染器在某的時候甚至渲染不出***幀。因為按鈕 是連續(xù)進行繪制的,每一個按鈕都會觸發(fā)一個紋理加載,然后復制整個字體緩存。系統(tǒng)根本沒有這么多內存去存儲這么多緩存的備份。

清空緩存

因為用作字形緩存的紋理是非常大的,它們有時會被系統(tǒng)回收再利用,以便為其它程序更多的RAM。

當用戶隱藏當前的應用時,系統(tǒng)給應用發(fā)送一條消息要求釋放盡可能多的內存。很明顯,這就需要銷毀***的字形緩存結構。在Android中,這個大緩存結構就是所有字形的緩存。除了默認***個創(chuàng)建的以外(1024*512的默認緩存)。

紋理結構在沒有存儲空間的時會被清空。字體渲染器使用LRU算法對素有字體進行記錄,僅僅是記錄而已。如果需要,就會根據(jù)最近最少使用的紋理來清除內存。目前沒有提供這個操作,但是它確實是一個不錯的優(yōu)化策略。

批處理和合并操作

Android4.3引入的繪制批處理和合并操作是一項重要的優(yōu)化,徹底減少了大量往OpenGL驅動發(fā)送指令的問題。

為了進行合并操作,字體渲染器在進行多種繪制調用的時候會緩存文字,每個緩存紋理都會擁有一個客戶端的2048 quads的數(shù)組(1 quad = 1 glyph)。當調用lilbhwui中的一個文字繪制API時,字體渲染器獲取合適的網(wǎng)格為每個字形符號進行位置和u/v坐標的繪制。網(wǎng)格在批處理的末 端被發(fā)送到GPU上(由延遲顯示系統(tǒng)決定)。或者當一個quad的緩沖區(qū)滿了的時候,可能會出現(xiàn)多網(wǎng)格渲染同一個字符串的情況——一個字符緩存占用一個網(wǎng) 格。

這個優(yōu)化過程很容易實現(xiàn),對顯示效果幫助也很大。因為字體渲染器使用多緩存結構,所以在一個字符串的渲染過程匯總,可能字形符號會來自不同的紋理。 如果沒有批處理好合并操作的話,每個繪制調用都要傳遞給GPU。字體渲染器就需要不斷切換不同的緩存結構,這樣會帶來很大的消耗。

在測試字體渲染器的時候,我已經(jīng)在一個測試App中發(fā)現(xiàn)了這個問題。這個App只是簡單地用不同的樣式和大小渲染一句“hello world”。其中字母“o”被存儲在不同的紋理中,和其它的字符不一樣。這種情況導致字體渲染器開始時只繪制了“hell”,然后渲染“o”,然后再渲 染“w”,然后在渲染“o”,接著才是“rld”。這5個繪制調用和5個紋理進行綁定連接后,只有其中兩個是實際需要的,現(xiàn)在渲染器先繪制“hell w rld”,然后在一起繪制兩個“o”,這就是批處理和合并操作的好處了。

優(yōu)化紋理加載

之前提到過字體渲染在更新緩存紋理的時候(記錄每個紋理中的臟數(shù)據(jù)塊)會盡可能加載少一點數(shù)據(jù)。但是很不幸,這個方法還是有兩個限制。

首先,OpenGL ES2.0不允許隨意上傳一個矩形區(qū)域。glTextSubImage2D 會讓你指定矩形的x/y坐標和寬高來更新矩形里面的紋理。并且它會把矩形的寬當做內存里的數(shù)據(jù)幅度,這個可以通過創(chuàng)建一個合適大小的CPU緩沖區(qū)來解決, 但是也需要事先知道這個矩形的到底有多大。

有一個很好的折衷,就是加載包含臟數(shù)據(jù)塊(矩形)的最小像素帶。因為這個像素帶和紋理一樣寬,這樣就可以節(jié)省空間。比每次都要更新整個紋理效果好得多。

第二個問題是紋理加載屬于異步調用,這樣可能造成相當長的CPU延遲(甚至可能會達到1毫秒,依賴紋理的大小、驅動程序還有GPU)。像之前說的那 樣,如果使用預緩存應該是沒有問題的。但是如果使用的是“重字體”的場景,或者是區(qū)域化語言的場景的話(較多的使用字形符號比如中文),那么問題就還是會 出現(xiàn)的。

令人欣慰的是,OpenGL3.0為這兩個問題提供了解決方案,這樣就可以直接使用一個像素存儲的屬性來加載數(shù)據(jù)矩形了。GL_UNPACK_ROW_LENGTH這個屬性指定了內存源數(shù)據(jù)的寬度。需要注意的是,這個屬性會影響到當前OpenGL上下文的全局狀態(tài)。

加載紋理時,CPU延遲可以通過使用像素緩沖對象(PBOs)來避免。就像所有OpenGL里的緩沖區(qū)對象一樣PBO會駐留在GPU中,但也可以映 射到內存中。PBOs有很多有趣的屬性,但是我們關心的是一個在主存中取消映射關系后還可以進行異步加載紋理的屬性,此時操作隊列變成:

glMapBufferRange → write glyphs to buffer → glUnmapBuffer → glPixelStorei(GL_UNPACK_ROW_LENGTH) → glTexSubImage2D

調用glTexSubImage2D可以立即返回,而不用阻塞渲染器,字體渲染器可以在內存中映射整個緩沖區(qū),而且似乎不會出現(xiàn)問題。這對于緩存紋理的更新操作是一個不錯的方案。

這兩種OpenGL ES3.0的優(yōu)化方法會出現(xiàn)在Android4.4中。

陰影效果

一般文字在渲染的時候都會帶有陰影效果,這是一個相當耗費資源的操作。在臨近的字形符號可以進行相互模糊操作之后,字體渲染器不再進行獨立的預模糊 操作。有很多中方法可以實現(xiàn)模糊化,但是為了在同一幀中把這些調配操作和紋理采樣操作最小化,陰影效果會被簡單存儲為紋理,在多幀切換的時候可以保存。

因為應用程序可以輕易地拖垮GPU,所以我們還是得依靠CPU來對文字進行模糊化。最簡單和高效的方式就是使用Renderscript的C++ API,只需要簡單幾行代碼就可以實現(xiàn)核心功能。最簡單的方法是在初始化Renderscript的時候指定RS_INIT_LOW_LATENCY標記 來強制運行在CPU上。

未來的優(yōu)化操作

有一個優(yōu)化方法我希望可以在我離開Android團隊之前實現(xiàn)。文字預緩存、異步和部分紋理更新都是一些重要的優(yōu)化操作。但是柵格化文字符號一直都是一個很耗費資源的操作,在systrace可以很容易看到(啟用gfs標識然后看precacheText事件)。

對預緩存的一個簡單的優(yōu)化方式就是,把這個操作放到另一個工作線程去執(zhí)行,把柵格化操作放到后臺。這個技術已經(jīng)被用到一些復雜的路徑柵格化操作中,但是沒有添加到OpenGL架構之中。

改進批處理和合并操作也是一個可能的優(yōu)化方式,用于繪制文字的顏色一般是被發(fā)送到一個fragment陰影統(tǒng)一操作。這樣可以減少發(fā)送到GPU的頂 點數(shù)據(jù),但副作用會產生很多不需要的批處理指令:一個批處理操作只能包含一種文字顏色。如果文字顏色也存儲為頂點屬性,那么就可以網(wǎng)GPU傳遞更少的數(shù) 據(jù)。

源代碼

如果想詳細地看看字體渲染器的實現(xiàn),可以瀏覽libhwui的GitHub,可以從FontRender.cpp開始,因為很多驚喜都在這里發(fā)生,它的支持類可以在font或者sub目錄找到。對了,PixelBuffer.cpp這個文件也不錯,可以看看。這就是一個像素緩沖區(qū)的抽象實現(xiàn),可以用于CPU(uint8_t類型的數(shù)組)或者GPU緩沖區(qū)(PBO)。

***的話

本文只是對Android的字體渲染器進行簡單介紹,還有很多實現(xiàn)的細節(jié)沒有考慮到,或者很多問題以后會說明,所以有什么問題可以盡管向我提問。

原文鏈接: medium   翻譯: chris

譯文鏈接: http://blog.jobbole.com/70468/

責任編輯:閆佳明 來源: blog.jobbole
相關推薦

2010-08-13 11:02:27

Flex渲染器

2009-07-15 13:48:26

Swing模型和渲染器

2009-07-16 10:11:06

渲染器RendererSwing組件

2009-07-16 10:26:49

渲染器接口Swing

2009-11-23 19:51:48

ibmdwWeb

2017-12-26 14:27:24

2010-08-13 11:21:31

Flex渲染器

2017-05-10 14:47:37

Headless Ch頁面 Docker

2022-04-18 08:09:44

渲染器DOM掛載Vue.js

2019-08-01 15:19:26

前端開發(fā)技術

2023-05-24 16:41:41

React前端

2010-06-30 13:45:05

ZKZK 5.0.3

2014-04-29 14:16:54

2020-11-06 15:20:45

瀏覽器前端架構

2012-06-01 10:28:54

Web

2012-06-06 15:57:29

Web

2022-07-04 08:29:13

electron通信

2022-08-14 23:04:54

React前端框架

2013-10-31 10:54:11

ServerClouda

2013-11-18 14:42:53

瀏覽器渲染
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 狠狠av | 久久爱一区 | 色偷偷噜噜噜亚洲男人 | 成人欧美一区二区三区黑人孕妇 | 99精品福利视频 | 大伊人久久 | 免费h在线| 欧美在线激情 | 日日夜夜免费精品视频 | 久久久久久蜜桃一区二区 | a级在线观看 | 久久久久久久一区 | 国产一区二区不卡 | 中文字幕一级毛片视频 | 久久久女| 日韩高清中文字幕 | 精品国产一区二区国模嫣然 | 精品久久久久久久久久久院品网 | 九九热这里只有精品在线观看 | 国产精品久久久久久中文字 | 日本aⅴ中文字幕 | 久久综合伊人一区二区三 | 国产超碰人人爽人人做人人爱 | 奇色影视| 91天堂网| 国产一区二区 | 五月综合激情网 | 免费黄色大片 | 国产成人jvid在线播放 | 色综合色综合色综合 | 中文字幕国产精品 | 亚洲日韩中文字幕 | 91久久| 亚洲一级毛片 | 国产精品视频久久 | 男女爱爱网站 | 日韩一区不卡 | 日韩不卡一区二区 | 福利精品在线观看 | 91免费看片| 国产成人免费视频网站视频社区 |