Android——圖形系統

    本文來自 http://dragon.leanote.com/post/Android%E5%9B%BE%E5%BD%A2%E7%B3%BB%E7%BB%9F-II-%E6%9E%B6%E6%9E%84 的譯文~感謝原博主


名詞

Display: 顯示屏
HWC:Hardware Composer ,硬件合成器
HAL:Hardware Abstract Layer,硬件抽象層
Overlay plane: 疊加平面
Buffer: 緩衝區
BufferQueue:緩衝區隊列
layer:層,繪圖層
surface:表面
texture: 紋理
frame:幀,圖像幀,視頻幀,顯示幀。


前言

本文描述了android系統級別的圖形架構的核心元素,以及應用框架和多媒體系統如何使用這些元素。本文關注圖形數據的緩衝區如何在整個系統中移動。如果你也好奇 爲什麼SurfaceView和TextureView的行爲,或者Surface和EGLSurface間的交互,你來對地方了。

假定你熟悉android設備和應用,你無需瞭解應用框架的細節,本文僅提及了少數幾個API,不過本文與其他公開文檔沒什麼重疊。本文的目的讓讀者瞭解參與渲染一個輸出楨的典型幾個事件,因此你可以在設計時有所選擇。爲了做到這一點,我們由底至上描述UI 類如何工作,而不是它們的用法。

前面的幾節包含了一些背景材料,因此建議不要跳過。我們從解釋android圖形緩衝區開始,描述合成及顯示機制,轉到支持數據合成的高級機制。

本文主要基於Android4.4。本文中提及的代碼來自於AOSP或者Grafika,一個開源測試項目。

ANativeWindow class 是NDK提供的C++版本的Surface class。例如,你可以調用ANativeWindow_fromSurface從一個surface來獲取ANativeWindow實例。如同在java中一樣,你可以對其執行lock、render、unlock並post。
實際上,基本的native window 類型,僅僅是對生產者側的BudderQueue的一個封裝。

BufferQueue and gralloc

Android圖形的核心就是BufferQueue類,其職責很簡單:連接 生產者(產生圖形數據緩衝)到 消費者(接收圖形數據緩衝以顯示或者進一步處理)。生產者和消費者可以處於不同的進程。系統中,幾乎所有的圖形數據緩衝區的移動都依賴於BufferQueue。

基本用法很簡單:生產者請求一個自由緩衝區(dequeueBuffer()),指定參數集(長、寬、高、格式以及一套用法標誌)。生產者填充緩衝區然後將其返回到隊列(queueBuffer())。隨後,消費者獲得緩衝區(acquireBuufer()),並使用其中的內容。當消費者處理完畢,將緩衝區返回到隊列(releaseBuffer())。

大多數新的Android設備支持“同步框架”。在與能夠異步操作圖形數據的硬件組件合作市,該框架容許 系統乾的很漂亮。例如:一個生產者可以提交一系列的OpenGL ES繪製命令,然後在渲染完成前就可以將輸出緩衝壓入隊列中。緩衝區伴隨一個fence,用於當內容準備好時發出信號。當buffer返回到自由緩衝區列表時,將伴隨第二個fence,這樣即使緩衝區的內容可能仍在使用中時,消費者也可以釋放緩衝區。這種方法改善了Buffer在系統中移動的延遲和帶寬。

隊列的某些參數,例如所持有緩衝區的最大數據,由生產者和消費者共同決定。

BufferQueue 負責在需要時分配緩衝區。已分配緩衝區將盡量保留,除非某些參數發生改變:例如,如果請求的緩衝區尺寸變化,舊緩衝區將被釋放,重新分配符合需要的緩衝區。

BufferQueue 目前總是由消費者來創建和“所有”。在Android4.3中,消費者必須與BufferQueue處於同一進程中,而生產者卻可以在另外的進程中,依賴binder訪問BufferQueue。不過在4.4中,實現卻更加通用化了一點。

緩衝區總是通過handle來傳遞的。緩衝區內容不會被BufferQueue拷來拷去,因爲這樣移動數據非常沒效率。

gralloc HAL

實際的緩衝區分配是由內存分配器gralloc完成。gralloc 通過廠商相關的HAL接口來實現。alloc(…)的參數如你所料:長、寬、高、格式以及一套用法標誌。注意,這些標誌值得好好關注。

gralloc分配器不僅僅是在本地堆上分配內存的一種方式。在某些情形下,分配的內存可能不是緩存一致的(cache-coherent),或者是用戶空間不可訪問的,這取決於這些標誌,例如:

  • 軟件訪問這些內存的頻率(CPU)
  • 硬件訪問這些內存的頻率(GPU)
  • 是否被用作 OpenGL ES(GLES) 紋理
  • 是否被視頻解碼器使用

例如,如果你的格式是RGBA8888,並且你指明這塊緩衝區將由軟件訪問(也就是說你的應用將直接修改像素),那麼gralloc將分配一塊緩衝區,其像素點大小爲4字節,格式爲RGBA。相反,如果你指定該緩衝區只能被硬件訪問,用作GLES紋理,gralloc將按照GLES的驅動來行事了:BGRA格式,非線性swizzled 佈局,可選顏色格式,等等;提供硬件所期望的格式將改善性能。

特定的平臺上,某些標誌是有排斥性的。例如,video encoder標誌可能要求 YUX像素,因此與“software access”、RGBA888就不相容。

gralloc返回緩衝區句柄 可以通過binder在進程間傳遞。

SurfaceFlinger and Hardware Composer

SurfaceFlinger的角色是從從多個生產者接收圖形數據緩衝區,合成並將結果送到Display。早期版本 中,SurfaceFlinger將數據傳送到硬件FrameBuffer(/dev/graphics/fb0),不過現在完全不一樣了。

當應用來到前臺,WindowManager服務向SurfaceFlinger申請一個繪圖surface。SurfaceFlinger創建一個“layer”,layer的主要成員就是BufferQueue,而SurfaceFlinger就是BufferQueue的消費者。一個Binder對象從消費者側經由WindowManager傳遞給應用,應用可以使用該Binder對象將幀直接發送給SurfaceFlinger。

berg:在Android4.3中,這個binder對象實際上就是Surface對象,包含一個ISurface指針,以及一個ISurfaceTexture指針。

  1. 注意:WindowManager使用術語“windows”而不是“layer”,在WindowManager中“layer”代表其他東東。我們還是使用SurfaceFlinger的術語,有些人也爭論說SurfaceFlinger應該爲 LayerFlinger

對於大多數應用,通常屏幕上存在三個layer:狀態條、導航條,以及應用UI。 當然也有例外,HOME應用就有一個單獨的用於牆紙的layer,而全屏遊戲會藏起狀態條。注意,每個layer都可以獨立更新。

設備Display以某個固定的速率刷新,通常在手機和平板上是60HZ。爲避免tear現象,因此僅在週期內刷新內容是非常重要的。系統使用VSYNC信號,保證對內容刷新的安全性。

刷新速率 可能隨情況變化,某些移動設備根據當前條件,其刷新率在58fps-62fps。對於HDML 電視,理論上,刷新率爲24-48HZ以便匹配視頻。由於我們只能在一個刷新週期內刷新平率,因此,以200fps來提交緩衝區其實是個浪費,因爲大部分幀都看不到。因此,與其在app提交緩衝區時立刻行動,SurfaceFlinger僅僅在Display準備好才被喚醒。

當VSYNC信號到達,SurfaceFlinger檢查它的layer列表,看看有沒有新的緩衝區。如果有,獲取;否則,直接使用之前獲取的緩衝區。SurfaceFlinger 總是希望有點啥可以顯示,因此它總是抓着一塊緩衝區。如果沒有緩衝區提交到某個layer上,這個layer直接被無視。

一旦SurfaceFlinger收集到可視的幾個layer的所有緩衝區時,它詢問硬件合成器HWC 應該怎樣執行合成。

Hardware Composer

HWC 在Android3.0中被首次引入,這些年穩步發展。其主要目的是根據可用硬件選擇 最有效率的緩衝合成方式。作爲一個HAL,其實現是設備相關的,通常由Display硬件的OEM來實現。

The value of this approach is easy to recognize when you consider “overlay planes.”這種方法的價值在於,容易識別出 什麼時候你應該考慮採用“overlay planes”。overlay plane 的目的是在Display硬件中而不是在GPU中合成多個緩衝區。例如,如果你有一個豎直方向的手機,狀態條、導航條以及應用內容這三個layer的內容存在於分離的緩衝區內。你可以 將這三個緩衝區的內容依次渲染疊加到一個新的草稿(scratch)緩衝區中,然後傳遞給Display硬件;更有效率的方法是:你直接將這三塊緩衝區傳遞給Display硬件,告訴它應該將每個緩衝區顯示在屏幕的哪個區域。

如你所想,不同Display處理器的能力很不同。overlay的數目、是否可以被旋轉和混合、位置和overlap的限制,這些能力難於通過某個API來表達。因此,HWC的工作方式是這樣的:

  • SurfaceFlinger 提供給HWC 一個完整的layer列表,然後詢問:“你希望如何處理這些layer”
  • 作爲迴應,HWC 將每個layer標記爲“overlay”或者 “GLES composition”
  • SurfaceFlinger處理任何GLES合成工作,將輸出緩衝傳遞給HWC,然後讓HWC完成剩下的工作。

由於決策代碼有硬件提供商來定製,因此可能發揮硬件的最大性能。

當屏幕沒什麼改變時,overlay planes 可能性能低於GL合成。當overlay內容中含有透明像素且overlapping layers被混合在一起時,這種說法一定程度上正確。在這樣的場合中,HWC可以選擇要求GLES合成 某些或者全部的layer,然後保留合成的緩衝區。如果SurfaceFlinger回頭再次請求同樣的buffer列表,HWC可以僅僅繼續顯示之前已經合成的保留下來的草稿(scratch)緩衝區。這可以提高空閒設備的電池壽命。

Android4.4的設備通常支持四個overlay plane。如果試圖合成更多的layer,會導致系統使用GLES合成部分layer; 因此,應用使用的layer數目,將對電源消耗和性能有明顯的影響。

命令adb shell dumpsys SurfaceFlinger 可以查看SurfaceFlinger的詳細信息,HWC的信息在輸出的尾部:

type source crop frame name
HWC [0.0, 0.0, 320.0, 240.0] |[48, 411, 1032, 1149] SurfaceView
HWC [0.0, 75.0, 1080.0, 1776.0] |[0, 75, 1080, 1776] com.android.grafika/com.android.grafika.PlayMovieSurfaceActivity
HWC [0.0, 0.0, 1080.0, 75.0] |[0, 0, 1080, 75] StatusBar
HWC [0.0, 0.0, 1080.0, 144.0] |[0, 1776, 1080, 1920] NavigationBar
FB TARGET [ 0.0, 0.0, 1080.0, 1920.0] | [ 0, 0, 1080, 1920] HWC_FRAMEBUFFER_TARGET

這些信息告訴你,屏幕上有哪些layer,是否由overlay(HWC) 或者 OpenGL ES (GLES)合成,以及一些其它你可能不關注的信息。稍後,將檢查“source crop”和“frame”的值。

GLES合成器輸出到FB_TARGET layer 上。既然上面所有的layer都使用overlay,FC_TARGET 在當前幀中並沒有使用。FB_TARGET的名字指示了它之前的角色:在沒有overlay支持而使用/dev/graphics/fb0的設備中,所有的合成工作都有GLES來完成,合成的結果將寫到framebuffer中。而最近的設備中,通常沒有簡單的framebuffer,因此FB_TARGET實際上是一個草稿(scratch)緩衝區。

  1. 注意:這就是爲什麼舊版本的屏幕截圖器不能工作的原因:它們試圖讀取framebuffer,而這個東東現在沒有了。

overlay plane 還有另外一個重要的角色:它們是能顯示DRM內容的唯一方式。DRM 保護的緩衝區不能夠被SurfaceFlinger或者GLES驅動訪問,這意味着,如果HWC切換到GLES合成,你的視頻就沒顯示了。

The Need for Triple-Buffering

。。。。

上圖描述了SurfaceFlinger和BufferQueue的流程,在幀期間During frame:

  1. 紅色緩衝區填充,滑動到BufferQueue
  2. 當紅色緩衝區離開應用後,藍色緩衝區滑入,替代紅色緩衝區
  3. 綠色緩衝區 以及陰影所示系統UI 滑入 HWC(圖上SurfaceFlinger仍然有這些緩衝區,但是現在HWC 已經準備將他們用於下一個VSYNC到達的顯示,通過overlay).

    系統UI進程提供了狀態條和導航條,爲簡化起見 這些layer沒有改動。因此,SurfaceFlinger仍繼續使用之前獲取的緩衝區。

藍色緩衝區 由Display和BufferQueue同時引用,應用目前還不容需將其用於渲染,直到對應的同步fence發出信號。

在VSYNC到達是,下列操作同時發生:

  1. 紅緩衝區 跳入 SurfaceFlinger,替代了綠色緩衝區
  2. 綠緩衝區跳入 Display,替代了藍色緩衝區。a dotted-line green twin appears in the BufferQueue綠色虛線的雙胞胎(twin) 出現在BufferQueue。

    1. berg:這裏可能說綠色緩衝區同時也通過dequeueBuffer操作放入了BufferQueue,參見《BufferQueue and gralloc》這一節。
  3. 藍色緩衝區的fence 發出信號,系統中的藍色緩衝區清空。

    1. 這裏的緩衝區並沒有實際清空;如果你沒有繪製就提交,你講得到同樣的藍色的內容。清空 實際上是清除緩衝區的內容,應用應該在開始繪製之前做清除工作。
  4. 顯示區域由 《blue+systemUI 》變爲 《green+systemUI》
    。。。。

Virtual displays

SurfaceFlinder 支持一個主顯示屏,以及一個“外部”顯示屏,例如通過HDMI連接的電視。它還支持多個“虛擬”顯示屏。虛擬顯示屏 使得合成的輸出在系統內部可用。虛擬顯示屏可以用於錄製屏幕,甚至通過網絡來發送。

虛擬顯示屏 可以與主屏分享同樣的layer集合(layer stack),或者也可由有着自己的layer集。對於虛擬顯示屏,沒有VSYNC信號,因此主屏的VSYNC信號用於觸發所有顯示屏的合成。

早期的虛擬顯示屏總是使用GLES來合成,HWC 主要管理主顯示屏的合成。在Android4.4中,HWC 也開始參與虛擬現實的合成。

正如你想到的,虛擬設備的幀也是寫到一個BufferQueue中。

Case study: screenrecord

我們已經有了BufferQueue和SurfaceFlinger的一些背景知識,下面檢驗一下。

Android4.4中引入的screenrecord 命令,容你將屏幕顯示的任何東東錄製爲MP4文件。爲了做到這一點,我們必須從SurfaceFlinger接受合成後的幀,送去視頻編碼器,然後將編碼後的視頻數據寫到文件。視頻編碼器由一個獨立的進程“mediaserver”所管理,因此我們必須將大量的圖形緩衝區在系統中跨進程移動。爲了更有挑戰性一點,我們試圖以60fps全屏錄製視頻。最有效完成該工作的關鍵是BufferQueue。

MediaCodec 類容許應用以包含原始數據的緩衝區來提交數據,也可以通過一個surface。我們稍後在討論Surface的細節,現在,我們簡單的認爲它是生產者端的BufferQueue的一個封裝。當screenrecord請求訪問一個視頻編碼器時,mediaserver將創建一個BufferQueue,將自身作爲消費者端,然後將其作爲surface傳遞給生產者。

screenrecord 然後要求SurfaceFlinger 創建一個虛擬顯示屏,直接鏡像主顯示屏,引導它將輸出發送給前面由mediaserver返回的surface。注意:這個例子中,SurfaceFlinger 是生產者而不是消費者。

一旦配置完成,screenrecord 就可以坐等編碼後的數據出現了。當應用繪製時,它們的緩衝區交付給SurfaceFlinger,後者將這些緩衝區合成爲一塊緩衝區,然後直接發送給mediaserver的視頻編碼器。screenrecord進程完全看不到這些幀。

Case study: Simulate Secondary Displays

WindowManager可以要求SurfaceFlinger創建一個可見的layer,SurfaceFlinger將作爲該layer對應的BufferQueue的消費者。WindowManager也可能要求SurfaceFlinger 創建一個虛擬顯示屏,這種情形SurfaceFlinger將作爲BufferQueue的生產者。如果你上面的layer和虛擬顯示屏連接在一起,會發生啥?

你創建了一個閉環,合成的屏幕將出現在一個window中。當然,這個window是合成輸出的一部分,因此下一次刷新時,該合成的圖像將也顯示window內容。海龜背地球。。。。

Surface and SurfaceHolder


Surface類 早就包含在API 1.0中了。其描述很簡單:一個原始緩衝區的巨涌,有屏幕合成器所管理。這段話早期是精確的,不過符合不了現代系統的標記。

Surface 代表了生產者側的BufferQueue,通常由SurfaceFlinger所消費。當你在一個surface上渲染時,其最終結果放在某個緩衝區中被送到消費者。Surface並不簡單的是一塊你可以胡亂塗改的原始內存塊。

用於顯示屏Surface的BufferQueue通常被配置爲三緩衝,不過緩衝區還是按需分配的、當生產者生成緩衝區過慢時(例如30fps的動畫 顯示在 60fps的顯示屏時),隊列中可能僅實際分配了兩塊緩衝區。這樣最小化了內存消耗。你可以通過 dumpsys SurfaceFlinger 命令來查看每個layer的緩衝區分配情況。

  1. type | handle | hint | flag | tr | blnd | format | source crop (l,t,r,b) | frame | name
  2. -----------+----------+------+------+----+------+-------------+--------------------------------+------------------------+------
  3. HWC | b583b3d0 | 0002 | 0000 | 00 | 0100 | RGBA_8888 | 0.0, 0.0, 1080.0, 1920.0 | 0, 0, 1080, 1920 | com.android.settings/com.android.settings.DevelopmentSettings
  4. HWC | b583b330 | 0002 | 0000 | 00 | 0105 | RGBA_8888 | 0.0, 0.0, 1080.0, 75.0 | 0, 0, 1080, 75 | StatusBar
  5. HWC | b583b150 | 0002 | 0000 | 00 | 0105 | RGBA_8888 | 0.0, 0.0, 1080.0, 144.0 | 0, 1776, 1080, 1920 | NavigationBar
  6. FB TARGET | b61b1880 | 0000 | 0000 | 00 | 0105 | RGBA_8888 | 0.0, 0.0, 1080.0, 1920.0 | 0, 0, 1080, 1920 | HWC_FRAMEBUFFER_TARGET
  7. Allocated buffers:
  8. 0xb583b150: 648.00 KiB | 1080 (1152) x 144 | 1 | 0x00000900
  9. 0xb583b1f0: 8640.00 KiB | 1080 (1152) x 1920 | 1 | 0x00000900
  10. 0xb583b330: 337.50 KiB | 1080 (1152) x 75 | 1 | 0x00000900
  11. 0xb583b3d0: 8640.00 KiB | 1080 (1152) x 1920 | 1 | 0x00000900
  12. 0xb583b4c0: 337.50 KiB | 1080 (1152) x 75 | 1 | 0x00000900
  13. 0xb583b560: 8640.00 KiB | 1080 (1152) x 1920 | 1 | 0x00000900
  14. 0xb583b5b0: 7200.00 KiB | 1260 (1280) x 1920 | 3 | 0x00000900
  15. 0xb583b6f0: 337.50 KiB | 1080 (1152) x 75 | 1 | 0x00000900
  16. 0xb61a33d0: 648.00 KiB | 1080 (1152) x 144 | 1 | 0x00000900
  17. 0xb61a3790: 337.50 KiB | 1080 (1152) x 75 | 1 | 0x00000900
  18. 0xb61a3f60: 8640.00 KiB | 1080 (1152) x 1920 | 1 | 0x00001a00
  19. 0xb61b1880: 8640.00 KiB | 1080 (1152) x 1920 | 1 | 0x00001a00
  20. 0xb62ee1f0: 648.00 KiB | 1080 (1152) x 144 | 1 | 0x00000900
  21. Total allocated (estimate): 53694.00 KB

Canvas Rendering

曾幾何時,所有的渲染都是軟件完成的,當前你現在還是可以這樣做,skia 圖形庫提供了底層實現。如果你希望畫一個矩形,你可以調用圖形庫,讓圖形庫去修改緩衝區。爲了保證緩衝區不會被兩個客戶端一起更新,或者 顯示時還在被寫入,你需要使用鎖。lockCanvas() 鎖定某個緩衝區,然後返回一個Canvas 用於繪圖;而unlockCanvasAndPost() 解鎖緩衝區,然後將其發送給合成器。

時代在進步,當有通用目的的3D引擎的設備出現後,Android 圍繞OpenGL ES來重定位。不過,爲應用以及應用框架保持舊的API 可工作還是很重要的,因此開始進行 對CanvasAPI進行硬件加速。你可以看看Hardware Acceleration中的一些圖,效果顯著。Note in particular that while the Canvas provided to a View’s onDraw() method may be hardware-accelerated, the Canvas obtained when an app locks a Surface directly with lockCanvas() never is. 特別是,如果 Canvas 提供給View的onDraw()方法是基於硬件加速的時候,…【 沒看懂】

當你爲訪問Canvas 而鎖住一個surface時。”CPU 渲染器“ 連接到生產者側的BufferQueue,保持,直到Surface銷燬時才摧毀連接。大多素的生產者(如GLES)可以被斷開後重連接到一個新的surface,不過基於Canvas的”CPU 渲染器“沒有重連接能力。這意味着 ,如果你已經爲canvas鎖住了一個surface,你不能使用GLES繪製該surface,或者向該surface發送某個視頻解碼器的圖像幀。

當生產者首次從BufferQueue中申請一塊緩衝區時,緩衝區被分配出來,內容清零。這種初始化是必要的,以避免進程間非故意的數據共享。當你重新使用某個緩衝區時,之前的內容還在。如果你重複調用 lockCanvas() 和 unlockCanvasAndPost()且並不繪製點啥的話,你將僅僅在之前渲染過的幀中循環而已。

Surface的lock/unlock代碼維護了先前渲染的緩衝區的引用。如果你在鎖定surface時指明一塊髒區域 ,它將從先前的緩衝區中拷貝那些“乾淨”的像素。這塊緩衝區將公平地由由SurfaceFlinger 或者 HWC 處理;不過既然我們僅需要讀取該緩衝區,這裏沒必要去等待排他性訪問。

除了Canvas之外,應用直接繪製surface的主要方式是通過OpenGL ES。這在 EGLSurface and OpenGL ES 這一節描述。

SurfaceHolder

如果工作在surface,那就需要一個SurfaceHolder,尤其是SurfaceView 。surface代表了原始的由合成器管理的緩衝區,而SurfaceHolder由應用管理,跟蹤瞭解高層信息(維度、格式、etc)。java語言的定義鏡像了底層本地實現。有爭議說這樣劃分沒啥用,不過長期以來SurfaceHolder就已經是公共API的一部反。

總的來說,任何需要處理View的主體,都牽涉到了一個SurfaceHolder。某些其他的API,如MediaCodec,將直接操作surface。你可以很容易從SurfaceHolder獲得surface的引用。

SurfaceHolder還實現了 Surface參數的讀寫API,如尺寸、格式。

EGLSurface and OpenGL ES

OpenGL ES定義了圖形渲染的一套API,而不是定義了一套window系統。爲了容許GLES 工作在各種平臺,它需要與一個瞭解如何在操作系統上創建和訪問windows的庫來協作。在Android上,該庫被稱爲EGL。如果你希望繪製紋理,你會使用GLES調用;如果你希望將你渲染結果顯示到屏幕上,你應該使用EGL調用。

在你開始使用GLES任何功能前,你需要創建一個GL 上下文。在EGL中,這疑問這創建一個EGLContext和一個EGLSurface。GLES操作將應用於當前上下文中,該上下文通過線程局部存儲來訪問,而不是通過參數來傳遞。這要求你很小心 ,你的渲染代碼在哪個線程執行,在那個線程上的是哪個上下文。

EGLSurface 可以使EGL分配的一塊離屏(off-screen)緩衝,又稱pbuffer,也可以是操作系統分配的window。EGL window surfaces 通過函數eglCreateWindowSurface()創建,該函數接收一個“window”對象作爲參數,在Android,這個對象的類型可能是SurfaceView、SurfaceTexture 、SurfaceHolder 或者 Surface – 所有的這些類型底層都有一個BufferQueue。當你調用該函數時,EGL創建一個新的EGLSurface對象,並將此對象連接到該窗口對象的BufferQueue的生產者上。從這點看,EGLSurface的渲染將導致 一塊緩衝區被 出隊列,渲染,入隊列以供消費者使用。(術語“window”指示了期望的用法,但是你需要牢記,輸出並不一定要顯示在屏幕上。)

EGL不提供 lock/unlock調用。相反,你發出繪製命令,然後調用eglSwapBuffer()來提交當前幀。這個方法的名稱來源於傳統的前後緩衝切換,但是實際的實現可能完全不同。

一個EGLSurface同一時刻只能對應於一個surface,因爲你只能將一個生產者連接到某個BufferQueue;但是,如果你銷燬了EGLSurface,它將從BufferQueue斷開,這時候就可以容許其他生產者連接到該BufferQueue了。

一個指定的線程可以通過修改“current”來在多個EGLSurface間切換。當然一個EGLSurface一次只能作爲一個線程的“current“ 。

最常見的錯誤是,當談到EGLSurface時,認爲它是Surface(例如 SurfaceHolder)的另外一面。這兩個術語是完全不同的概念:你可以在某個沒有關聯到Surface的EGLSurface上繪圖;你可以無需EGL就使用Surface;EGLSurface僅提供給GLES一個位置繪圖而已。

ANativeWindow

Surface類使用java語言實現。其C++的等效類是ANativeWindow,由NDK 公開。你可以調用ANativeWindow_fromSurface(),從Surface中獲取ANativeWindow實例,然後lock、渲染、unlock-and-post.

爲了在本地碼中創建一個EGL windows surface,你需要傳遞一個EGLNativeWindowType的實例到eglCreateWindowSurface()。EGLNativeWindowType 實際上是 ANativeWindow的同義詞。

事實上,native window 的基礎類型也僅僅是 生產者端的BufferQueue的封裝。

C++類型 Java類型
EGL nativce window surface EGLSurface
EGLNativeWindowType = ANativeWindow Surface

SurfaceView and GLSurfaceView


前面我們已經探索了低層組件,現在是時候看看它們如何裝配到應用直接使用的高層組件了。

Android應用框架UI 基於 View的繼承體系。雖然大部分細節與本討論無關,不過 這有助於理解:這些UI元素是如何經過一個複雜的測量和layout過程,最終被裝配到一個矩形區域。所有可視的View對象 被渲染到一個SurfaceFlinger創建的Surface,這個Surface在應用切換到前臺時由WindowManager設置。layout和渲染 工作是在應用的UI線程中執行的。

不管有多少layout和View,所有的東東最後被渲染到一個緩衝區,不管View是否使用了硬件加速。

SurfaceView 接收與其他View類似的參數,因此你可以傳入 位置、尺寸 以及其他什麼元素。不過,當SurfaceView開始渲染時,這些內容是完全透明的。SurfaceView的View部分僅僅是一個可以透視的佔位。

當SurfaceView 的View 組件希望變爲可視時,系統框架要求WindowManager 請求SurfaceFlinger創建一個新的surface。(這些不會同步發生,這就是爲什麼你需要提供一個回調以便在Surface創建完成後得到通知的原因)。默認情況下,新的surface 放在應用的UI surface之後,不過這個默認的z-order 可以通過將surface置頂而修改。

你在這個新surface上 渲染的任何東東最後都是由SurfaceFlinger 而不是應用自身來合成。這就是SurfaceView的真正威力:你獲取的這個surface可以在獨立的線程中或者其他進程中渲染,從而與應用UI執行的任何渲染 隔離,而且對應的buffer直接傳遞給SurfaceFlinger—你不能完全無視UI線程,因爲你仍然需要與Activity的生命週期合作,當View的尺寸或者位置修改時,你也可能需要調整點啥 —不過,你有完全屬於你一整個surface ,與應用UI和其它layer的調和將由HWC處理。

這裏值得花點時間指出:新的surface 代表 生產者側的BufferQueue,消費者則是 某個SurfaceFlinger layer。

你可以使用任何能夠寫入BufferQueue的機制來更新surface: 使用Surface-supplied Canvas函數;將一個EGLSurface連接到surface,然後使用GLES來繪圖;配置一個MediaCodec視頻解碼器來寫入surface。

Composition and the Hardware Scaler

現在我們的內容有點兒多了,有必要回去看看dumpsys SurfaceFlinger的信息。

type source crop frame name
HWC [0.0, 0.0, 320.0, 240.0] |[48, 411, 1032, 1149] SurfaceView
HWC [0.0, 75.0, 1080.0, 1776.0] |[0, 75, 1080, 1776] com.android.grafika/com.android.grafika.PlayMovieSurfaceActivity
HWC [0.0, 0.0, 1080.0, 75.0] |[0, 0, 1080, 75] StatusBar
HWC [0.0, 0.0, 1080.0, 144.0] |[0, 1776, 1080, 1920] NavigationBar
FB TARGET [ 0.0, 0.0, 1080.0, 1920.0] | [ 0, 0, 1080, 1920] HWC_FRAMEBUFFER_TARGET

這段信息 是在一臺豎屏的nexus5手機上使用Grafika’s播放QVGA 視頻時抓取的。注意,列表的順序是由後至前,SurfaceView的Surface 在最後,而應用UIlayer在前,狀態條和導航條在最前。

“source crop”指示了SurfaceFlinger 將要顯示的那部分surface緩衝區。
注意 ”SurfaceView“,該layer放置了我們的視頻內容,其source crop與視頻的尺寸一致,該尺寸 SurfaceFlinger 是瞭解的,因爲MediaCodec解碼器 使用該尺寸的緩衝區。不過,幀矩形區域的尺寸就完全不一樣了:984*738

SurfaceFlinger 通過縮放來填充幀矩形區域。如果你在同一個surface上開始播放一個不同的視頻,底層的BufferQueue將自動重新分配合適的緩衝區,SurfaceFlinger也會調整source crop。如果新視頻的縱橫比變了,應用將需要強制re-layout以便進行匹配,這會導致WindowManager告訴SurfaceFlinger刷新幀矩形。

如果你使用其他方式如GLES 來渲染surface,你可以使用SurfaceHolder#setFixedSize() 來設置surface 尺寸。舉例來講,你也可以配置一個遊戲總是以1280x720進行渲染,這將顯著降低像素數量,需要在 2560x1440 的平板或者4K顯示時,顯示處理器將負責進行縮放。

GLSurfaceView

GLSurfaceView類提供了一些輔助類,幫助管理EGL上下文,線程間通信,以及與Activity 生命週期的交互。僅此而已。爲使用GLES,GLSurfaceView也並不是必須的。

例如,GLSurfaceView創建了一個線程,用於渲染和配置EGL上下文。在Activity 暫停時,該線程將自動清除。大多數應用不必瞭解EGL 就可以GLES和GLSurfaceView。
大多數情形下,GLSurfaceView 非常有用,可以讓使用GLES更容易。如果有幫助,用;沒有,不理它。

SurfaceTexture


SurfaceTexture 是一個在Android3中引入的較新的類。正如 SurfaceView 是Surface和View的結合體,SurfaceTexture 是 Surface 和 GLES 紋理的結合體。

SurfaceTexture and Surface

Case Study: Grafika’s “Continuous Capture” Activity

TextureView


SurfaceView or TextureView?

Case Study: Grafika’s Play Video (TextureView)

Case Study: Grafika’s Double Decode

Conclusion


Appendix A: Game Loops


Queue Stuffing

Choreographer

Thread Management

Appendix B: SurfaceView and the Activity Lifecycle

Appendix C: Tracking BufferQueue with systrace

發佈了100 篇原創文章 · 獲贊 266 · 訪問量 69萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章