Android 圖形架構


轉載地址 : http://blog.csdn.net/new_szsheep/article/details/41348581


圖形架構


每一個開發者都應該知道Surface, SurfaceHolder, EGLSurface, SurfaceView, GLSurfaceView, SurfaceTexture, TextureView 以及 SurfaceFlinger。


這篇文章主要描述了Android系統級圖形架構的必要元素,以及如何被應用框架以及多媒體系統應用。這裏主要集中說明圖形buffer如何在系統間

流轉。如果你想了解SurfaceView以及TextureView爲什麼如此運行,以及Surface和EGLSurface如何交互,那你就來對地方了。


這裏假設你掌握一些Android設備以及應用開發的知識概念,但不需要詳細瞭解應用框架,同時這裏提及的API細節不多。這裏的內容與其他

公開的文章沒有太多重疊。這文章的目標是提供一種對圖形從如何着色到輸出中涉及事件的認知,好讓你在開發應用時候可以獲得一些建議選擇。

要達到此目的,我們要置底而上的描述UI類如何工作而不是如何使用。


文章的前面部分涉及到一些後面會用到的背景材料,故此最好就是從頭到尾的閱讀此文章,不要跳到感興趣的部分。我會從解釋Android圖形Buffer開始,

接着描述合成以及顯示的機制,在進一步瞭解高層提供合成數據的機制。


這文章主要涉及Android 4.4的系統,與早期的版本工作機制有區別,甚至未來很有可能也不一樣。跟版本相關的特性會在文章一些地方給指出。


從多方面,我都建議參考Grafika的開源代碼,這是google爲測試而弄的開源項目,它比看例子更快的瞭解系統。


BufferQueue 和 gralloc

要了解Android的圖形系統如何工作,我們要從場景的背後開始。Android所有圍繞圖形相關的中心點是一個成爲BufferQueue的類。它的作用十分的簡單:

把提供圖形數據buffer的生產者與接受圖形數據並顯示或進一步處理的消費者連接起來。生產者與消費者可以存在與不同的進程。幾乎所有涉及到在圖形

系統中移動的事情,都依賴BufferQueue。


基本的用法很直接。生產者請求一個空的buffer(dequeueBuffer()),然後給此buffer定義一些特性,如寬,高,像素格式以及使用標誌。生產者然後對此buffer

植入內容,並把它歸還到隊列(queueBuffer())。過後,消費者從隊列獲取buffer(acquireBuffer()) 並使用。當消費者用完後,它會把buffer歸還給隊列(releaseBuffer())。


大部分最近的Android設備都支持"sync Framework"。這允許具備硬件異步處理圖形數據的系統很好工作。舉個例子,生產者可以提交了一系列OpenGL ES的

繪畫命令,然後在着色未完成時把buffer放進隊列。buffer帶有一個可以提示內容準備好的(fence)圍欄。另一種(fence)圍欄可以在buffer被歸還到空列表時候附上,

好讓消費者可以在buffer還在用的時候釋放buffer。當buffer在系統中移動時,這種方法提升了延遲以及吞吐量。


隊列的一些特性,如它能維持的最大數目buffer,是由生產者和消費者聯合決定的。


有需要時候,BufferQueue會創建buffer來響應。buffer會一直保留直到其特性改變。舉個例子,如果生產者開始請求不同大小的buffer,則舊buffer會被釋放,新的

buffer會被按需創建。


數據結構目前總是由消費者創建以及“擁有”。在Android 4.3裏,僅僅生產者是Binder化,也就是說生產者可能存在在另一個進程,而消費者與創建隊列的進程同屬一個進程。

在4.4版本,這個改進了一點,朝着更大衆化的去實現。


buffer內容從不由BufferQueue拷貝。移動那麼大量的數據是低效的。因此,buffer總是以句柄的方式移動。


gralloc HAL

實際的buffer創建是通過被稱爲"gralloc"內存創建器,這個是按照設備商指定的接口來實現的。alloc函數接受如你期望的參數:寬,高,像素格式以及一套使用標誌。

這些使用標誌值得進一步關注。


gralloc創建器並不只是另外一種在堆上的內存創建器。在某些情況,創建的內容也許不是與buffer一致或完全不能在用戶空間訪問。創建的本質是由使用標誌覺定的,

如下面屬性:

  • 內存如何經常從軟件上訪問(CPU)
  • 內存如何經常從硬件上訪問(GPU)
  • 是否內存被作爲一個OpenGL ES紋理被使用
  • 是否內存被視頻編碼器使用
舉個例子,如果你指定像素格式爲RGBA 8888,以及表明buffer可以從軟件訪問---意味着你的應用可以直接接觸像素---然後,創建器就需要創建一個由4字節代表
一個像素點的buffer。如果相反,你指明此緩存僅僅只能從硬件訪問並且作爲GLES的紋理,則創建器可以做任何GLES驅動可以做的事---BGRA 次序,非線性
"swizzled" 佈局,可替換顏色格式等。允許硬件使用它期望的格式可以提高性能。


某些值不能在特定的平臺上被組合。舉個例子,”視頻編碼器“ 標誌也許需要 YUV 像素, 所以如果增加” 從軟件訪問“ 和 指定 RGBA 8888 的格式會導致操作失敗。

由gralloc創建器返回的句柄可以在進程間通過Binder的方式傳遞。


SurfaceFlinger 和 Hardware Composer

擁有圖形數據buffer是美好的事,讓他在你的設備屏幕顯示出來會是更好的事情。這就需要SurfaceFlinger和Hardware Composer HAL。

SurfaceFlinger的作用就是從多頭源那裏接收buffer數據,合成他們併發往顯示器。曾幾何時,這個動作是由軟件把數據貼到硬件幀buffer來完成的,但這已是很久

以前的事情。


當一個應用調到前景,WindowManager服務就會要求SurfaceFlinger來繪製表面。SurfaceFlinger會創建圖層,它的主要構件是BufferQueue,對於這個BufferQueue,

SurfaceFlinger是作爲消費者角色。一個生產者的Binder 對象被從WindowManager傳到應用,藉助此Binder可以讓應用開始發送幀到SurfaceFlinger。(注意:

WindowManager 使用術語 ”window“ 而不是 ”Layer“,”Layer" 對其來說意味着其他東西。我們將會暫時的使用SurfaceFlinger術語。對於SurfaceFlinger應該實際被叫做

LayerFlinger這點是存在爭議的)


對於大多數應用,任何時候在屏幕上總是存在三個層:位於屏幕頂部的狀態欄,位於屏幕底部的導航欄,和應用自身的UI。其他一些應用會比這多或少。

舉個例子:默認的Home應用有一個分離的層作爲牆紙,而全屏遊戲也許會隱藏狀態欄。每一個圖層都可以獨立刷新。狀態欄和導航欄由系統進程來着色,

而應用圖層則由app着色,兩者之間無協調。


設備顯示總是在一定頻率下刷新的,典型的手機、平板是60幀每秒。如果顯示內容以一半頻率刷新,”tearing(撕裂)“就很明顯了。所以,在兩個刷新間隔間更新內容

顯得很重要。系統會接收來自顯示器的信號,通知其可以安全更新內容。因爲歷史原因,我們都稱這個爲 VSYNC 信號。


刷新率也許會隨時間變化,舉個例子,一些移動設備會在58到62之間變動,依賴於當前的情況。對於HDMI付着的電視,這個理論上下降到24到48幀率來適應視頻。

因爲我們只能在一個刷新間隔更新屏幕一次,以200的幀率提交buffer給顯示器將會是浪費,因爲大部分的幀從不會看得到。SurfaceFlinger只有當顯示器準備好接收新東西之

後纔會喚醒,而不是應用一提交buffer就開始執行。


當VSYNC信號到達,SurfaceFlinger會遍歷它的圖層列表來找新buffer(提交)。如果找到一個新的,則獲取出來。如果沒,他繼續使用之前獲取的buffer。SurfaceFlinger

總是要有東西去顯示,故此它總會掛着一個buffer。如果從沒有圖層上的buffer提交,則此圖層會被忽略。


一旦SurfaceFlinger收集到所有可見圖層的buffer,它會詢問Hardware Composer如何來組合。


Hardware Composer

Hardware Composer HAL ("HWC") 首次在Android 3.0被引進,經過數年已經變得很穩定了。它主要的目的是選擇最高效的途徑來合成buffer。作爲HAL,它的實現是依賴

設備的,並通常由OEM顯示硬件廠家完成。


這個方法的價值在於可以很容易的識別你該什麼時候用”Overlay planes“。Overlay plane的使用目的就是把多個buffer在顯示器而不是GPU那裏合成起來。舉個例子,假設你

有一臺Android手機豎着擺,屏幕上有狀態欄和導航欄以及其他地方是應用UI。每一個圖層的內容都在分開的buffer裏。你可以先把應用的內容畫好在一個草稿圖層

那裏,接着把狀態欄的圖層着色,接着是導航欄,最後把草稿圖層的buffer發往顯示設備。又或者,你可以把三個buffer都送到顯示設備硬件,然後告知它從三個不同的buffer

獲取內容,在屏幕的不同部分進行着色。明顯,最後的辦法更高效。


正如你所想的,不同顯示設備的處理能力明顯有區別。overlay的數量,圖層是否可旋轉或混合,以及對位置和重疊的束縛會比較困難通過API來展現。因此HWC如下工作:

  1. SurfaceFlinger提供給HWC一個完整的圖層列表,並詢問”你將如何處理它“
  2. HWC通過在每個圖層上標明”重疊 overlay"或”GLES 合成“來響應。
  3. SurfaceFlinger來處理所有的GLES合成,把輸出buffer發給HWC並讓HWC處理剩餘的事情
因爲這個決定的代碼是可以由硬件提供商客製化,故此這是最有可能獲取最高表現。

當屏幕沒有更新的情況下,overlay plane比GL合成顯得更低效率。這尤其在overlay的內容有透明像素,並且重疊圖層混合在一起時候。這種情況,HWC可以選擇請求GLES
來合成一些或全部圖層,同時保留合成的buffer。如果SurfaceFlinger又回來要求合成同樣的buffer組時候,HWC可以僅僅只顯示之前合成好的草稿buffer。這可以提高設備待
機時候的電池壽命。

安裝Android 4.4的設備一般支持四個overlay plane。嘗試合成比存在的圖層更多的圖層會導致系統使用GLES來合成部分圖層。因此,應用使用的圖層數量可以對電池消耗以
及性能只有有數的影響。

你可以通過adb shell dumpsys Surfaceflinger的命令來確切的顯示SurfaceFlinger的工作情況。結果比較冗長,與我們目前關於HWC討論相關的內容部分是在結果的底部:

<pre class="prettyprint" name="code" style="white-space: pre-wrap; word-wrap: break-word; font-size: 13px; margin-top: 0px; margin-bottom: 1em; color: rgb(0, 102, 0); line-height: 19px; padding: 1em; overflow: auto; border: 1px solid rgb(221, 221, 221); background-color: rgb(247, 247, 247);"><span class="pln" style="color: rgb(0, 0, 0);">    type    </span><span class="pun" style="color: rgb(102, 102, 0);">|</span><span class="pln" style="color: rgb(0, 0, 0);">          source crop              </span><span class="pun" style="color: rgb(102, 102, 0);">|</span><span class="pln" style="color: rgb(0, 0, 0);">           frame           name
</span><span class="pun" style="color: rgb(102, 102, 0);">------------+-----------------------------------+--------------------------------</span><span class="pln" style="color: rgb(0, 0, 0);">
        HWC </span><span class="pun" style="color: rgb(102, 102, 0);">|</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="pun" style="color: rgb(102, 102, 0);">[</span><span class="pln" style="color: rgb(0, 0, 0);">    </span><span class="lit" style="color: rgb(0, 102, 102);">0.0</span><span class="pun" style="color: rgb(102, 102, 0);">,</span><span class="pln" style="color: rgb(0, 0, 0);">    </span><span class="lit" style="color: rgb(0, 102, 102);">0.0</span><span class="pun" style="color: rgb(102, 102, 0);">,</span><span class="pln" style="color: rgb(0, 0, 0);">  </span><span class="lit" style="color: rgb(0, 102, 102);">320.0</span><span class="pun" style="color: rgb(102, 102, 0);">,</span><span class="pln" style="color: rgb(0, 0, 0);">  </span><span class="lit" style="color: rgb(0, 102, 102);">240.0</span><span class="pun" style="color: rgb(102, 102, 0);">]</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="pun" style="color: rgb(102, 102, 0);">|</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="pun" style="color: rgb(102, 102, 0);">[</span><span class="pln" style="color: rgb(0, 0, 0);">   </span><span class="lit" style="color: rgb(0, 102, 102);">48</span><span class="pun" style="color: rgb(102, 102, 0);">,</span><span class="pln" style="color: rgb(0, 0, 0);">  </span><span class="lit" style="color: rgb(0, 102, 102);">411</span><span class="pun" style="color: rgb(102, 102, 0);">,</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="lit" style="color: rgb(0, 102, 102);">1032</span><span class="pun" style="color: rgb(102, 102, 0);">,</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="lit" style="color: rgb(0, 102, 102);">1149</span><span class="pun" style="color: rgb(102, 102, 0);">]</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="typ" style="color: rgb(102, 0, 102);">SurfaceView</span><span class="pln" style="color: rgb(0, 0, 0);">
        HWC </span><span class="pun" style="color: rgb(102, 102, 0);">|</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="pun" style="color: rgb(102, 102, 0);">[</span><span class="pln" style="color: rgb(0, 0, 0);">    </span><span class="lit" style="color: rgb(0, 102, 102);">0.0</span><span class="pun" style="color: rgb(102, 102, 0);">,</span><span class="pln" style="color: rgb(0, 0, 0);">   </span><span class="lit" style="color: rgb(0, 102, 102);">75.0</span><span class="pun" style="color: rgb(102, 102, 0);">,</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="lit" style="color: rgb(0, 102, 102);">1080.0</span><span class="pun" style="color: rgb(102, 102, 0);">,</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="lit" style="color: rgb(0, 102, 102);">1776.0</span><span class="pun" style="color: rgb(102, 102, 0);">]</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="pun" style="color: rgb(102, 102, 0);">|</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="pun" style="color: rgb(102, 102, 0);">[</span><span class="pln" style="color: rgb(0, 0, 0);">    </span><span class="lit" style="color: rgb(0, 102, 102);">0</span><span class="pun" style="color: rgb(102, 102, 0);">,</span><span class="pln" style="color: rgb(0, 0, 0);">   </span><span class="lit" style="color: rgb(0, 102, 102);">75</span><span class="pun" style="color: rgb(102, 102, 0);">,</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="lit" style="color: rgb(0, 102, 102);">1080</span><span class="pun" style="color: rgb(102, 102, 0);">,</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="lit" style="color: rgb(0, 102, 102);">1776</span><span class="pun" style="color: rgb(102, 102, 0);">]</span><span class="pln" style="color: rgb(0, 0, 0);"> com</span><span class="pun" style="color: rgb(102, 102, 0);">.</span><span class="pln" style="color: rgb(0, 0, 0);">android</span><span class="pun" style="color: rgb(102, 102, 0);">.</span><span class="pln" style="color: rgb(0, 0, 0);">grafika</span><span class="pun" style="color: rgb(102, 102, 0);">/</span><span class="pln" style="color: rgb(0, 0, 0);">com</span><span class="pun" style="color: rgb(102, 102, 0);">.</span><span class="pln" style="color: rgb(0, 0, 0);">android</span><span class="pun" style="color: rgb(102, 102, 0);">.</span><span class="pln" style="color: rgb(0, 0, 0);">grafika</span><span class="pun" style="color: rgb(102, 102, 0);">.</span><span class="typ" style="color: rgb(102, 0, 102);">PlayMovieSurfaceActivity</span><span class="pln" style="color: rgb(0, 0, 0);">
        HWC </span><span class="pun" style="color: rgb(102, 102, 0);">|</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="pun" style="color: rgb(102, 102, 0);">[</span><span class="pln" style="color: rgb(0, 0, 0);">    </span><span class="lit" style="color: rgb(0, 102, 102);">0.0</span><span class="pun" style="color: rgb(102, 102, 0);">,</span><span class="pln" style="color: rgb(0, 0, 0);">    </span><span class="lit" style="color: rgb(0, 102, 102);">0.0</span><span class="pun" style="color: rgb(102, 102, 0);">,</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="lit" style="color: rgb(0, 102, 102);">1080.0</span><span class="pun" style="color: rgb(102, 102, 0);">,</span><span class="pln" style="color: rgb(0, 0, 0);">   </span><span class="lit" style="color: rgb(0, 102, 102);">75.0</span><span class="pun" style="color: rgb(102, 102, 0);">]</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="pun" style="color: rgb(102, 102, 0);">|</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="pun" style="color: rgb(102, 102, 0);">[</span><span class="pln" style="color: rgb(0, 0, 0);">    </span><span class="lit" style="color: rgb(0, 102, 102);">0</span><span class="pun" style="color: rgb(102, 102, 0);">,</span><span class="pln" style="color: rgb(0, 0, 0);">    </span><span class="lit" style="color: rgb(0, 102, 102);">0</span><span class="pun" style="color: rgb(102, 102, 0);">,</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="lit" style="color: rgb(0, 102, 102);">1080</span><span class="pun" style="color: rgb(102, 102, 0);">,</span><span class="pln" style="color: rgb(0, 0, 0);">   </span><span class="lit" style="color: rgb(0, 102, 102);">75</span><span class="pun" style="color: rgb(102, 102, 0);">]</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="typ" style="color: rgb(102, 0, 102);">StatusBar</span><span class="pln" style="color: rgb(0, 0, 0);">
        HWC </span><span class="pun" style="color: rgb(102, 102, 0);">|</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="pun" style="color: rgb(102, 102, 0);">[</span><span class="pln" style="color: rgb(0, 0, 0);">    </span><span class="lit" style="color: rgb(0, 102, 102);">0.0</span><span class="pun" style="color: rgb(102, 102, 0);">,</span><span class="pln" style="color: rgb(0, 0, 0);">    </span><span class="lit" style="color: rgb(0, 102, 102);">0.0</span><span class="pun" style="color: rgb(102, 102, 0);">,</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="lit" style="color: rgb(0, 102, 102);">1080.0</span><span class="pun" style="color: rgb(102, 102, 0);">,</span><span class="pln" style="color: rgb(0, 0, 0);">  </span><span class="lit" style="color: rgb(0, 102, 102);">144.0</span><span class="pun" style="color: rgb(102, 102, 0);">]</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="pun" style="color: rgb(102, 102, 0);">|</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="pun" style="color: rgb(102, 102, 0);">[</span><span class="pln" style="color: rgb(0, 0, 0);">    </span><span class="lit" style="color: rgb(0, 102, 102);">0</span><span class="pun" style="color: rgb(102, 102, 0);">,</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="lit" style="color: rgb(0, 102, 102);">1776</span><span class="pun" style="color: rgb(102, 102, 0);">,</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="lit" style="color: rgb(0, 102, 102);">1080</span><span class="pun" style="color: rgb(102, 102, 0);">,</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="lit" style="color: rgb(0, 102, 102);">1920</span><span class="pun" style="color: rgb(102, 102, 0);">]</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="typ" style="color: rgb(102, 0, 102);">NavigationBar</span><span class="pln" style="color: rgb(0, 0, 0);">
  FB TARGET </span><span class="pun" style="color: rgb(102, 102, 0);">|</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="pun" style="color: rgb(102, 102, 0);">[</span><span class="pln" style="color: rgb(0, 0, 0);">    </span><span class="lit" style="color: rgb(0, 102, 102);">0.0</span><span class="pun" style="color: rgb(102, 102, 0);">,</span><span class="pln" style="color: rgb(0, 0, 0);">    </span><span class="lit" style="color: rgb(0, 102, 102);">0.0</span><span class="pun" style="color: rgb(102, 102, 0);">,</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="lit" style="color: rgb(0, 102, 102);">1080.0</span><span class="pun" style="color: rgb(102, 102, 0);">,</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="lit" style="color: rgb(0, 102, 102);">1920.0</span><span class="pun" style="color: rgb(102, 102, 0);">]</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="pun" style="color: rgb(102, 102, 0);">|</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="pun" style="color: rgb(102, 102, 0);">[</span><span class="pln" style="color: rgb(0, 0, 0);">    </span><span class="lit" style="color: rgb(0, 102, 102);">0</span><span class="pun" style="color: rgb(102, 102, 0);">,</span><span class="pln" style="color: rgb(0, 0, 0);">    </span><span class="lit" style="color: rgb(0, 102, 102);">0</span><span class="pun" style="color: rgb(102, 102, 0);">,</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="lit" style="color: rgb(0, 102, 102);">1080</span><span class="pun" style="color: rgb(102, 102, 0);">,</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="lit" style="color: rgb(0, 102, 102);">1920</span><span class="pun" style="color: rgb(102, 102, 0);">]</span><span class="pln" style="color: rgb(0, 0, 0);"> HWC_FRAMEBUFFER_TARGET</span>

這表明了屏幕上有哪些圖層,這些圖層是否被Overlay(HWC)處理或OpenGL ES合成(GLES)處理,以及其他一堆你大概不關心的數據(句柄,提示,標誌以及其他我們
已經截去的一些信息) ”source crop“和”frame“的價值將會在遲點更緊密的調查。

FB_TARGET layer是GLES合成的輸出地方。因爲上述所有圖層使用Overlays,所以FB_TARGET沒有在這個幀上使用。圖層的名字暗示着它原有的用途:在設備上以 
/dev/graphics/fb0存在,無Overlays,所有組合都在GLES完成,並且輸出會寫到幀buffer。最近的設備,一般說來都沒有簡單的幀buffer,所以FB_TARGET 圖層就是草稿
buffer。(注意:這就是爲什麼舊版本開發的屏幕捕抓應用無法再湊效:因爲他們嘗試去讀幀buffer,但並不存在這的東西)

Overlay Plane還有其他重要的作用:他們是唯一的方式來顯示DRM內容。DRM保護的buffer不能由SurfaceFlinger或GLES驅動訪問,意味着如果由HWC切換到GLES組合,你的視頻將會不可見。


三個Buffer的必要性

爲了避免存在顯示的撕裂情況,系統需要雙buffer:當後buffer準備時候,前buffer就顯示。在VSYNC信號這個點,如果後buffer準備好了,你就要迅速的切換他們。這個在你可

以直接在幀buffer上繪畫的系統上是可行的,但當加入合成步驟時候,就會出現顯示停頓。因爲SurfaceFlinger的觸發方式,會導致雙buffer管道中出現氣泡。


假設第N幀正被顯示,第N+1幀已經由SurfaceFlinger取出準備在下一個VSYNC信號發給顯示器。(假設幀N是由Overlay來組合的,因此我們不能在顯示器沒用完此Buffer的情況下更改Buffer的內容。)當VSYNC信號到達時,HWC交替buffer。當應用正開始着色第N+2幀到曾經存儲幀N的Buffer時,SurfaceFlinger正掃描圖層列表,尋求更新的

Buffer。SurfaceFlinger此時是不會找到任何新Buffer,故此打算準備在下一個VSYNC信號來時又顯示N+1幀。過後,應用完成了N+2的着色,並準備入列給SurfaceFlinger,但

已經太遲了。這效果相當於砍了最大幀率的一半。


我們可以通過三個Buffer來彌補。在VSYNC信號前,幀N正在顯示,幀N+1已經合成好(或安排給一個Overlay Plane)並且準備顯示,幀N+2已經列隊準備被SurfaceFlinger獲

取。當屏幕翻轉,Buffer通過無氣泡階段旋轉。應用只有少於一個VSYNC的時間來做着色和入列。SurfaceFlinger/HWC在離下一個翻轉時刻之前進行合成時擁有一個完整的

VSYNC時間。下面圖顯示了對於任何應用想在屏幕上顯示東西,至少要花去兩個VSYNC週期。隨着時延的增加,設備會認爲對觸摸輸入的響應減少了。


圖1. SurfaceFlinger + BufferQueue


上圖描述了SurfaceFlinger和BufferQueue的流程。

  1. 紅Buffer填滿後,滑向BufferQueue
  2. 在紅Buffer離開應用後,藍Buffer滑進來,替換紅Buffer
  3. 綠Buffer和系統UI滑進HWC(顯示SurfaceFlinger依然擁有這些Buffer,但現在HWC已經準備把他們通過疊加方式在下一個VSYNC信號時送到屏幕)
藍色Buffer即被顯示器引用,也被BufferQueue引用。應用app在同步圍欄響應前不允許對他進行着色。

在VSYNC信號刻,所以下面的動作同時發生:
  • 紅色Buffer躍進SurfaceFlinger,替代綠Buffer
  • 綠Buffer跳進顯示,替代藍Buffer,虛線綠色對出現在BufferQueue(替代之前的藍Buffer)
  • 藍色Buffer的圍欄發出信號,應用app中的藍Buffer清空
  • 顯示矩形那裏從(藍Buffer+系統UI)變成(綠Buffer+系統UI)
*系統UI的處理提供狀態欄和導航欄,在這裏沒變化。因此SurfaceFlinger保持之前獲取的Buffer。實際上,會有兩個Buffer,一個給狀態欄,一個給導航欄,
他們的大小剛好符合內容。每一個都會入列到自有的BufferQueue裏。

**Buffer實際上並不”空“。如果你沒對它進行任何繪畫處理而提交它,則你會獲得一樣的藍色內容。清除Buffer的結果是”空“,這是應用app在開始畫之前需要執行的動作。

我們可以減少延遲,通過意識到圖層組合不會需要全部的VSYNC信號週期。如果合成是在Overlays那裏執行,基本耗費零CPU和GPU時間。但我們不能靠這個,因此我們需要
一點時間。如果應用app在VSYNC信號間隔的半途中開始着色,同時SurfaceFlinger把HWC設置推遲到下一個VSYNC信號即將到達前的數微秒時,我們可以把延遲從2幀降到
1.5幀。理論上你可以着色和組合在一個週期完成,允許返回到Buffer隊列去;但讓其降那麼低,目前設備來說很難做到。在着色,組合以及切換overlays到GLES組合的時間只
要出現輕微的波動,就可以引起我們錯過Buffer切換的時間限制,只好重複前一幀。

SurfaceFlinger的Buffer處理示範了之前提及的基於圍欄的Buffer管理。如果我們需要全速播放動畫,我們需要一個獲取好的Buffer作爲顯示(”前“),和一個獲取好的Buffer作爲
下一個交替的Buffer(”後“)。如果我們在overlay顯示Buffer,則內容會直接由顯示訪問並不允許被觸動。但如果你看在dumpsys SurfaceFlinger的輸出的處於激活狀態圖層
的BufferQueue,你可以看到一個獲取出的Buffer,一個排隊的Buffer,和一個空Buffer。這是因爲當SurfaceFlinger獲取一個新”後“Buffer時,它要釋放當前的”前“Buffer到隊
列。”前“Buffer還在被顯示使用中,因此任何取出Buffer的東西都要等待圍欄信號達到後才能在其上繪畫。只要每一個人都遵循圍欄規則,所有的管理隊列的IPC消息可以與顯
示併發發生。

虛擬顯示

SurfaceFlinger支持一個”主“顯示,就是內建於你手機或平板上的,同時支持一個“外部”顯示,如通過HDMI連接的電視。它也支持一定數量的“虛擬”顯示,這些顯示使合成的
輸出存在系統中。虛擬顯示可以被用來錄製屏幕或發送到網絡。

虛擬顯示可以與主顯示共享同一套圖層,也可以有自己的一套圖層。虛擬顯示沒有VSYNC,因此主顯示的VSYNC可被用來作爲所有顯示合成動作的觸發信號。

過去,虛擬顯示總是由GLES組合。Hardware Composer只給主顯示管理合成。在Android 4.4系統,Hardware Composer擁有參與虛擬顯示合成的權利。

如你所想的,由虛擬顯示產生的幀都寫到一個BufferQueue裏。

案例學習:screenrecord

現在,我們已經擁有了BufferQueue和SurfaceFlinger的一些背景知識,對於檢視實際的案例很有幫助。

screenrecord命令,在Android 4.4引入,允許你去記錄出現在屏幕的任何東西並作爲.mp4格式文件存於磁盤。要實現這個功能,我們要接收來自SurfaceFlinger的合成幀,並
把它們送到視頻編碼器,然後把編碼後的數據寫入到文件。視頻編碼器由一個獨立的進程管理,稱爲“mediaserver”,因此我們需要在系統中移動大量的圖形buffer。爲了增加
挑戰,我們嘗試以全分辨率記錄60fps頻率的視頻。這裏高效工作的關鍵是BufferQueue。

MediaCodec 類允許應用app提供存在於buffer的原始數據,或通過surface的方式提供。我們遲點才仔細討論Surface,現在只要把它假設爲在BufferQueue一端的生產者封裝。
當screenrecord請求訪問視頻編碼器時,mediaserver創建一個BufferQueue並把自己作爲它一端的消費者,然後把BufferQueue的生產者一端作爲Surface發回給
screenreocrd。

screenrecord命令然後要求SurfaceFlinger創建一個虛擬顯示,鏡像於主顯示(就是擁有主顯示的所有圖層),並引導其把輸出發送到來自Mediaserver的Surface。
注意,這裏SurfaceFlinger是Buffer的生產者而不是消費者。

一旦配置完成,screenrecord就可以坐着等待編碼數據的出現。當應用app繪製時候,他們的buffer發送到SurfaceFlinger,由它合成到一個buffer發送到mediaserver上的視頻
編碼器。所有幀對於screenrecord來說是透明的。mediaserver有其自己的方法來移動buffer,通過句柄的方式來減少負載。

案例學習:模擬第二個顯示

WindowManager可以請求SurfaceFlinger創建一個可視圖層,使SurfaceFlinger作爲BufferQueue的消費者。同樣也可以要求SurfaceFlinger創建一個虛擬顯示,
使SurfaceFlinger作爲生產者。如果把這兩者聯合起碼,會如何呢?配置一個虛擬顯示來着色虛擬圖層?

你創建了一個閉環,在這裏,合成的屏幕出現在一個窗口裏。當然,這個窗口現在是合成輸出的一部分,因此在下一個刷新,在窗口的合成圖像會顯示窗口的內容。這就是
“馱着一隻馱着烏龜的烏龜羣”。你可以在設置中激活"Developer option",選擇模擬第二顯示,並激活一個窗口。作爲加分點,我們用screenrecord來捕抓使能顯示,來一幀
一幀的播放。

Surface 和 SurfaceHolder

Surface類從1.0開始就作爲公共API。它的描述簡單表述爲 ” 指向一個即將被屏幕合成的原生buffer的句柄“。這個闡述初始寫下時比較準確,遠遠落後於現代系統的標識。
Surface常作爲Buffer隊列的生產者(但不是總是),由SurfaceFlinger消費。當你在Surface着色時,結果置於一個buffer並被傳遞到消費者那裏。Surface不是簡單的一個
你可以在上面塗畫的內存塊。

顯示Surface的BufferQueue典型的被配置爲三Buffer;但Buffer是要求時才創建。所以一旦生產者產生Buffer慢--也許在60fps的顯示系統中產生30fps的動畫---在隊列中也許就只有2個Buffer。這可以有助於減少內存消耗。你可以在dumpsys SurfaceFlinger的結果裏看到與每個圖層相關的Buffer概要。

Canvas Rendering (畫布着色)
曾經,所有的着色都是軟件執行,現在你依然也可以這樣做。底層的實現是由Skia圖形庫提供。如果你想畫一個長方形,你可以調用庫函數,正確地在buffer上設置字節。
要保證一個buffer不會由兩個客戶端來同時更新,或者在顯示時刻去被寫進內容,你必須在訪問時候對其上鎖。lockCanvas 對Buffer上鎖並返回一個Canvas來用作畫畫。
unlockcanvasAndPost解鎖Buffer並把其發送給合成器。

隨着時間推移,帶有通用能力的3D引擎設備出現了,Android針對OpenGL ES調整自己。然而,對於應用,框架代碼,保持舊API能工作是很重要的。因此有些工作必須在
硬件加速的Canvas的API上努力。就如你在Harware Acceleration頁面看到的,這個道路有點崎嶇。要特別注意,提供給View的onView方法的Canvas也許是硬件加速,但在
app應用直接用lockCanvas對Surface上鎖獲得的Canvas從來不是。

當你爲了Canvas訪問而對Surface上鎖時,”CPU 着色器“ 連接到BufferQueue的生產者端,並直到Surface被銷燬才解除連接。大部分其他生產者(如GLES)可以解除連接並
再次連接到Surface,但基於畫布的”CPU 着色器“則不行。這意味如果你對一個Canvas上鎖了,你不能用GLES在Surface上繪畫或從視頻解碼器那裏發送幀。

第一次生產者從BufferQueue請求Buffer時,Buffer被創建並初始化爲零。初始化是必須的,它可以避免無意的進程間數據共享。然而當你要重新使用Buffer時候,會發現之前的
內容依然出現。如果你重複的調用lockCanvas和unlockCanvasAndPost而沒有進行任何繪畫,你將會在着色過得幀內循環。

Surface上鎖/解鎖的代碼保持了一份之前着色的Buffer引用。如果當你在上鎖Surface時指定一塊髒區域,它會拷貝之前Buffer中的非髒的像素。這很大的可能Buffer會由
SurfaceFlinger或HWC處理。但既然我們僅僅讀取它,因此沒有必要等待來獨佔訪問。

應用直接在Surface上繪畫的主要非Canvas方法是通過OpenGL ES。這會在EGLSurface 和 OpenGL ES章節闡述。

SurfaceHolder

與Surface一起工作的東西需要一個SurfaceHolder,尤其是SurfaceView。原本的概念是,Surface代表原始的合成器管理的buffer,而SurfaceHolder由應用app管理,並跟蹤

更高一層信息,如維度和格式。Java語言定義與底層本地實現是對應的。可以說,這樣分割不再有用,但這成爲公共API的一部分很久了。


一般說來,任何與View相關的,都涉及一個SurfaceHolder。一些其他APIs,如MediaCodec,會自己在Surface上操作。你可以很容易的從SurfaceHolder獲取Surface,

以便當你擁有它的時候可以傳遞給後者。


設置和獲取Surface參數,如大小,格式的API通過SurfaceHolder來實現。


EGLSurface和OpenGL ES

OpenGL ES定義了着色圖形的API。它不是定義窗口系統。爲了允許GLES可以在多平臺上使用,它被設計成與一個知道如何通過操作系統創建和訪問窗口的庫結合。Android

用的庫叫做EGL。如果你要畫多邊形圖,你調用GLES;如果你要把你着色的圖放到屏幕,你調用EGL。


在你使用GLES做任何事前,必須創建一個GL上下文。在EGL中,這意味着創建一個EGLContext和EGLSurface。GLES操作作用與當前上下文,此上下文是由線程本地存儲訪

問而不是作爲一個參數來傳遞。這意味着你必須注意你當前着色代碼執行所在的線程,以及在那個線程上是那個上下文。


EGLSurface可以是一個由EGL創建的離屏Buffer(稱爲”pbuffer“)或一個由操作系統創建的窗口。EGL窗口surface通過eglCreateWindowSurface函數創建的。它以”window 

object“ 爲參數,在android上可以是SurfaceView,SurfaceTexture,SurfaceHolder或Surface,所有這些內裏都有一個BufferQueue。當你調用此函數,EGL創建一個新的EGLSurface對象,把其連接到窗口對象的BufferQueue的生產者接口。從那個時候開始,着色那個EGLSurface會使一個Buffer被出列,着色,然後被消費者入列使用。

(術語”window“ 表明被期待的使用,但記住的是,輸出不一定是最終出現在顯示器)


EGL 並不提供上鎖/解鎖的調用。相反,你發起繪畫命令,然後調用eglSwapBuffers函數來提交當前幀。這個方法名字來自前後Buffer交換的傳統,但實際上已經完全不一樣

了。

同一時刻僅有一個EGLSurface可以被關聯到一個Surface---你只能有一個生產者連接到BufferQueue-----但如果你銷燬了EGLSurface,它會解除與BufferQueue的連接,並

允許其他來連接。


一個給定的線程可以通過改變”current“來在多個EGLSurface之間切換。一個EGLSurface必須是隻屬於一個線程在某一個時刻的current。


最普遍的錯誤就是當考慮到EGLSurface時,假設它只是Surface的另一面(像SurfaceHolder)。它是相關但完全獨立的概念。你可以在沒有Surface支持的EGLSurface上

繪畫,也可以在沒有EGL下使用Surface。EGLSurface僅僅是給GLES一個地方去畫。


ANativeWindow

公共的Surface類是在Java語言下實現的。在C/C++環境下,與之等價的是一個ANativeWindow 類, 部分由Android NDK暴露。你可以調用ANativeWindow_FromSurface函數

來從Surface獲得ANativeWindow。就像是它在Java環境下的表弟,你可以對其上鎖,在軟件方式下繪畫,然後解鎖並投遞。


要從本地化代碼中創建一個EGL window surface,你必須傳遞一個EGLNativeWindowType實例到eglCreateWindowSurface函數那裏。EGLNativeWindowType只

是ANativeWindow的同義詞,所以你可以隨意的cast一方到一方。


事實上,基本的”native window“類型僅僅是對BufferQueue的生產者端的一個包裝,不應大驚小怪。


SurfaceView和GLSurfaceView

到此,我們都瀏覽了底層的構件,是時後看看他們如何適應於app所基於的上層結構。

Android應用UI框架是基於對從View開始的分層。大部分的細節不會影響這裏討論,但有助瞭解UI元素把自己填進矩形區域所經歷複雜的測量和佈局過程。當應用被推到

前面時候,所有可視的View對象都渲染到由WindowManager設置的SurfaceFlinger創建的Surface。佈局以及渲染都是由應用的UI線程執行的。


不管你有多少個佈局和View,所有的事情都渲染到一個簡單的緩存。不管View是否硬件加速的,這都是正確的。


SurfaceView採取跟其他View一樣的參數,因此你可以給予它一個位置,大小,並給他配上其他元素。當要着色的時候,它內容卻完全是透明的。SurfaceView的View部分僅僅

是一個透明的佔位符。


當SurfaceView的View部分即將可見時,框架會要求WindowManager去要求SurfaceFlinger創建一個新的Surface。(這不是同步發生的,這就是爲什麼你必須提供一個回調

函數來通知你Surface創建完成)默認地,新Surface被置於應用app UI Surface之後,但默認的”Z-ordering“可以被覆蓋來使Surface置於頂部。


無論你着色什麼哦那個西到這個Surface,最終會由SurfaceFlinger合成,而不是由應用app。SurfaceView真正實力是:你獲取的Surface可以由分離的線程或分離的進程來着色,與由app UI執行的任何着色動作是隔離的,並且Buffer直接跑到SurfaceFlinger。你不能完全忽視UI線程--你依然要協調Activity的生命週期,還有你也許需要因爲大小或位置

的改變而做適當調整---但你自己擁有一整個Surface,由應用appUI來混合,其他圖層則交由Hardware Composer完成。


這個新Surface是BufferQueue的生產者,SurfaceFlinger是BufferQueue的消費者,花點時間注意這點是很值得的。你可以用任何可以提供給BufferQueue的機制來更新Surface

。你可以:使用Surface提供的Canvas函數,依附一個EGLSurface和GLES來在其上繪畫,並配置一個MediaCodec視頻解碼器來對它寫操作。


Composition 和 Hardware Scaler(合成與硬件縮放)

現在,我們有了更多一點的上下文了,回頭看看dumpsys SurfaceFlinger結果中我們之前忽略的幾項很有幫助。回到Hardware Composer討論,我們看下面的輸出結果:

<pre class="prettyprint" name="code" style="white-space: pre-wrap; word-wrap: break-word; font-size: 13px; margin-top: 0px; margin-bottom: 1em; color: rgb(0, 102, 0); line-height: 19px; padding: 1em; overflow: auto; border: 1px solid rgb(221, 221, 221); background-color: rgb(247, 247, 247);"><span class="pln" style="color: rgb(0, 0, 0);">     </span><span class="pun" style="color: rgb(102, 102, 0);">|</span><span class="pln" style="color: rgb(0, 0, 0);">           frame           name
</span><span class="pun" style="color: rgb(102, 102, 0);">------------+-----------------------------------+--------------------------------</span><span class="pln" style="color: rgb(0, 0, 0);">
        HWC </span><span class="pun" style="color: rgb(102, 102, 0);">|</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="pun" style="color: rgb(102, 102, 0);">[</span><span class="pln" style="color: rgb(0, 0, 0);">    </span><span class="lit" style="color: rgb(0, 102, 102);">0.0</span><span class="pun" style="color: rgb(102, 102, 0);">,</span><span class="pln" style="color: rgb(0, 0, 0);">    </span><span class="lit" style="color: rgb(0, 102, 102);">0.0</span><span class="pun" style="color: rgb(102, 102, 0);">,</span><span class="pln" style="color: rgb(0, 0, 0);">  </span><span class="lit" style="color: rgb(0, 102, 102);">320.0</span><span class="pun" style="color: rgb(102, 102, 0);">,</span><span class="pln" style="color: rgb(0, 0, 0);">  </span><span class="lit" style="color: rgb(0, 102, 102);">240.0</span><span class="pun" style="color: rgb(102, 102, 0);">]</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="pun" style="color: rgb(102, 102, 0);">|</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="pun" style="color: rgb(102, 102, 0);">[</span><span class="pln" style="color: rgb(0, 0, 0);">   </span><span class="lit" style="color: rgb(0, 102, 102);">48</span><span class="pun" style="color: rgb(102, 102, 0);">,</span><span class="pln" style="color: rgb(0, 0, 0);">  </span><span class="lit" style="color: rgb(0, 102, 102);">411</span><span class="pun" style="color: rgb(102, 102, 0);">,</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="lit" style="color: rgb(0, 102, 102);">1032</span><span class="pun" style="color: rgb(102, 102, 0);">,</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="lit" style="color: rgb(0, 102, 102);">1149</span><span class="pun" style="color: rgb(102, 102, 0);">]</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="typ" style="color: rgb(102, 0, 102);">SurfaceView</span><span class="pln" style="color: rgb(0, 0, 0);">
        HWC </span><span class="pun" style="color: rgb(102, 102, 0);">|</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="pun" style="color: rgb(102, 102, 0);">[</span><span class="pln" style="color: rgb(0, 0, 0);">    </span><span class="lit" style="color: rgb(0, 102, 102);">0.0</span><span class="pun" style="color: rgb(102, 102, 0);">,</span><span class="pln" style="color: rgb(0, 0, 0);">   </span><span class="lit" style="color: rgb(0, 102, 102);">75.0</span><span class="pun" style="color: rgb(102, 102, 0);">,</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="lit" style="color: rgb(0, 102, 102);">1080.0</span><span class="pun" style="color: rgb(102, 102, 0);">,</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="lit" style="color: rgb(0, 102, 102);">1776.0</span><span class="pun" style="color: rgb(102, 102, 0);">]</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="pun" style="color: rgb(102, 102, 0);">|</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="pun" style="color: rgb(102, 102, 0);">[</span><span class="pln" style="color: rgb(0, 0, 0);">    </span><span class="lit" style="color: rgb(0, 102, 102);">0</span><span class="pun" style="color: rgb(102, 102, 0);">,</span><span class="pln" style="color: rgb(0, 0, 0);">   </span><span class="lit" style="color: rgb(0, 102, 102);">75</span><span class="pun" style="color: rgb(102, 102, 0);">,</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="lit" style="color: rgb(0, 102, 102);">1080</span><span class="pun" style="color: rgb(102, 102, 0);">,</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="lit" style="color: rgb(0, 102, 102);">1776</span><span class="pun" style="color: rgb(102, 102, 0);">]</span><span class="pln" style="color: rgb(0, 0, 0);"> com</span><span class="pun" style="color: rgb(102, 102, 0);">.</span><span class="pln" style="color: rgb(0, 0, 0);">android</span><span class="pun" style="color: rgb(102, 102, 0);">.</span><span class="pln" style="color: rgb(0, 0, 0);">grafika</span><span class="pun" style="color: rgb(102, 102, 0);">/</span><span class="pln" style="color: rgb(0, 0, 0);">com</span><span class="pun" style="color: rgb(102, 102, 0);">.</span><span class="pln" style="color: rgb(0, 0, 0);">android</span><span class="pun" style="color: rgb(102, 102, 0);">.</span><span class="pln" style="color: rgb(0, 0, 0);">grafika</span><span class="pun" style="color: rgb(102, 102, 0);">.</span><span class="typ" style="color: rgb(102, 0, 102);">PlayMovieSurfaceActivity</span><span class="pln" style="color: rgb(0, 0, 0);">
        HWC </span><span class="pun" style="color: rgb(102, 102, 0);">|</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="pun" style="color: rgb(102, 102, 0);">[</span><span class="pln" style="color: rgb(0, 0, 0);">    </span><span class="lit" style="color: rgb(0, 102, 102);">0.0</span><span class="pun" style="color: rgb(102, 102, 0);">,</span><span class="pln" style="color: rgb(0, 0, 0);">    </span><span class="lit" style="color: rgb(0, 102, 102);">0.0</span><span class="pun" style="color: rgb(102, 102, 0);">,</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="lit" style="color: rgb(0, 102, 102);">1080.0</span><span class="pun" style="color: rgb(102, 102, 0);">,</span><span class="pln" style="color: rgb(0, 0, 0);">   </span><span class="lit" style="color: rgb(0, 102, 102);">75.0</span><span class="pun" style="color: rgb(102, 102, 0);">]</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="pun" style="color: rgb(102, 102, 0);">|</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="pun" style="color: rgb(102, 102, 0);">[</span><span class="pln" style="color: rgb(0, 0, 0);">    </span><span class="lit" style="color: rgb(0, 102, 102);">0</span><span class="pun" style="color: rgb(102, 102, 0);">,</span><span class="pln" style="color: rgb(0, 0, 0);">    </span><span class="lit" style="color: rgb(0, 102, 102);">0</span><span class="pun" style="color: rgb(102, 102, 0);">,</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="lit" style="color: rgb(0, 102, 102);">1080</span><span class="pun" style="color: rgb(102, 102, 0);">,</span><span class="pln" style="color: rgb(0, 0, 0);">   </span><span class="lit" style="color: rgb(0, 102, 102);">75</span><span class="pun" style="color: rgb(102, 102, 0);">]</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="typ" style="color: rgb(102, 0, 102);">StatusBar</span><span class="pln" style="color: rgb(0, 0, 0);">
        HWC </span><span class="pun" style="color: rgb(102, 102, 0);">|</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="pun" style="color: rgb(102, 102, 0);">[</span><span class="pln" style="color: rgb(0, 0, 0);">    </span><span class="lit" style="color: rgb(0, 102, 102);">0.0</span><span class="pun" style="color: rgb(102, 102, 0);">,</span><span class="pln" style="color: rgb(0, 0, 0);">    </span><span class="lit" style="color: rgb(0, 102, 102);">0.0</span><span class="pun" style="color: rgb(102, 102, 0);">,</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="lit" style="color: rgb(0, 102, 102);">1080.0</span><span class="pun" style="color: rgb(102, 102, 0);">,</span><span class="pln" style="color: rgb(0, 0, 0);">  </span><span class="lit" style="color: rgb(0, 102, 102);">144.0</span><span class="pun" style="color: rgb(102, 102, 0);">]</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="pun" style="color: rgb(102, 102, 0);">|</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="pun" style="color: rgb(102, 102, 0);">[</span><span class="pln" style="color: rgb(0, 0, 0);">    </span><span class="lit" style="color: rgb(0, 102, 102);">0</span><span class="pun" style="color: rgb(102, 102, 0);">,</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="lit" style="color: rgb(0, 102, 102);">1776</span><span class="pun" style="color: rgb(102, 102, 0);">,</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="lit" style="color: rgb(0, 102, 102);">1080</span><span class="pun" style="color: rgb(102, 102, 0);">,</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="lit" style="color: rgb(0, 102, 102);">1920</span><span class="pun" style="color: rgb(102, 102, 0);">]</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="typ" style="color: rgb(102, 0, 102);">NavigationBar</span><span class="pln" style="color: rgb(0, 0, 0);">
  FB TARGET </span><span class="pun" style="color: rgb(102, 102, 0);">|</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="pun" style="color: rgb(102, 102, 0);">[</span><span class="pln" style="color: rgb(0, 0, 0);">    </span><span class="lit" style="color: rgb(0, 102, 102);">0.0</span><span class="pun" style="color: rgb(102, 102, 0);">,</span><span class="pln" style="color: rgb(0, 0, 0);">    </span><span class="lit" style="color: rgb(0, 102, 102);">0.0</span><span class="pun" style="color: rgb(102, 102, 0);">,</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="lit" style="color: rgb(0, 102, 102);">1080.0</span><span class="pun" style="color: rgb(102, 102, 0);">,</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="lit" style="color: rgb(0, 102, 102);">1920.0</span><span class="pun" style="color: rgb(102, 102, 0);">]</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="pun" style="color: rgb(102, 102, 0);">|</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="pun" style="color: rgb(102, 102, 0);">[</span><span class="pln" style="color: rgb(0, 0, 0);">    </span><span class="lit" style="color: rgb(0, 102, 102);">0</span><span class="pun" style="color: rgb(102, 102, 0);">,</span><span class="pln" style="color: rgb(0, 0, 0);">    </span><span class="lit" style="color: rgb(0, 102, 102);">0</span><span class="pun" style="color: rgb(102, 102, 0);">,</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="lit" style="color: rgb(0, 102, 102);">1080</span><span class="pun" style="color: rgb(102, 102, 0);">,</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="lit" style="color: rgb(0, 102, 102);">1920</span><span class="pun" style="color: rgb(102, 102, 0);">]</span><span class="pln" style="color: rgb(0, 0, 0);"> HWC_FRAMEBUFFER_TARGET</span>

這個結果是在Portrait模式下使用Nexus5機器,用GraFika的播放視頻來播放視頻時候捕抓的。注意,這裏的表是按照從後到前的順序排列的:SurfceView的Surface在後面,
應用的UI層在它之上,接着是狀態欄和導航欄在最頂層。視頻的分辨率是 QVGA(320X240)。

”source crop"表明了SurfaceFlinger準備顯示Surface的Buffer的部分內容。應用app UI被賦予一個和全屏(1080X1920)一樣大小的Surface。但被狀態欄和導航欄所遮擋的點不會被着色,所以源被裁減到一個從距離頂部75像素開始,到離底部144像素的矩形框內。狀態欄和導航欄有比較小的Surface,所以Source Crop描述了一個以左上(0,0)點開始擴展到其內容的矩形。

“frame”是在顯示上像素所在的矩形。對於app UI層,frame適配source crop,這是因爲我們把顯示區域大小的層的一個內容拷貝到另外一個顯示區域大小圖層的相同位置。
對於狀態欄和導航欄,幀矩形框大小是一樣的。但內容被調整,以便導航欄可以在屏幕底部顯示。

現在考慮標有“SurfaceView”的圖層持有我們的視頻。因爲MediaCodec解碼器(buffer生產者)從隊列取出該大小的buffer,所以SurfaceFlinger知道source crop適配視頻大
小。這幀矩形有完全不一樣的大小---984X738

SurfaceFlinger通過縮放buffer的內容來適應幀矩形達到處理大小的區別,如需要,放大或縮小。選擇這個大小,是因爲這個大小的比例剛好如視頻的4:3,並且是在View佈局限
制下儘可能寬。(爲了美學,還包含了一些在屏幕邊緣的填充)

如果你在同一個Surface播放不同的視頻,底層的BufferQueue會自動重新申請新大小的緩存,並且SurfaceFlinger會調整source Crop。如果視頻方向比例不一樣,應用會被迫
去重佈局View來適應它,從而使WindowManager通知SurfaceFlinger更新幀矩形。

如果你通過一些其他方式,假設GLES,來在Surface上着色,你可以用SurfaceHolder#setFixedSize函數來設置Surface大小。你可以,舉例,配置一個遊戲總在1280X720上着色這會明顯降低在2560X1440分辨率或4K 電視用於顯示的像素數目。顯示處理器負責縮放處理。如果不希望遊戲變得奇怪,你可以通過設置最小邊爲720像素,但長邊依比例設置來調整遊戲的外在比例。你可以看Grafika的“Hardware Scaler Exerciser”的Activity例子。


GLSurfaceView

GLSurfaceView類提供了幫助類來幫助管理EGL上下文,線程內溝通,與Activity生命週期的互動。你並不需要使用GLSurfaceView來使用GLES。

舉個例子,GLSurfaceView創建了一個線程來着色以及配置EGL上下文。當activity暫停時,此狀態自動清除。大部分應用不需要了解EGL的任何事情才能伴隨GLSurfaceView
來使用GLES。

大部分情況下,GLSurfaceView是非常有用的,並且能與GLES工作變得更簡單。在某些情況下,它會成爲阻礙。如果有用,則用,如果不,則不用。

SurfaceTexture

SurfaceTexure類是一個相對新來者,在Android3.0引入。如SurfaceView是Surface和View的結合,SurfaceTexture差不多是Surface和GLES Texture的結合。

當你創建一個SurfaceTexture,你創建了一個BufferQueue,並且其應用app作爲消費端。當新Buffer被生產者入列,你的應用app會由回調函數被通知(onFrameAvailable)。

你的應用app調用updateTextImage來釋放之前持有的Buffer,並從隊列請求一個新Buffer,並調用EGL的一些函數來使Buffer作爲外部texture對於GLES可用。


外部texture(GL_TEXTURE_EXTERNAL_OES)與由GLES(GL_TEXTURE_2D)創建的texture並相當一樣。你必須有點區別的配置你的着色器,並且有些事情你不能讓它們做

。但關鍵點是:你可以直接從你BufferQueue接收到的數據來着色紋理化的多邊形。


你也許想知道,我們如何可以保證Buffer的數據格式是GLES可以辨別的東西---gralloc 支持廣泛的格式。當SurfaceTexture 創建 BufferQueue時,它設置消費者使用標誌爲

GRALLOC_USAGE_HW_TEXTURE,確保任何由gralloc創建的Buffer對GLES有用。


因爲SurfaceTexture與EGL上下文交互,你必須小心地從合適線程去調用它的方法。這個在類文檔中被提出。


如果你更深入的閱讀類文檔,你也許會發現一些奇怪的調用。一個取時間戳,其他取變形矩陣,每一個的值都由前一個調用updateTexImage來設置。這證明了BufferQueue

不單單傳遞緩存句柄,而是更多的到消費者。每一個緩存都伴隨時間戳以及變形參數。


爲了效率,提供了轉換功能。在一些案例,源數據對於消費者也許是“錯誤”擺向;但我們不是先旋轉它在發送,相反我們可以把數據以目前的擺向並伴隨一個修正它的轉換來發

送。轉換矩陣可以與其他在已用的數據點上的轉換融合,這可以減少負載。


時間戳對於特定的Buffer源很有用。舉個例子,假設你把攝像頭的輸出連接到生產者接口(SetPreviewWith)。如果你想創建一個視頻,你必須爲每一幀設置呈現的時間戳;但

你希望時間戳基於幀被捕獲的時間,不是被你應用app接收Buffer的時間。隨Buffer提供的時間戳是由攝像頭代碼設置,導致一系列更加一致的時間戳。


SurfaceTexture和Surface

如果你仔細查看API,你將發現對於應用,只有一個路徑來創建一個簡單的surface,那就是通過把SurfaceTexture作爲唯一構造函數的參數來創建。(API11之前,根本沒有Surface的公共構造函數)。如果你把SurfaceTexture視爲Surface和Texture的結合,這也許有一點落伍了。


在引擎蓋子下面,SurfaceTexture被稱爲GLConsumer,更準確的反應了其作爲BufferQueue的消費者以及擁有者的角色。當你創建來自SurfaceTexture的Surface時,你正在做的事情是創建一個代表SurfaceTexture的BufferQueue生產者一方的對象。


案例學習:Grafika的“Continuous Capture” Activity


攝像頭可以提供適合錄製電影的幀流。如果你要在屏幕顯示,你創建一個SurfaceView,傳Surface給setPreviewDisplay,並讓生產者(攝像頭)與消費者(SurfaceFlinger)

做餘下的事情。如果你要錄製視頻,你調用MediaCodec的createInputSurface創建Surface,傳給攝像頭,並又一次你可以坐在那裏休息一下。如果你同時既要顯示視頻又要

錄製,那你就參與多點。


“Continuous capture” 應用顯示來自Camera中正在錄製的Video。這個案例,編碼的視頻被寫到內存中的環形Buffer,並可以隨時寫到磁盤裏。只要你跟蹤到每一個東西的位

置,那麼實現是簡單的。

有三個BufferQueue參與。應用app使用SurfaceTexture接收來自攝像頭的幀,並轉換爲一個外部的GLES texture。應用app聲明瞭一個SurfaceView,我們用它來顯示幀,並且我

們用一個輸入surface配置MediaCodec編碼器來創建視頻。因此,一個BufferQueue由app創建,一個由SurfaceFlinger,和一個由mediaserver。

Grafika continuouscapture activity

圖2. Grafika‘s continuous capture activity


在上面的圖,箭頭顯示了來自攝像頭的數據傳播方向。BufferQueue標以顏色(紫色生產者,藍綠色消費者)。注意“Camera”實際上是在mediaserver進程。


編好的H.264 視頻走向應用app進程中內存的環形buffer,然後當“capture”按鈕按下,MediaMuxer類會用來把視頻數據以.mp4文件保存到磁盤。

所有的三個BufferQueue由應用app中一個單獨的EGL上下文來處理,GLES的操作是在UI線程中執行的。在UI線程中做SurfaceView着色一般是不建議的,但因爲我們正在做的

簡單操作是由GLES渠道異步處理的,因此這樣做應該沒問題。(如果視頻編碼器上鎖了且我們屏蔽了嘗試出列一個Buffer的動作,應用app將會變得無響應。但在那點上,不管

如何我們可能就失敗了)編碼數據的處理---管理環形buffer並寫到磁盤---是由分離的線程處理的。


大部分的配置是發生在SurfaceView的回調函數 surfaceCreated上的。EGLContext被創建,且EGLSurface也爲顯示和視頻編碼器而創建。當新一幀數據到達,我們會告訴

SurfaceTexture去獲取它並讓它作爲GLES texture的存在,接着對每一個EGLSurface用GLES命令來着色(傳遞來自Surface Texture的變形參數以及時間戳)。編碼線程

從MediaCodec拉取編碼輸出,並存放到內存。


TextureView

TexureView在Android 4.0引進。這是目前這裏討論最複雜的View對象,結合了View和SurfaceTexture。


回憶一下,SurfaceTexture是一個GL消費者,消費着圖形數據buffer並讓他們作爲texture的存在。TextureView封裝了SurfaceTexture,接管了響應回調函數以及請求新Buffer的

責任。新Buffer的到來,導致了TextureView產生了一個View刷新請求。當被要求繪畫時候,TextureView使用最近接收Buffer的內容作爲數據源,並在View狀態下指明它在什麼地點和什麼方式來着色。


你可以用GLES在TextureView如向你在SurfaceView一樣着色。只要把SurfaceTexture傳遞給EGL窗口的創建調用。然而,這樣做會暴露潛在問題。


在我們看到的大部分東西里,BufferQueue已經在不同的進程中傳遞buffer。當使用GLES着色一個TextureView,生產者和消費者都在一個進程,甚至都在一個線程。假設我們

從UI線程連續快速的提交幾個Buffers。EGL buffer交換調用將需要從BufferQueue出列一個buffer,它將會停止直到有一個buffer是存在。只有等到消費者請求一個buffer來着色

時候,纔會有可以被出列的buffer存在,但這個也是在這個UI線程發生的,因此我們被卡住了。


解決方案就是讓BufferQueue保證永遠有一個Bufer存在可以被出列,以至於Buffer交換調用不會停止。一個保證這個的方法就是讓BufferQueue在新buffer入列時候丟棄之前入列的Buffer的內容,並對最新Buffer數目和最大獲取Buffer數目設置限制。(如果你的隊列有三個Buffers,所有三個Buffer都已被消費者獲取,從而沒有Buffer可以出列,緩存的

交換調用就會失敗或掛起。因此我們必須防止消費者一次獲取超過2個buffers)。丟棄Buffer通常是不可取的,因此只有在特定條件下才會如此,如消費者和生產者在同一個進程中。


SurfaceView或TextureView?

SurfaceView和TextureView都擔當類似的角色,但卻有非常不同的實現。要決定哪一個是最優,需要對權衡取捨有一定的理解。

因爲TextureView是View層級裏的一個正常公民,它表現的像其他View,可以覆蓋或被其他元素覆蓋。你可以使用簡單的API調用執行任意的變形和獲取以Bitmap格式的內容

的操作。


與TextureView最大的區別是在合成階段的表現。對於SurfaceView,內容是被寫到一個SurfaceFlinger合成好分離的圖層,原則上帶有覆蓋。對於TextureView,View的合成

總是與GLES合成的,並且更新其內容也會引發其他View元素重畫(例如,他們被置於TextureView的頂部)。在View着色完成後,應用app UI圖層必須由SurfaceFlinger來與

其他圖層合成,因此你實際上合成了每個可視像素兩次。對於全屏視頻播放器,或僅僅作爲佈局在視頻之上的UI元素的任何應用,SurfaceView提供了更好的表現。


如早期注意的,DRM保護視頻僅能在一個覆蓋面上展現。支持內容保護的視頻播放器必須以SurfaceView來實現。


案例學習:Grafika’s 播放視頻(TextureView)

Grafika包含了一對視頻播放器,一個以TextureView實現,另外一個以SurfaceView實現。視頻解碼的內容,即從MediaCodec發往一個surface的幀,對兩個都是一樣的。最

感興趣的不同實現是要求展現正確縱橫比的階段。


當SurfaceView 要求一個FrameLayout的客製化實現,通過簡單配置變形矩陣給TextureView#setTransform函數就可以更改SurfaceTexture的大小。對於前者,你正在通過

WindowManager來發送新窗口位置以及大小給SurfaceFlinger;對於後者,你只要不同方式的着色它。


否則,兩個實現方式都遵循一樣的模式。一旦Surface被創建,播放被激活。當“play”被按下,視頻解碼線程就開始,Surface作爲輸出目標。之後,應用app代碼不需要做任何

事,---合成和顯示會要麼由SurfaceFlinger(對於SurfaceView)或要麼由TextureView處理。


案例學習:Grafika‘s 雙解碼

這個應用演示了在TextureView內部操作SurfaceTexture。

這個應用的基本組織結構是一對TextureView,並排的顯示不同的視頻。爲了模擬一個視頻會議應用的需求,我們要在應用因爲方向轉變而被停止並且被恢復情況下,保持

Mediaodec解碼器活躍。技巧就是你不能在沒有完全重新配置Surface情況下更改MediaCodec解碼器要用到的Surface,而重新配置會是一個相當繁重的操作;因此我們要保持

Surface處於活躍。Surface只是一個指向SurfaceTexture的BufferQueue生產者句柄,而且SurfaceTexture由TextureView管理。因此我們也要保持SurfaceTexture處於活躍。因

此,我們如何處理TextureView的拆除呢?

很巧合,TextureView提供了一個setSurfaceTexture的函數來處理我們希望做的。我們從TextureView獲取一個SurfaceTexture的參考,並保存在一個靜態字段裏。當

應用被關閉,我們從onSurfaceTextureDestroyed返回“false”來阻止SurfaceTexture的毀滅。當應用重新開始,我們把舊的SurfaceTexture填充到新的TextureView那裏。

TextureView類處理EGL 上下文的創建和銷燬。

每一個視頻解碼器從另外一個線程來驅動。第一眼看去,它似乎向EGL需要線程本地化一樣;但記住,解碼輸出的緩存實際上正從mediaserver被髮送到我們的BufferQueue的

消費者那裏(SurfaceTexture)。TextureView 幫我們操心渲染,而且它們在UI線程那裏執行。


以SurfaceView實現的應用也許會有一點難度。我們不能只是創建一對SurfaceView,並引導輸出到它們那裏,因爲Surface會在應用方向改變時候被銷燬。除此之外,那會增加

兩個圖層,而覆蓋層的數量限制會強烈的促使我們要保持圖層在最小的數目。因此相反的,我們要創建一對SurfaceTexture來接收來自視頻解碼器的輸出,並在應用執行渲染

工作,使用GLES渲染兩個四邊形texture到SurfaceView的Surface上。


Conclusion

我們希望這個頁面可以提供對於內視Android在系統層面上處理圖形的方法有很好的幫助。一些相關話題的信息和建議可以在下面的附錄中找到。


附錄A:Game Loops

一個很流行的實現遊戲循環的方法如下:

<pre class="prettyprint" name="code" style="white-space: pre-wrap; word-wrap: break-word; font-size: 13px; margin-top: 0px; margin-bottom: 1em; color: rgb(0, 102, 0); line-height: 19px; padding: 1em; overflow: auto; border: 1px solid rgb(221, 221, 221); background-color: rgb(247, 247, 247);"><span class="kwd" style="color: rgb(0, 0, 136);">while</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="pun" style="color: rgb(102, 102, 0);">(</span><span class="pln" style="color: rgb(0, 0, 0);">playing</span><span class="pun" style="color: rgb(102, 102, 0);">)</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="pun" style="color: rgb(102, 102, 0);">{</span><span class="pln" style="color: rgb(0, 0, 0);">
    advance state </span><span class="kwd" style="color: rgb(0, 0, 136);">by</span><span class="pln" style="color: rgb(0, 0, 0);"> one frame
    render the </span><span class="kwd" style="color: rgb(0, 0, 136);">new</span><span class="pln" style="color: rgb(0, 0, 0);"> frame
    sleep </span><span class="kwd" style="color: rgb(0, 0, 136);">until</span><span class="pln" style="color: rgb(0, 0, 0);"> it</span><span class="pun" style="color: rgb(102, 102, 0);">’</span><span class="pln" style="color: rgb(0, 0, 0);">s time to </span><span class="kwd" style="color: rgb(0, 0, 136);">do</span><span class="pln" style="color: rgb(0, 0, 0);"> the </span><span class="kwd" style="color: rgb(0, 0, 136);">next</span><span class="pln" style="color: rgb(0, 0, 0);"> frame
</span><span class="pun" style="color: rgb(102, 102, 0);">}</span>

這個有一些問題,最基本的想法就是遊戲可以定義“frame”是什麼。不同的顯示會以不同的頻率刷新,以及刷新率會變化。如果你生產的幀比顯示他們的速度快,你不得不偶

爾丟棄一個。如果你生產的幀太慢,SurfaceFlinger會定期出現不能成功獲取一個新緩存,並會重新顯示前一幀。這兩個情況都會產生顯示的問題。


你所需要做的就是匹配好顯示幀率,並且依據從前一幀開始所過的時間多少來決定遊戲狀態的推進。有兩個方法來達到如此:1. 把BufferQueue填滿,並依靠來回地

“swap buffer”。2. 使用Choreographer(API 16+)


Queue Stuffing(隊列填充)

這是很容易實現: 只要能多快有多快的切換Buffer。在Android早期版本,SurfaceView#lockCanvas會讓你休眠100ms作爲懲罰。現在由BufferQueue來控制步調,並且

BufferQueue可以如SurfaceFlinger一樣快速的清空。


這種方法的例子可以在Android Breakout那裏看到。它使用GLSurfaceView,調用應用的OnDrawFrame回調然後交換Buffer來運行一個循環。如果BufferQueue是滿的,

eglSwapBuffers調用會等待直到有一個Buffer存在。當SurfaceFlinger釋放Buffers時候,它們就變得可獲取的,一般是在獲取一個新Buffer到顯示之後。因爲這個是在VSYNC

時刻發生的,你的繪畫週期時間將會匹配刷新率,大部分的。


這個方法也有一些問題。第一,app被綁在SurfaceFlinger activity,這個將依據需要做的工作多少以及是否需要與其他進程爭奪CPU時間來決定需要花費不同的時間。

既然你的遊戲狀態是依據切換緩存間隔的時間來推進,你的動畫就不會在固定的頻率刷新。當不是很一致運行在平均60fps下,但你很有可能不會察覺這個碰撞。


第二,第一對Buffer的切換會發生的非常迅速,這是因爲此時BufferQueue還不滿。幀之間計算的時間幾乎接近0,所以遊戲會產生一些不產生任何東西的幀。在遊戲中如

Breakout,會在每一次刷新時刻去更新屏幕,除了遊戲第一次啓動,隊列總是滿的,因此影響不會很明顯。遊戲經常暫停動畫,然後又回到儘可能快的模式,也許會看到奇怪的

打嗝。


Choreographer

Choreographer允許你設立會在下一個VSYNC時刻調用的回調函數。實際的VSYNC時機會作爲參數傳遞。所以即使你應用app沒有立刻喚醒,你依然有一個顯示開始刷新的

時刻計劃。使用則個值而不是當前時間,可以給你遊戲狀態更新邏輯提供一致的時間源。


不幸的,事實上在每一個VSYNC時刻後你獲得一個回調這事並不能保證你的回調被及時的執行或你不能快速的處理它。你的app要檢測落後的情況並人爲的丟棄一些幀。


在Grafika的“Record GL app” activity提供了這方面的例子。在一些設備上(Nexus 4 和 Nexus 5),如果你只坐在那裏看,那麼activity將會開始丟棄一些幀。GL 着色雖然很簡

單,但View元素常會被重繪,並且測量/佈局的傳遞在設備進入低功耗狀態下會花費很長時間。(依據systrace,Android 4.4系統上,時鐘變慢後它花費28ms而不是6ms。

如果你在屏幕上拖動你的手指,設備人爲你在於activity交互,因此時鐘會加速到比較高,你就從不會拋棄一幀)


簡單的補救就是在VSYNC時刻後如果當前時間超過N微妙,Choreographer回調就簡單的丟棄一幀。理想的N值是基於之前觀察的VSYNC的間隔。舉個例子,如果刷新週期在

16.7ms(60fps),如果你運行的滯後超過15ms,你也許會丟棄一幀。


如果你觀察“Record GL app”運行,你會看到丟棄幀計數器在增加,甚至可以在丟棄幀時候看到邊界在閃紅色。如果你的眼睛不是特別好,那麼你可能看不到動畫的斷續。在

60fps速度下,應用app會經常丟棄幀,只要動畫連續的以固定速度進行,則沒有任何人會注意這點。你能拋棄多少,在某程度上這是由你畫的內容,顯示的特性以及使用app

的人對於閃動有多敏感來決定。


Thread Management

一般來說,如果你在SurfaceView,GLSurfaceView,或 TextureView上着色,你要在指定的線程上進行。絕對不要在UI線程上做重活或一些不確定時間的事情上。

Breakout和“Record GL app” 使用指定的着色線程, 並且他們也在這個線程上更新動畫。只要遊戲狀態能迅速的刷新,這是一個很合理的方法。

其他遊戲完全的把遊戲邏輯和遊戲着色分開。如果你有一個簡單的遊戲,僅僅是每100毫秒移動一個塊,而不用做別的,你可以有一個指定的線程僅僅做如下:

<pre class="prettyprint" name="code" style="white-space: pre-wrap; word-wrap: break-word; font-size: 13px; margin-top: 0px; margin-bottom: 1em; color: rgb(0, 102, 0); line-height: 1.5; padding: 1em; overflow: auto; border: 1px solid rgb(221, 221, 221); background-color: rgb(247, 247, 247);"><span class="pln" style="color: rgb(0, 0, 0);">run</span><span class="pun" style="color: rgb(102, 102, 0);">()</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="pun" style="color: rgb(102, 102, 0);">{</span><span class="pln" style="color: rgb(0, 0, 0);">
        </span><span class="typ" style="color: rgb(102, 0, 102);">Thread</span><span class="pun" style="color: rgb(102, 102, 0);">.</span><span class="pln" style="color: rgb(0, 0, 0);">sleep</span><span class="pun" style="color: rgb(102, 102, 0);">(</span><span class="lit" style="color: rgb(0, 102, 102);">100</span><span class="pun" style="color: rgb(102, 102, 0);">);</span><span class="pln" style="color: rgb(0, 0, 0);">
        </span><span class="kwd" style="color: rgb(0, 0, 136);">synchronized</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="pun" style="color: rgb(102, 102, 0);">(</span><span class="pln" style="color: rgb(0, 0, 0);">mLock</span><span class="pun" style="color: rgb(102, 102, 0);">)</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="pun" style="color: rgb(102, 102, 0);">{</span><span class="pln" style="color: rgb(0, 0, 0);">
            moveBlock</span><span class="pun" style="color: rgb(102, 102, 0);">();</span><span class="pln" style="color: rgb(0, 0, 0);">
        </span><span class="pun" style="color: rgb(102, 102, 0);">}</span><span class="pln" style="color: rgb(0, 0, 0);">
    </span><span class="pun" style="color: rgb(102, 102, 0);">}</span>

(你也許需要以睡眠時間爲基礎的固定時鐘來防止漂移----sleep() 不是很一致, moveBlock() 接受一個非零時間---但你懂的)


當繪畫代碼喚醒,它只是抓住鎖,獲取當前位置的塊,釋放鎖並開始繪畫。與其基於inter-frame的delta時間來做分級運動,你只有有一個線程移動事物,另外一個線程在繪畫

開始後繪畫出現在任何地方的東西。

對於擁有任何複雜度的場景,你會需要創建一個由喚醒時間排序的即將到來的事件列表,並休眠直到下一個事件到達,但這是一樣的思想。


附錄B: SurfaceView 和 Activity 生命週期

當使用SurfaceView時候,從其他線程而不是UI線程來着色Surface被認爲是很實際的做法。這引起了一個關於其他線程與Activity生命週期交互的問題。


1. Application onCreate / onResume / onPause

2. Surface create / changed / destroyed


當Activity開始,你的回調按照以下次序:

  • onCreate
  • onReusme
  • surfaceCreated
  • surfaceChanged
如果你按“back”,你有:
  • onPause
  • surfaceDestroyed (在Surface即將消失時候調用)
如果你旋轉屏幕,Activity被拆除然後重建,因此你獲得一個完整週期。如果這個很重要,你可以說,這是一次通過檢測 isFinishing()函數來快速重啓。(很有可能開始或停止
Activity太快,導致SurfaceCreate() 也許實際上只在onPause()發生。

如果你按下power鍵來滅屏,你只能得到onPause()---沒有 surfaceDestroyed()。Surface要保持激活,着色纔可以進行。你甚至能保持獲取Choreographer事件,只要你繼續請求。如果你有一個鎖屏會強制不同方向,你的activity也許重新開始當設備點亮時候。但如果沒有,你會得到一個你之前擁有的surface的黑屏。

這裏引起了基本的問題,當對SurfaceView使用分離的着色線程:線程的生命週期要聯繫到activity還是surface?答案依賴於你希望在屏滅時候發生的事情而定。有兩個基本的
方式:1. 在Activity的開始/停止 那裏 開始/停止線程。 2. 在Surface的創建/銷燬那裏開始/停止線程。

#1 與app生命週期交互的很好。我們在onResume那裏啓動着色線程,並在onPause那裏停止它。當創建和配置這個線程時,這方法有點笨拙。因爲某些時候,Surface已經
存在,有些時候,它不存在(例子,它在按下power鍵使屏幕開關時刻依然處於激活)。我們不得不等待surface被創建後才能配置和初始化線程,但我們不能簡單的在
surfaceCreate回調函數裏來做,因爲如果Surface並沒有重新創建,這個回調不會被調用。因此我們需要詢問或緩存Surface的狀態,並送到着色進程那裏。只,我們必須要
小心在線程間傳遞對象----最好通過消息處理器來傳遞Surface或SurfaceHolder,而不是直接填進線程裏,以求避免在多核系統中發生什麼(Android SMP Prime)

#2 有一定的吸引力,因爲Surface和着色器邏輯上是互聯的。我們在Surface創建後啓動線程,避免了對一些線程間通信的關注。Surface 創建/改變消息簡單的投遞。我們需
要確保當屏幕熄滅時候,着色停止,在屏幕亮起時恢復;讓Choreographer停止調用幀繪畫回調是一個簡單的事情。我們的onResume函數會需要恢復回調函數,僅當着色線
程在運行。這也許沒那麼簡單---如果我們基於幀間消逝的時間來做動畫,我們可以在下一個事件到來時,有非常大的間隙;因此,一個顯示的暫停/恢復 消息也許是合要求
的。


上述都是主要關注着色線程如何配置以及是否它是運行的。一個相關的關注是提取當activity被殺死時,來自線程的狀態。(in onPuase 或 onScreenInstantceState), 方法

 #1 會比較好,因爲一旦着色線程被連接上,他的狀態可以不需要同步原語就能訪問。

你可以看方法#2 在Grafik的“Hardware scaler exercise”的例子


附錄C:使用systrace跟蹤BufferQueue

如果你真的需要了解圖像Buffer如何周圍移動,你需要使用systrace。系統層的圖形代碼結構化很好,如大多數相關的應用框架代碼一樣。使能“gfx”和“view”標籤,同時一般的

“sched"。

對如何高效使用systrace的全部描述會填充相當長的文檔。一個顯著的項就是在trace中出現的BufferQueue。如果你之前用過systrace,你可能會看到他們,但卻不肯定他們是

什麼。作爲例子,如果你在Grafik的”play video“跑的時候抓到一份trace,你可以看到一行標有”SurfaceView“。這行告訴你在給定時刻有多少個buffer在隊列中。


當app激活時,你會注意到這個值在增加---觸發Mediacoder解碼器渲染幀-----以及當SurfaceFlinger工作時候,消費Buffer時候,這個值在減少。如果你以30fps顯示視頻,隊列

的值會從0到1變化,因爲60fps頻率的顯示可以很容易的跟上源。(你也將注意到SurfaceFlinger僅僅在有事情做的時候才喚醒,並不是60次沒秒。系統儘量避免工作,在沒有

更新屏幕時刻會完全停止VSYNC)


如果你打開”play video“並抓到一份新trace,你會看到一行有很長名字的的一行(com.android.grafika/com.android.grafika.PlayMovieActivity"),這是主UI圖層,無疑這是另一

個BufferQueue。因爲TextureView着色到UI圖層,而不是另外一個圖層,你會在這裏看到所有的視頻驅動的更新。



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