天天看點

Android圖形顯示系統——上層顯示2:硬體加速實作Android界面繪制的硬體加速實作

android的界面繪制的硬體加速采取上下整合的一套流程實作

Android圖形顯示系統——上層顯示2:硬體加速實作Android界面繪制的硬體加速實作

hardwarerenderer->threadedrenderer:組織硬體加速渲染的類,下發建立顯示清單和回放的指令。

gles20recordngcanvas gles20canvas hardwarecanvas:與canvas平級的ui渲染引擎支援,但這個canvas隻能存儲指令到顯示清單中,并在threadedrenderer中的渲染線程輔助下運作。

rendernode:所有view對應一個構成一個rendernode

rendernodeanimator:動畫用

hardwarelayer:調savelayer時産生,緩存繪制内容為一個layer

displaylistrenderer:對應于hardwarecanvas,建立顯示清單的類

rendernode:一個渲染節點,包含繪制指令和相關資源

baserendernodeanimator:動畫用

renderproxy:由于opengl上下文是線程私有的,需要使用到opengl的操作都必須在同一線程。這個類的作用就是按commander模式做一個中轉,把事務轉移到持有上下文的線程中執行。

layer:對應于上層的hardwarelayer,實際上是緩存繪制過的内容到一張紋理上,至于如何緩存,有fbo方式和copytex方式

openglrenderer:這個類并不直接被上層調用,但它是執行實際渲染任務的入口類,定位性能問題一般直接從這個類看起。

ps:建議仔細看看renderproxy.cpp裡面的commander模式實作方法,确實相當之精妙簡潔,不過感覺c11有匿名函數後不需要這麼麻煩了。

很多介紹顯示清單機制的文章都是一帶而過,仿佛得到一個顯示清單并回放是很簡單的事情。但真正動手寫時,就會發現有很多問題:

1、如何存儲每個api及相關參數?為每個api建立一個類,回放時調類方法?還是把api及參數作一個編碼,然後回放時解碼,用虛拟機的方式執行?用前者實作比較簡單,擴充相對容易,但每個api建一個類,需要非常大的代碼量(越多的代碼意味着越容易出錯);用後者,需要建構編碼解碼的邏輯,總體代碼量較少,但是虛拟機進行中switch case代碼冗長,且不容易作擴充。

2、資源怎麼處理?這個是最為棘手的,如果拷貝資源,會大幅降低效率,不可取,但如果不拷貝,上層在傳入資源後馬上修改這些資源,回放時結果會是錯誤的(如傳入bitmap a之後,調用drawbitmap之後,馬上修改a的内容,回放時a就是修改後的)。原則上自然是不拷貝,但如何限制上層行為呢?

Android圖形顯示系統——上層顯示2:硬體加速實作Android界面繪制的硬體加速實作

drawop即産生渲染效果的算符,stateop為産生狀态變更的算符,須與後續的drawop配合使用。

之是以采用獨立成類的設計方式,是為了滿足批處理優化的需要。

resouces保留在對應的resoucecaches中。

drawop為渲染算符,分的子類較多,主要是以下幾頂:

calldrawglfunction->drawfunctorop:

用于webkit/chromium的硬體加速渲染,webkit/chromium浏覽器核心中将基于opengl的渲染代碼封裝為函數functor,傳入hwui引擎中執行。

drawsometextop:

繪制文本的算符,歸結為一個算符的原因是文本解析的步驟是統一的

drawcolorop:

将區域刷成指定顔色的算符

drawboundedop:

drawrect、drawbitmap及一般的drawpath均繼承于此算符,其特點是渲染存在邊界。可以設法判斷是否覆寫

drawlayerop:

繪制layer

drawshadowop:

繪制陰影

儲存/恢複狀态/建層:saveop/restoretocountop/savelayerop

矩陣變換相關:translateop/rotateop/skewop/setmatrixop/concatmatrixop

設定裁剪區域的算符:clipop/cliprectop/clippathop/clipregionop

設定paint的采樣模式:resetpaintfilterop/setuppaintfilterop

hwui的緩存是比較複雜的,一方面,由于采用基于顯示清單的異步渲染機制,用于渲染的資源本身需要在清單中緩存。另一方面,由于gpu/顯示卡渲染的異構性,其所需要的資源必須要由顯示清單中的資源上傳或映射而來,上傳的資源和映射關系本身構成顯存的緩存。

caches 作為單例,存儲了所有的渲染緩存,主要内容如下:

這種單例設計模式自然完全沒有考慮同一程序中可能有多個線程使用hwui的情況,是以如果要将hwui改成支援多線程分别使用,需要作不少手術。

如圖所示:

Android圖形顯示系統——上層顯示2:硬體加速實作Android界面繪制的硬體加速實作

上層canvas的api中所夾帶的資源,建立顯示清單時在resourcecache中緩存一次(bitmap僅引用,其餘的全部拷貝),在回放顯示清單時再繼續建構各自對應的cache。

caches中的所有緩存,除resourcecache之外的不妨統稱為enginecache。這個緩存關系就是:

api(java virtual machine)——resourcecache——enginecache

緩存比對的查詢機制都是依靠指針,由于path、paint等資源中會夾帶effect、shader等特效,當應用層修改這些東西後,由于指針沒變,緩存無法感覺其變化而更新。

是以在skia裡面為skpath、skpaint加入了generationid,當它們附帶的特效發生改變時,這個id同時修改,依此來校驗api-resourcecache,resourcecache—enginecache是否一緻,若不一緻自然是要重新再拷貝一遍/重新生成一次cache。

由于resourcecache不複制bitmap,必須要防止在渲染過程中上層把bitmap給釋放/修改掉。

但它隻防止了釋放,并沒有阻止修改的實作,是以這個隻能靠應用開發者自覺。

代碼見 frameworks/base/core/jni/android/graphics/bitmap.cpp

resourcecache不包含bitmap(雖然會阻止上層回收),占用記憶體還是很少的,緩存大頭還在 enginecache

hwui用的是2.0以上的opengles版本,着色器的建構是很重要的部分。不過,2d繪圖的着色器相對也較簡單。

這裡的設計思想是先翻譯 skpaint 及其中的 skshader為 programdescription 結構,然後由 programcache 去根據這個結構,選擇合适的着色器語言片斷,拼裝起來,組成 glprogram

skshader 是一個父類,包含 bitmap shader,gradient shader 等好幾類,是以這裡對每一類都要有對應的函數去解析。

主要函數:

skiashader::describe

programcache::generatevertexshader

programcache::generatefragmentshader

至于programcache的着色器代碼怎麼寫的,用的正交投影還是透視投影,紋理貼圖怎麼實作等,這裡就不詳述了。

代碼參考texturecache::get 和 texturecache::generatetexture

基礎的紋理上傳,不多述。

這個是android 4.3起引入的機制,将預加載所得的圖檔,先整合到一張 graphicbuffer 上,轉變為一張eglimage。然後各應用在使用硬體加速渲染ui時,将此eglimage映射為自身的opengl紋理,進而免去這部分資源紋理上傳的過程,且由于應用間共享紋理,節省了記憶體。

詳細看老羅的部落格吧,雖然個人感覺把這一個簡單的功能講太細了:

<a href="http://blog.csdn.net/luoshengyang/article/details/45831269">http://blog.csdn.net/luoshengyang/article/details/45831269</a>

文字/文本繪制對任何一個2d渲染引擎來說,都是一個棘手的事。主要是因為文本解析本身需要大量的時間,肯定需要緩存,但使用緩存的話,由于各個文字在各種字型下的解析結果都不一樣,全緩存進來記憶體耗費極高,是不可能的。

沒有什麼完美的方案去設計一個文本緩存機制,正好比沒有絕對正确的企業管理模式。

hwui中是這麼處理的:

緩存的設計(這個是每一個fontrenderer都包含的):

Android圖形顯示系統——上層顯示2:硬體加速實作Android界面繪制的硬體加速實作

代碼見fontrenderer::inittexttexture

對skia解析出來的字形skglyph,會按pixelbuffer 由小到大逐次去找一個對應位置,然後複制上去,如果是有變換需要(mgammatable存在),則在這個過程順便把gammatable變換做了。

在後面渲染時,pixelbuffer會上傳為texture,然後gpu就可以使用字形渲染的結果了。

至于 mgammatable,可詳細看 gammafontrenderer 和 lookup3gammafontrenderer 類。

pixelbuffer 根據 裝置支援的opengl es 版本和屬性配置(ro.hwui.use_gpu_pixel_buffers)選用cpupixelbuffer或gpupixelbuffer(需要3.0以上版本和屬性開關開啟)。cpupixelbuffer就是malloc出來的記憶體,gpupixelbuffer是pbo。opengles 3.0 标準有pbo映射為cpu記憶體的api(glmapbufferrange),會提升緩存過程中上傳的效率。

(注:gpupixelbuffer這一段代碼也是使用pbo的好教材,需要了解pbo如何使用的可以參考下)。

在渲染時先根據已經緩存好的字形位置,算出紋理采樣的坐标,塞進對應cache的vbo,然後周遊所有的cachetexture,渲染包含有待渲染文字的cache即可。

代碼見 fontrenderer::issuedrawcommand

延遲渲染模式下,不管是多少個字,始終是根據cache數來調drawcall,這樣,drawcall的調用次數就比較少了。

出于記憶體優化的考慮,中間一層 pixelbuffer 是可以不要的,但相應地就要在外面把 gammatable 映射做掉,邏輯會複雜一些。

hwui引擎中實作drawpath時,沒有自己去計算路徑點,而是調用skia的drawpath接口繪制一張a8的模闆,然後按模闆把shader混合進去。對應的pathcache就是存儲這個模闆的。

Android圖形顯示系統——上層顯示2:硬體加速實作Android界面繪制的硬體加速實作

在android 4.0時,繪制圓角矩形、圓形等特殊形狀時,是按drawpath的方式,生成模闆再混合,這種方式需要占用記憶體,且不是很效率,是以後面hwui中加入了處理形體的功能,這就是曲紋細分器tessellator,它通過解析skpath,生成一系列頂點來描述形體。

目前主要支援凸形狀(詳見pathtessellator的實作),目前細分過程仍然是靠cpu實作的,在未來手機上的gpu支援曲紋細分的shader後,可以把這部分工作轉移到gpu上。

tessellationcache就是曲紋細分器生成的vbo(vetex buffer object),相對于模闆(一張a8紋理)而言節省不少記憶體,且執行時一般效率更高(曲紋細分方式由于頂點數多,vertex shader負荷較大,但相對于模闆方式,fragment shader負荷較小,記憶體帶寬占用較少)。

(略,以後有空再補)

Android圖形顯示系統——上層顯示2:硬體加速實作Android界面繪制的硬體加速實作

關于顯示清單的建立過程可以參考老羅部落格:

<a href="http://blog.csdn.net/luoshengyang/article/details/45943255">http://blog.csdn.net/luoshengyang/article/details/45943255</a>

延遲渲染是在回放顯示清單時,先做一步預處理(defer),然後再執行處理後的指令(flush)。

看這段回放的主代碼可以知道,延遲渲染是先建立一個延遲渲染清單,然後把顯示清單中的指令全部往裡面加進去(這個過程中做預處理),然後交由延遲渲染清單去回放(flush)。

預處理的作用主要是:

(1)合并渲染,減少drawcall調用

(2)避免部分的過度繪制

過度繪制/overdraw是指同一個像素被渲染多次的情形。解決overdraw的方法要使用指令清單(顯示清單),對清單中每個繪制指令計算其涵蓋區域。然後是計算重複渲染的區域,設法将這個區域上面的繪制指令合并

drawop算子需要實作ondefer這個方法,為 deferreddisplaylist 提供兩個資訊:deferinfo和deferreddisplaystate。

deferinfo反映這個drawop算子本身的性質(能否合并,是否透明,歸屬哪一類),deferreddisplaystate則是結合算子所處的矩陣變換狀态,反映該算子在最終顯示屏的地位(渲染邊界、矩陣變換)

hwui中,避免過度繪制的條件很苛刻,需要完全不透明且完全覆寫,是以defer作用主要展現在合并渲染上了。

支援合并渲染的drawop需要實作一個特殊的multidraw函數,用以将同類一系列drawop的渲染在同一函數完成。

目前所看到合并渲染僅限于繪制assetatlas資源的操作合并與多次繪制文字繪制的合并。

當應用記憶體不足時,會盡量去回收記憶體,其中hwui所占的cache在回收的範圍之内,最終調用caches::flush回收,有三種模式:

kflushmode_layers:

清除layercache和renderbuffercache

kflushmode_moderate:

除上面外,清除部分字型緩存、圖檔紋理、路徑紋理

kflushmode_full:

字型緩存全清,再把fbo、dither清除掉

Android圖形顯示系統——上層顯示2:硬體加速實作Android界面繪制的硬體加速實作

請注意:

program是不清的。在記憶體依然緊張時,會在上層直接摧毀opengl上下文。

1、完備的gpu繪制流程,在上層api不變的前提下,妥善解決了2d渲染的性能問題

2、延遲渲染合并了大量的渲染指令,drawcall調用少效率高,且有一定的防止過度繪制的功能

3、有一層一層回收緩存的機制

4、相當好的基于opengles 2d 的引擎範本,很多代碼(比如:紋理上傳、pbo、曲紋細分)很有參考價值。

1、上下層耦合關系嚴重,對上依賴于java層的合理調用,對下依賴于skia,不容易提供單獨的基于硬體加速的2d渲染引擎,容易出現記憶體/資源洩露

2、資源在cpu和gpu中均做cache,占用記憶體較多:顯示清單中的圖檔資源和紋理圖檔同時存在,字型三重緩存

3、延遲渲染機制做得還是不夠好,消除過度繪制的能力有限,而且每幀都要算一次延遲渲染資訊。

繼續閱讀