原文:https://source.android.com/devices/graphics/architecture.html
Display: 顯示屏
HWC:Hardware Composer ,硬件合成器
HAL:Hardware Abstract Layer,硬件抽象層
Overlay plane: 疊加平面
Buffer: 緩沖區(qū)
BufferQueue:緩沖區(qū)隊列
layer:層,繪圖層
surface:表面
texture: 紋理
frame:幀,圖像幀,視頻幀,顯示幀。
本文描述了android系統(tǒng)級別的圖形架構的核心元素,以及應用框架和多媒體系統(tǒng)如何使用這些元素。本文關注圖形數(shù)據(jù)的緩沖區(qū)如何在整個系統(tǒng)中移動。如果你也好奇 為什么SurfaceView和TextureView的行為,或者Surface和EGLSurface間的交互,你來對地方了。
假定你熟悉android設備和應用,你無需了解應用框架的細節(jié),本文僅提及了少數(shù)幾個API,不過本文與其他公開文檔沒什么重疊。本文的目的讓讀者了解參與渲染一個輸出楨的典型幾個事件,因此你可以在設計時有所選擇。為了做到這一點,我們由底至上描述UI 類如何工作,而不是它們的用法。
前面的幾節(jié)包含了一些背景材料,因此建議不要跳過。我們從解釋android圖形緩沖區(qū)開始,描述合成及顯示機制,轉到支持數(shù)據(jù)合成的高級機制。
本文主要基于Android4.4。本文中提及的代碼來自于AOSP或者Grafika,一個開源測試項目。
ANativeWindow class 是NDK提供的C++版本的Surface class。例如,你可以調用ANativeWindow_fromSurface從一個surface來獲取ANativeWindow實例。如同在java中一樣,你可以對其執(zhí)行l(wèi)ock、render、unlock并post。
實際上,基本的native window 類型,僅僅是對生產(chǎn)者側的BudderQueue的一個封裝。
Android圖形的核心就是BufferQueue類,其職責很簡單:連接 生產(chǎn)者(產(chǎn)生圖形數(shù)據(jù)緩沖)到 消費者(接收圖形數(shù)據(jù)緩沖以顯示或者進一步處理)。生產(chǎn)者和消費者可以處于不同的進程。系統(tǒng)中,幾乎所有的圖形數(shù)據(jù)緩沖區(qū)的移動都依賴于BufferQueue。
基本用法很簡單:生產(chǎn)者請求一個自由緩沖區(qū)(dequeueBuffer()),指定參數(shù)集(長、寬、高、格式以及一套用法標志)。生產(chǎn)者填充緩沖區(qū)然后將其返回到隊列(queueBuffer())。隨后,消費者獲得緩沖區(qū)(acquireBuufer()),并使用其中的內(nèi)容。當消費者處理完畢,將緩沖區(qū)返回到隊列(releaseBuffer())。
大多數(shù)新的Android設備支持“同步框架”。在與能夠異步操作圖形數(shù)據(jù)的硬件組件合作市,該框架容許 系統(tǒng)干的很漂亮。例如:一個生產(chǎn)者可以提交一系列的OpenGL ES繪制命令,然后在渲染完成前就可以將輸出緩沖壓入隊列中。緩沖區(qū)伴隨一個fence,用于當內(nèi)容準備好時發(fā)出信號。當buffer返回到自由緩沖區(qū)列表時,將伴隨第二個fence,這樣即使緩沖區(qū)的內(nèi)容可能仍在使用中時,消費者也可以釋放緩沖區(qū)。這種方法改善了Buffer在系統(tǒng)中移動的延遲和帶寬。
隊列的某些參數(shù),例如所持有緩沖區(qū)的最大數(shù)據(jù),由生產(chǎn)者和消費者共同決定。
BufferQueue 負責在需要時分配緩沖區(qū)。已分配緩沖區(qū)將盡量保留,除非某些參數(shù)發(fā)生改變:例如,如果請求的緩沖區(qū)尺寸變化,舊緩沖區(qū)將被釋放,重新分配符合需要的緩沖區(qū)。
BufferQueue 目前總是由消費者來創(chuàng)建和“所有”。在Android4.3中,消費者必須與BufferQueue處于同一進程中,而生產(chǎn)者卻可以在另外的進程中,依賴binder訪問BufferQueue。不過在4.4中,實現(xiàn)卻更加通用化了一點。
緩沖區(qū)總是通過handle來傳遞的。緩沖區(qū)內(nèi)容不會被BufferQueue拷來拷去,因為這樣移動數(shù)據(jù)非常沒效率。
實際的緩沖區(qū)分配是由內(nèi)存分配器gralloc完成。gralloc 通過廠商相關的HAL接口來實現(xiàn)。alloc(…)的參數(shù)如你所料:長、寬、高、格式以及一套用法標志。注意,這些標志值得好好關注。
gralloc分配器不僅僅是在本地堆上分配內(nèi)存的一種方式。在某些情形下,分配的內(nèi)存可能不是緩存一致的(cache-coherent),或者是用戶空間不可訪問的,這取決于這些標志,例如:
例如,如果你的格式是RGBA8888,并且你指明這塊緩沖區(qū)將由軟件訪問(也就是說你的應用將直接修改像素),那么gralloc將分配一塊緩沖區(qū),其像素點大小為4字節(jié),格式為RGBA。相反,如果你指定該緩沖區(qū)只能被硬件訪問,用作GLES紋理,gralloc將按照GLES的驅動來行事了:BGRA格式,非線性swizzled 布局,可選顏色格式,等等;提供硬件所期望的格式將改善性能。
特定的平臺上,某些標志是有排斥性的。例如,video encoder標志可能要求 YUX像素,因此與“software access”、RGBA888就不相容。
gralloc返回緩沖區(qū)句柄 可以通過binder在進程間傳遞。
SurfaceFlinger的角色是從從多個生產(chǎn)者接收圖形數(shù)據(jù)緩沖區(qū),合成并將結果送到Display。早期版本 中,SurfaceFlinger將數(shù)據(jù)傳送到硬件FrameBuffer(/dev/graphics/fb0),不過現(xiàn)在完全不一樣了。
當應用來到前臺,WindowManager服務向SurfaceFlinger申請一個繪圖surface。SurfaceFlinger創(chuàng)建一個“l(fā)ayer”,layer的主要成員就是BufferQueue,而SurfaceFlinger就是BufferQueue的消費者。一個Binder對象從消費者側經(jīng)由WindowManager傳遞給應用,應用可以使用該Binder對象將幀直接發(fā)送給SurfaceFlinger。
berg:在Android4.3中,這個binder對象實際上就是Surface對象,包含一個ISurface指針,以及一個ISurfaceTexture指針。
注意:WindowManager使用術語“windows”而不是“layer”,在WindowManager中“layer”代表其他東東。我們還是使用SurfaceFlinger的術語,有些人也爭論說SurfaceFlinger應該為 LayerFlinger。
對于大多數(shù)應用,通常屏幕上存在三個layer:狀態(tài)條、導航條,以及應用UI。 當然也有例外,HOME應用就有一個單獨的用于墻紙的layer,而全屏游戲會藏起狀態(tài)條。注意,每個layer都可以獨立更新。
設備Display以某個固定的速率刷新,通常在手機和平板上是60HZ。為避免tear現(xiàn)象,因此僅在周期內(nèi)刷新內(nèi)容是非常重要的。系統(tǒng)使用VSYNC信號,保證對內(nèi)容刷新的安全性。
刷新速率 可能隨情況變化,某些移動設備根據(jù)當前條件,其刷新率在58fps-62fps。對于HDML 電視,理論上,刷新率為24-48HZ以便匹配視頻。由于我們只能在一個刷新周期內(nèi)刷新平率,因此,以200fps來提交緩沖區(qū)其實是個浪費,因為大部分幀都看不到。因此,與其在app提交緩沖區(qū)時立刻行動,SurfaceFlinger僅僅在Display準備好才被喚醒。
當VSYNC信號到達,SurfaceFlinger檢查它的layer列表,看看有沒有新的緩沖區(qū)。如果有,獲??;否則,直接使用之前獲取的緩沖區(qū)。SurfaceFlinger 總是希望有點啥可以顯示,因此它總是抓著一塊緩沖區(qū)。如果沒有緩沖區(qū)提交到某個layer上,這個layer直接被無視。
一旦SurfaceFlinger收集到可視的幾個layer的所有緩沖區(qū)時,它詢問硬件合成器HWC 應該怎樣執(zhí)行合成。
HWC 在Android3.0中被首次引入,這些年穩(wěn)步發(fā)展。其主要目的是根據(jù)可用硬件選擇 最有效率的緩沖合成方式。作為一個HAL,其實現(xiàn)是設備相關的,通常由Display硬件的OEM來實現(xiàn)。
The value of this approach is easy to recognize when you consider “overlay planes.”這種方法的價值在于,容易識別出 什么時候你應該考慮采用“overlay planes”。overlay plane 的目的是在Display硬件中而不是在GPU中合成多個緩沖區(qū)。例如,如果你有一個豎直方向的手機,狀態(tài)條、導航條以及應用內(nèi)容這三個layer的內(nèi)容存在于分離的緩沖區(qū)內(nèi)。你可以 將這三個緩沖區(qū)的內(nèi)容依次渲染疊加到一個新的草稿(scratch)緩沖區(qū)中,然后傳遞給Display硬件;更有效率的方法是:你直接將這三塊緩沖區(qū)傳遞給Display硬件,告訴它應該將每個緩沖區(qū)顯示在屏幕的哪個區(qū)域。
如你所想,不同Display處理器的能力很不同。overlay的數(shù)目、是否可以被旋轉和混合、位置和overlap的限制,這些能力難于通過某個API來表達。因此,HWC的工作方式是這樣的:
由于決策代碼有硬件提供商來定制,因此可能發(fā)揮硬件的最大性能。
當屏幕沒什么改變時,overlay planes 可能性能低于GL合成。當overlay內(nèi)容中含有透明像素且overlapping layers被混合在一起時,這種說法一定程度上正確。在這樣的場合中,HWC可以選擇要求GLES合成 某些或者全部的layer,然后保留合成的緩沖區(qū)。如果SurfaceFlinger回頭再次請求同樣的buffer列表,HWC可以僅僅繼續(xù)顯示之前已經(jīng)合成的保留下來的草稿(scratch)緩沖區(qū)。這可以提高空閑設備的電池壽命。
Android4.4的設備通常支持四個overlay plane。如果試圖合成更多的layer,會導致系統(tǒng)使用GLES合成部分layer; 因此,應用使用的layer數(shù)目,將對電源消耗和性能有明顯的影響。
命令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,F(xiàn)C_TARGET 在當前幀中并沒有使用。FB_TARGET的名字指示了它之前的角色:在沒有overlay支持而使用/dev/graphics/fb0的設備中,所有的合成工作都有GLES來完成,合成的結果將寫到framebuffer中。而最近的設備中,通常沒有簡單的framebuffer,因此FB_TARGET實際上是一個草稿(scratch)緩沖區(qū)。
注意:這就是為什么舊版本的屏幕截圖器不能工作的原因:它們試圖讀取framebuffer,而這個東東現(xiàn)在沒有了。
overlay plane 還有另外一個重要的角色:它們是能顯示DRM內(nèi)容的唯一方式。DRM 保護的緩沖區(qū)不能夠被SurfaceFlinger或者GLES驅動訪問,這意味著,如果HWC切換到GLES合成,你的視頻就沒顯示了。
。。。。
上圖描述了SurfaceFlinger和BufferQueue的流程,在幀期間During frame:
系統(tǒng)UI進程提供了狀態(tài)條和導航條,為簡化起見 這些layer沒有改動。因此,SurfaceFlinger仍繼續(xù)使用之前獲取的緩沖區(qū)。
藍色緩沖區(qū) 由Display和BufferQueue同時引用,應用目前還不容需將其用于渲染,直到對應的同步fence發(fā)出信號。
在VSYNC到達是,下列操作同時發(fā)生:
綠緩沖區(qū)跳入 Display,替代了藍色緩沖區(qū)。a dotted-line green twin appears in the BufferQueue綠色虛線的雙胞胎(twin) 出現(xiàn)在BufferQueue。
berg:這里可能說綠色緩沖區(qū)同時也通過dequeueBuffer操作放入了BufferQueue,參見《BufferQueue and gralloc》這一節(jié)。
藍色緩沖區(qū)的fence 發(fā)出信號,系統(tǒng)中的藍色緩沖區(qū)清空。
這里的緩沖區(qū)并沒有實際清空;如果你沒有繪制就提交,你講得到同樣的藍色的內(nèi)容。清空 實際上是清除緩沖區(qū)的內(nèi)容,應用應該在開始繪制之前做清除工作。
顯示區(qū)域由 《blue+systemUI 》變?yōu)?《green+systemUI》
。。。。
SurfaceFlinder 支持一個主顯示屏,以及一個“外部”顯示屏,例如通過HDMI連接的電視。它還支持多個“虛擬”顯示屏。虛擬顯示屏 使得合成的輸出在系統(tǒng)內(nèi)部可用。虛擬顯示屏可以用于錄制屏幕,甚至通過網(wǎng)絡來發(fā)送。
虛擬顯示屏 可以與主屏分享同樣的layer集合(layer stack),或者也可由有著自己的layer集。對于虛擬顯示屏,沒有VSYNC信號,因此主屏的VSYNC信號用于觸發(fā)所有顯示屏的合成。
早期的虛擬顯示屏總是使用GLES來合成,HWC 主要管理主顯示屏的合成。在Android4.4中,HWC 也開始參與虛擬現(xiàn)實的合成。
正如你想到的,虛擬設備的幀也是寫到一個BufferQueue中。
我們已經(jīng)有了BufferQueue和SurfaceFlinger的一些背景知識,下面檢驗一下。
Android4.4中引入的screenrecord 命令,容你將屏幕顯示的任何東東錄制為MP4文件。為了做到這一點,我們必須從SurfaceFlinger接受合成后的幀,送去視頻編碼器,然后將編碼后的視頻數(shù)據(jù)寫到文件。視頻編碼器由一個獨立的進程“mediaserver”所管理,因此我們必須將大量的圖形緩沖區(qū)在系統(tǒng)中跨進程移動。為了更有挑戰(zhàn)性一點,我們試圖以60fps全屏錄制視頻。最有效完成該工作的關鍵是BufferQueue。
MediaCodec 類容許應用以包含原始數(shù)據(jù)的緩沖區(qū)來提交數(shù)據(jù),也可以通過一個surface。我們稍后在討論Surface的細節(jié),現(xiàn)在,我們簡單的認為它是生產(chǎn)者端的BufferQueue的一個封裝。當screenrecord請求訪問一個視頻編碼器時,mediaserver將創(chuàng)建一個BufferQueue,將自身作為消費者端,然后將其作為surface傳遞給生產(chǎn)者。
screenrecord 然后要求SurfaceFlinger 創(chuàng)建一個虛擬顯示屏,直接鏡像主顯示屏,引導它將輸出發(fā)送給前面由mediaserver返回的surface。注意:這個例子中,SurfaceFlinger 是生產(chǎn)者而不是消費者。
一旦配置完成,screenrecord 就可以坐等編碼后的數(shù)據(jù)出現(xiàn)了。當應用繪制時,它們的緩沖區(qū)交付給SurfaceFlinger,后者將這些緩沖區(qū)合成為一塊緩沖區(qū),然后直接發(fā)送給mediaserver的視頻編碼器。screenrecord進程完全看不到這些幀。
WindowManager可以要求SurfaceFlinger創(chuàng)建一個可見的layer,SurfaceFlinger將作為該layer對應的BufferQueue的消費者。WindowManager也可能要求SurfaceFlinger 創(chuàng)建一個虛擬顯示屏,這種情形SurfaceFlinger將作為BufferQueue的生產(chǎn)者。如果你上面的layer和虛擬顯示屏連接在一起,會發(fā)生啥?
你創(chuàng)建了一個閉環(huán),合成的屏幕將出現(xiàn)在一個window中。當然,這個window是合成輸出的一部分,因此下一次刷新時,該合成的圖像將也顯示window內(nèi)容。海龜背地球。。。。
Surface類 早就包含在API 1.0中了。其描述很簡單:一個原始緩沖區(qū)的巨涌,有屏幕合成器所管理。這段話早期是精確的,不過符合不了現(xiàn)代系統(tǒng)的標記。
Surface 代表了生產(chǎn)者側的BufferQueue,通常由SurfaceFlinger所消費。當你在一個surface上渲染時,其最終結果放在某個緩沖區(qū)中被送到消費者。Surface并不簡單的是一塊你可以胡亂涂改的原始內(nèi)存塊。
用于顯示屏Surface的BufferQueue通常被配置為三緩沖,不過緩沖區(qū)還是按需分配的、當生產(chǎn)者生成緩沖區(qū)過慢時(例如30fps的動畫 顯示在 60fps的顯示屏時),隊列中可能僅實際分配了兩塊緩沖區(qū)。這樣最小化了內(nèi)存消耗。你可以通過 dumpsys SurfaceFlinger 命令來查看每個layer的緩沖區(qū)分配情況。
type | handle | hint | flag | tr | blnd | format | source crop (l,t,r,b) | frame | name
-----------+----------+------+------+----+------+-------------+--------------------------------+------------------------+------
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
HWC | b583b330 | 0002 | 0000 | 00 | 0105 | RGBA_8888 | 0.0, 0.0, 1080.0, 75.0 | 0, 0, 1080, 75 | StatusBar
HWC | b583b150 | 0002 | 0000 | 00 | 0105 | RGBA_8888 | 0.0, 0.0, 1080.0, 144.0 | 0, 1776, 1080, 1920 | NavigationBar
FB TARGET | b61b1880 | 0000 | 0000 | 00 | 0105 | RGBA_8888 | 0.0, 0.0, 1080.0, 1920.0 | 0, 0, 1080, 1920 | HWC_FRAMEBUFFER_TARGET
Allocated buffers:
0xb583b150: 648.00 KiB | 1080 (1152) x 144 | 1 | 0x00000900
0xb583b1f0: 8640.00 KiB | 1080 (1152) x 1920 | 1 | 0x00000900
0xb583b330: 337.50 KiB | 1080 (1152) x 75 | 1 | 0x00000900
0xb583b3d0: 8640.00 KiB | 1080 (1152) x 1920 | 1 | 0x00000900
0xb583b4c0: 337.50 KiB | 1080 (1152) x 75 | 1 | 0x00000900
0xb583b560: 8640.00 KiB | 1080 (1152) x 1920 | 1 | 0x00000900
0xb583b5b0: 7200.00 KiB | 1260 (1280) x 1920 | 3 | 0x00000900
0xb583b6f0: 337.50 KiB | 1080 (1152) x 75 | 1 | 0x00000900
0xb61a33d0: 648.00 KiB | 1080 (1152) x 144 | 1 | 0x00000900
0xb61a3790: 337.50 KiB | 1080 (1152) x 75 | 1 | 0x00000900
0xb61a3f60: 8640.00 KiB | 1080 (1152) x 1920 | 1 | 0x00001a00
0xb61b1880: 8640.00 KiB | 1080 (1152) x 1920 | 1 | 0x00001a00
0xb62ee1f0: 648.00 KiB | 1080 (1152) x 144 | 1 | 0x00000900
Total allocated (estimate): 53694.00 KB
曾幾何時,所有的渲染都是軟件完成的,當前你現(xiàn)在還是可以這樣做,skia 圖形庫提供了底層實現(xiàn)。如果你希望畫一個矩形,你可以調用圖形庫,讓圖形庫去修改緩沖區(qū)。為了保證緩沖區(qū)不會被兩個客戶端一起更新,或者 顯示時還在被寫入,你需要使用鎖。 lockCanvas() 鎖定某個緩沖區(qū),然后返回一個Canvas 用于繪圖;而unlockCanvasAndPost() 解鎖緩沖區(qū),然后將其發(fā)送給合成器。
時代在進步,當有通用目的的3D引擎的設備出現(xiàn)后,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時?!盋PU 渲染器“ 連接到生產(chǎn)者側的BufferQueue,保持,直到Surface銷毀時才摧毀連接。大多素的生產(chǎn)者(如GLES)可以被斷開后重連接到一個新的surface,不過基于Canvas的”CPU 渲染器“沒有重連接能力。這意味著 ,如果你已經(jīng)為canvas鎖住了一個surface,你不能使用GLES繪制該surface,或者向該surface發(fā)送某個視頻解碼器的圖像幀。
當生產(chǎn)者首次從BufferQueue中申請一塊緩沖區(qū)時,緩沖區(qū)被分配出來,內(nèi)容清零。這種初始化是必要的,以避免進程間非故意的數(shù)據(jù)共享。當你重新使用某個緩沖區(qū)時,之前的內(nèi)容還在。如果你重復調用 lockCanvas() 和 unlockCanvasAndPost()且并不繪制點啥的話,你將僅僅在之前渲染過的幀中循環(huán)而已。
Surface的lock/unlock代碼維護了先前渲染的緩沖區(qū)的引用。如果你在鎖定surface時指明一塊臟區(qū)域 ,它將從先前的緩沖區(qū)中拷貝那些“干凈”的像素。這塊緩沖區(qū)將公平地由由SurfaceFlinger 或者 HWC 處理;不過既然我們僅需要讀取該緩沖區(qū),這里沒必要去等待排他性訪問。
除了Canvas之外,應用直接繪制surface的主要方式是通過OpenGL ES。這在 EGLSurface and OpenGL ES 這一節(jié)描述。
如果工作在surface,那就需要一個SurfaceHolder,尤其是SurfaceView 。surface代表了原始的由合成器管理的緩沖區(qū),而SurfaceHolder由應用管理,跟蹤了解高層信息(維度、格式、etc)。java語言的定義鏡像了底層本地實現(xiàn)。有爭議說這樣劃分沒啥用,不過長期以來SurfaceHolder就已經(jīng)是公共API的一部反。
總的來說,任何需要處理View的主體,都牽涉到了一個SurfaceHolder。某些其他的API,如MediaCodec,將直接操作surface。你可以很容易從SurfaceHolder獲得surface的引用。
SurfaceHolder還實現(xiàn)了 Surface參數(shù)的讀寫API,如尺寸、格式。
OpenGL ES定義了圖形渲染的一套API,而不是定義了一套window系統(tǒng)。為了容許GLES 工作在各種平臺,它需要與一個了解如何在操作系統(tǒng)上創(chuàng)建和訪問windows的庫來協(xié)作。在Android上,該庫被稱為EGL。如果你希望繪制紋理,你會使用GLES調用;如果你希望將你渲染結果顯示到屏幕上,你應該使用EGL調用。
在你開始使用GLES任何功能前,你需要創(chuàng)建一個GL 上下文。在EGL中,這疑問這創(chuàng)建一個EGLContext和一個EGLSurface。GLES操作將應用于當前上下文中,該上下文通過線程局部存儲來訪問,而不是通過參數(shù)來傳遞。這要求你很小心 ,你的渲染代碼在哪個線程執(zhí)行,在那個線程上的是哪個上下文。
EGLSurface 可以使EGL分配的一塊離屏(off-screen)緩沖,又稱pbuffer,也可以是操作系統(tǒng)分配的window。EGL window surfaces 通過函數(shù)eglCreateWindowSurface()創(chuàng)建,該函數(shù)接收一個“window”對象作為參數(shù),在Android,這個對象的類型可能是SurfaceView、SurfaceTexture 、SurfaceHolder 或者 Surface – 所有的這些類型底層都有一個BufferQueue。當你調用該函數(shù)時,EGL創(chuàng)建一個新的EGLSurface對象,并將此對象連接到該窗口對象的BufferQueue的生產(chǎn)者上。從這點看,EGLSurface的渲染將導致 一塊緩沖區(qū)被 出隊列,渲染,入隊列以供消費者使用。(術語“window”指示了期望的用法,但是你需要牢記,輸出并不一定要顯示在屏幕上。)
EGL不提供 lock/unlock調用。相反,你發(fā)出繪制命令,然后調用eglSwapBuffer()來提交當前幀。這個方法的名稱來源于傳統(tǒng)的前后緩沖切換,但是實際的實現(xiàn)可能完全不同。
一個EGLSurface同一時刻只能對應于一個surface,因為你只能將一個生產(chǎn)者連接到某個BufferQueue;但是,如果你銷毀了EGLSurface,它將從BufferQueue斷開,這時候就可以容許其他生產(chǎn)者連接到該BufferQueue了。
一個指定的線程可以通過修改“current”來在多個EGLSurface間切換。當然一個EGLSurface一次只能作為一個線程的“current“ 。
最常見的錯誤是,當談到EGLSurface時,認為它是Surface(例如 SurfaceHolder)的另外一面。這兩個術語是完全不同的概念:你可以在某個沒有關聯(lián)到Surface的EGLSurface上繪圖;你可以無需EGL就使用Surface;EGLSurface僅提供給GLES一個位置繪圖而已。
Surface類使用java語言實現(xiàn)。其C++的等效類是ANativeWindow,由NDK 公開。你可以調用ANativeWindow_fromSurface(),從Surface中獲取ANativeWindow實例,然后lock、渲染、unlock-and-post.
為了在本地碼中創(chuàng)建一個EGL windows surface,你需要傳遞一個EGLNativeWindowType的實例到eglCreateWindowSurface()。EGLNativeWindowType 實際上是 ANativeWindow的同義詞。
事實上,native window 的基礎類型也僅僅是 生產(chǎn)者端的BufferQueue的封裝。
C++類型 | Java類型 |
---|---|
EGL nativce window surface | EGLSurface |
EGLNativeWindowType = ANativeWindow | Surface |
前面我們已經(jīng)探索了低層組件,現(xiàn)在是時候看看它們?nèi)绾窝b配到應用直接使用的高層組件了。
Android應用框架UI 基于 View的繼承體系。雖然大部分細節(jié)與本討論無關,不過 這有助于理解:這些UI元素是如何經(jīng)過一個復雜的測量和layout過程,最終被裝配到一個矩形區(qū)域。所有可視的View對象 被渲染到一個SurfaceFlinger創(chuàng)建的Surface,這個Surface在應用切換到前臺時由WindowManager設置。layout和渲染 工作是在應用的UI線程中執(zhí)行的。
不管有多少layout和View,所有的東東最后被渲染到一個緩沖區(qū),不管View是否使用了硬件加速。
SurfaceView 接收與其他View類似的參數(shù),因此你可以傳入 位置、尺寸 以及其他什么元素。不過,當SurfaceView開始渲染時,這些內(nèi)容是完全透明的。SurfaceView的View部分僅僅是一個可以透視的占位。
當SurfaceView 的View 組件希望變?yōu)榭梢晻r,系統(tǒng)框架要求WindowManager 請求SurfaceFlinger創(chuàng)建一個新的surface。(這些不會同步發(fā)生,這就是為什么你需要提供一個回調以便在Surface創(chuàng)建完成后得到通知的原因)。默認情況下,新的surface 放在應用的UI surface之后,不過這個默認的z-order 可以通過將surface置頂而修改。
你在這個新surface上 渲染的任何東東最后都是由SurfaceFlinger 而不是應用自身來合成。這就是SurfaceView的真正威力:你獲取的這個surface可以在獨立的線程中或者其他進程中渲染,從而與應用UI執(zhí)行的任何渲染 隔離,而且對應的buffer直接傳遞給SurfaceFlinger—你不能完全無視UI線程,因為你仍然需要與Activity的生命周期合作,當View的尺寸或者位置修改時,你也可能需要調整點啥 —不過,你有完全屬于你一整個surface ,與應用UI和其它layer的調和將由HWC處理。
這里值得花點時間指出:新的surface 代表 生產(chǎn)者側的BufferQueue,消費者則是 某個SurfaceFlinger layer。
你可以使用任何能夠寫入BufferQueue的機制來更新surface: 使用Surface-supplied Canvas函數(shù);將一個EGLSurface連接到surface,然后使用GLES來繪圖;配置一個MediaCodec視頻解碼器來寫入surface。
現(xiàn)在我們的內(nèi)容有點兒多了,有必要回去看看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在前,狀態(tài)條和導航條在最前。
“source crop”指示了SurfaceFlinger 將要顯示的那部分surface緩沖區(qū)。
注意 ”SurfaceView“,該layer放置了我們的視頻內(nèi)容,其source crop與視頻的尺寸一致,該尺寸 SurfaceFlinger 是了解的,因為MediaCodec解碼器 使用該尺寸的緩沖區(qū)。不過,幀矩形區(qū)域的尺寸就完全不一樣了:984*738。
SurfaceFlinger 通過縮放來填充幀矩形區(qū)域。如果你在同一個surface上開始播放一個不同的視頻,底層的BufferQueue將自動重新分配合適的緩沖區(qū),SurfaceFlinger也會調整source crop。如果新視頻的縱橫比變了,應用將需要強制re-layout以便進行匹配,這會導致WindowManager告訴SurfaceFlinger刷新幀矩形。
如果你使用其他方式如GLES 來渲染surface,你可以使用SurfaceHolder#setFixedSize() 來設置surface 尺寸。舉例來講,你也可以配置一個游戲總是以1280x720進行渲染,這將顯著降低像素數(shù)量,需要在 2560x1440 的平板或者4K顯示時,顯示處理器將負責進行縮放。
GLSurfaceView類提供了一些輔助類,幫助管理EGL上下文,線程間通信,以及與Activity 生命周期的交互。僅此而已。為使用GLES,GLSurfaceView也并不是必須的。
例如,GLSurfaceView創(chuàng)建了一個線程,用于渲染和配置EGL上下文。在Activity 暫停時,該線程將自動清除。大多數(shù)應用不必了解EGL 就可以GLES和GLSurfaceView。
大多數(shù)情形下,GLSurfaceView 非常有用,可以讓使用GLES更容易。如果有幫助,用;沒有,不理它。
SurfaceTexture 是一個在Android3中引入的較新的類。正如 SurfaceView 是Surface和View的結合體,SurfaceTexture 是 Surface 和 GLES 紋理的結合體。