Android 硬件 OpenGL ES 模拟设计概述
簡介
Android 平臺的 OpenGL ES 模擬由多個組件實現(xiàn),它們是:
一些宿主機的 “翻譯器” 庫。它們實現(xiàn)了由 Khronos 定義的 EGL,GLES 1.1 和 GLES 2.0 ABIs,并把對應的函數(shù)調(diào)用翻譯為適當?shù)淖烂?API,比如:
- 實現(xiàn) EGL 接口的是 GLX (Linux),AGL (OS X) 或 WGL (Windows)
- 實現(xiàn) GLES 1.1 和 GLES 2.0 接口的是桌面 GL 2.0
模擬的客戶系統(tǒng)內(nèi)的一些系統(tǒng)庫,它們實現(xiàn)了相同的 EGL / GLES 1.1 和 GLES 2.0 ABIs。
它們收集 EGL/GLES 函數(shù)調(diào)用序列并把它們翻譯為定制的協(xié)議流,通過一個稱為 “QEMU Pipe” 的高速通信通道發(fā)送給模擬器程序。
目前為止,你需要知道的所有東西即是,Pipe 由一個定制的內(nèi)核驅動實現(xiàn),并提供了非常快速的帶寬。從客戶系統(tǒng)的角度來看,對 Pipe 所做的所有 read() 和 writes() 基本上是瞬間完成的。
- 模擬器程序內(nèi)特定的代碼,它能夠把協(xié)議流發(fā)送給理解協(xié)議格式的特殊的渲染庫或進程(這里稱為 “渲染器”)。
- 渲染器從協(xié)議流解碼 EGL/GLES 命令,并把它們派發(fā)給適當?shù)姆g器庫。
- 事實上,協(xié)議流是雙向流動的,盡管大多數(shù)命令導致數(shù)據(jù)從客戶系統(tǒng)傳送到宿主機。模擬的完整圖像將是這樣的:
(注意:只有 Linux 是 ‘GLX’,OS X 是 ‘AGL’,Windows 是 ‘WGL’)。
注意,在上圖中,只有最底下的宿主系統(tǒng)庫 不是 Android 提供的。
設計要求
上述設計來自項目初期決定的若干重要要求:
1 - 在模擬器之外的單獨進程中運行渲染器的能力非常重要。
由于各種實際的原因,我們計劃通過使用兩個不同的進程把核心的 QEMU 模擬與 UI 窗口完全分離。這樣,渲染器將實現(xiàn)為 UI 程序內(nèi)的庫,但需要從 QEMU 進程接收協(xié)議字節(jié)。
它們兩個間的通信通道將是一個快速的 Unix socket 或 Win32 命名管道。如果存在性能問題,具有適當同步原語的共享內(nèi)存片段也可以使用。
這解釋了為什么模擬器不改變,甚至試圖解析協(xié)議字節(jié)流。它只扮演客戶系統(tǒng)與渲染器之間的傻瓜代理。這也避免了在 QEMU 代碼內(nèi)增加太多的 GLES 特有代碼,而那將復雜地可怕。
2 - 使用生產(chǎn)商專有桌面 EGL/GLES 庫的能力非常重要。
像 NVidia,AMD,或 ARM 這樣的 GPU 生產(chǎn)商都提供了 EGL/GLES 庫的主機版本,來模擬它們各自的嵌入式圖形芯片。
渲染庫可以配置為使用這些庫來替代本項目提供的翻譯器庫。在更精確地模擬特定設備的行為時這可能很有用。
此外,這些供應商庫通常會暴露翻譯器庫不提供的特定于供應商的擴展。我們無法在不修改我們的代碼的情況下暴露它們,但無需太多痛苦就能夠做到這一點很重要。
代碼組織
上面提到的組件的源碼分布在 Android 源碼樹的多個目錄下:
模擬器的源碼位于 $ANDROID/external/qemu,本文檔的后面部分我們將用 $QEMU 指稱它。
客戶系統(tǒng)庫位于 $ANDROID/device/generic/goldfish/opengl,我們將稱為 $EMUGL_GUEST。
宿主機渲染器和翻譯器庫位于 $QEMU/android/android-emugl,我們將稱為 $EMUGL_HOST。
QEMU Pipe 內(nèi)核驅動位于 $KERNEL/drivers/misc/qemupipe (3.4) 或 $KERNEL/drivers/platform/goldfish/goldfish_pipe.c (3.10)。
其中 $ANDROID 是開源 Android 源碼樹的根目錄,$KERNEL 是 qemu 專有內(nèi)核源碼樹的根目錄 (這里使用 android-goldfish-xxxx 的一個分支)。
與這個項目相關的模擬器源碼有:
$QEMU/hw/android/goldfish/pipe.c -> 實現(xiàn) QEMU pipe 虛擬硬件。$QEMU/android/opengles.c -> 實現(xiàn) GLES 初始化。$QEMU/android/hw-pipe-net.c -> 實現(xiàn) QEMU Pipe 和渲染器庫之間的通信通道。其它的源碼還有:
$EMUGL_GUEST/system -> 系統(tǒng)庫$EMUGL_GUEST/shared -> 共享庫的客戶系統(tǒng)拷貝$EMUGL_GUEST/tests -> 各種測試程序$EMUGL_HOST/host -> 宿主機庫(翻譯器 + 渲染器)$EMUGL_HOST/shared -> 共享庫的宿主機拷貝共享庫實際不是共享的是有歷史原因的:某個時刻客戶系統(tǒng)和宿主代碼位于相同的地方。隨著 Android SDK 開出分支,那被證明是不切實際的,且不支持單獨的模擬器二進制文件能夠運行多個 Android 發(fā)行版的要求。
翻譯器庫
本項目提供了三個主機端的翻譯器庫:
libEGL_translator -> EGL 1.2 翻譯libGLES_CM_translator -> GLES 1.1 翻譯libGLES_V2_translator -> GLES 2.0 翻譯庫的完整的名字依賴于宿主機系統(tǒng)。為了簡單起見,只有庫名字的后綴會改變(即在 Windows 上沒有刪除 ‘lib’ 前綴),比如:
libEGL_translator.so -> for LinuxlibEGL_translator.dylib -> for OS XlibEGL_translator.dll -> for Windows這些庫的源碼位于 Android 源碼樹的下列路徑下:
$EMUGL_HOST/host/libs/Translator/EGL$EMUGL_HOST/host/libs/Translator/GLES_CM$EMUGL_HOST/host/libs/Translator/GLES_V2翻譯器庫也使用如下目錄中定義的通用的程序:
$EMUGL_HOST/host/libs/Translator/GLcommon協(xié)議概述
“協(xié)議” 按如下所述實現(xiàn):
EGL/GLES 函數(shù)調(diào)用通過一些 “規(guī)范” 文件描述,它們描述了類型,函數(shù)簽名,以及各種屬性。
這些文件由稱為 “emugen” 的工具讀取,它基于規(guī)范產(chǎn)生 C 源文件和頭文件。這些文件對應于編碼,解碼和 “wrappers”(更詳細的內(nèi)容在后面說明)。
系統(tǒng) “編碼器” 靜態(tài)庫使用這些生成的文件中的一些來編譯。它們包含了可以把 EGL/GLES 調(diào)用序列化為簡單的字節(jié)消息,并把它通過通用的 “IOStream” 對象發(fā)送的代碼。
宿主機 “解碼器” 靜態(tài)庫也使用這些生成的文件中的一些來編譯。它們的代碼從 “IOStream” 對象中提取字節(jié)消息,并把它們翻譯為函數(shù)調(diào)用。
IOStream 抽象
“IOStream” 是一個非常簡單的抽象類,用于在客戶系統(tǒng)和宿主系統(tǒng)中發(fā)送字節(jié)消息。它通過 $EMUGL/host/include/OpenglRender/IOStream.h 下的一個共享頭文件定義
注意,盡管路徑在 $EMUGL/host 下,但宿主系統(tǒng)和客戶系統(tǒng)源碼 同時 包含這個頭文件。IOStream 的主要設計思路是,發(fā)送一條消息,每一條做如下這些事情:
1/ 調(diào)用 stream->allocBuffer(size),這將返回一塊大小至少為 ‘size’ 個字節(jié)的內(nèi)存緩沖區(qū)的地址。
2/ 直接將序列化的命令(通常是一個頭部 + 一些載荷)的內(nèi)容寫入緩沖區(qū)。
3/ 調(diào)用 stream->commitBuffer() 發(fā)送它。
另外,也可以通過 stream->alloc() 和 stream->flush() 把多個命令打包為一個緩沖區(qū),如:
1/ buf1 = stream->alloc(size1)
2/ 把第一個命令的字節(jié)寫入 buf1
3/ buf2 = stream->alloc(size2)
4/ 把第二個命令的字節(jié)寫入 buf2
5/ stream->flush()
最后,有一些顯式的 read/write 方法,比如 stream->readFully() 或 stream->writeFully(),當你不想要中間緩沖區(qū)時可以被使用它們。在某些情況下實現(xiàn)會使用它們,比如,當從客戶系統(tǒng)向宿主機發(fā)送紋理數(shù)據(jù)時,為了避免中間內(nèi)存復制。
宿主機 IOStream 實現(xiàn)位于 $EMUGL/shared/OpenglCodecCommon/,特別要看:
$EMUGL_HOST/shared/OpenglCodecCommon/TcpStream.cpp-> 使用本地 TCP sockets$EMUGL_HOST/shared/OpenglCodecCommon/UnixStream.cpp-> 使用 Unix sockets$EMUGL_HOST/shared/OpenglCodecCommon/Win32PipeStream.cpp-> 使用 Win32 命名管道客戶系統(tǒng) IOStream 實現(xiàn)使用上面的 TcpStream.cpp,以及可替代的 QEMU 特有的源碼:
$EMUGL_GUEST/system/OpenglSystemCommon/QemuPipeStream.cpp-> 從客戶系統(tǒng)使用 QEMU pipe由于以下幾個原因,QEMU Pipe 執(zhí)行速度(約20倍)顯著增加:
從客戶系統(tǒng)的角度看,通過它的所有成功的 read() 和 write() 是瞬間完成的。
所有緩沖區(qū)/內(nèi)存復制直接由模擬器執(zhí)行,這比內(nèi)核中通過模擬的 ARM 指令做相同的事情要快得多。
無需瀏覽把數(shù)據(jù)打包進 TCP/IP/MAC 包,并把它們送給模擬的以太網(wǎng)設備的內(nèi)核 TCP/IP 棧,它們本身連接到一個內(nèi)部的防火墻實現(xiàn),其將解包數(shù)據(jù)包,重新匯集它們,并通過 BSD socket 把它們發(fā)送給宿主機內(nèi)核。
然而,如果有需要的話,你可以為客戶系統(tǒng)編寫一個使用不同的傳輸方式的 IOStream 實現(xiàn)。如果你要那么做,可以參考 $EMUGL_GUEST/system/OpenglCodecCommon/HostConnection.cpp,其包含了用于把客戶系統(tǒng)連接到宿主機的代碼,在每個線程的基礎上。
源碼的自動生成
emugen 工具位于 $EMUGL_HOST/host/tools/emugen。有一份 README 文件解釋了 emugen 是如何工作的。
You can also look at the following specifications files:
你也可以看一下如下的規(guī)范文件:
GLES 1.1:
$EMUGL_HOST/host/GLESv1_dec/gl.types$EMUGL_HOST/host/GLESv1_dec/gl.in$EMUGL_HOST/host/GLESv1_dec/gl.attribGLES 2.0:
$EMUGL_HOST/host/GLESv2_dec/gl2.types$EMUGL_HOST/host/GLESv2_dec/gl2.in$EMUGL_HOST/host/GLESv2_dec/gl2.attribEGL:
$EMUGL_HOST/host/renderControl_dec/renderControl.types$EMUGL_HOST/host/renderControl_dec/renderControl.in$EMUGL_HOST/host/renderControl_dec/renderControl.attrib注意 EGL 規(guī)范文件位于名為 renderControl_dec 的目錄下,且其文件名以 renderControl 開頭。
這主要是出于歷史原因,但也與這樣的事實有關,即協(xié)議的這個部分包含了一些函數(shù)/調(diào)用/規(guī)范的支持,但它們不是 EGL 規(guī)范本身的一部分,但添加了所有功能都需要的一些功能。比如,它們具有與 gralloc 系統(tǒng)庫模塊有關的調(diào)用,gralloc 系統(tǒng)庫模塊用于在比 EGL 更低的層面管理圖形 Surfaces。
一般來說,客戶系統(tǒng)編碼器源碼位于名為 $EMUGL_GUEST/system/<name>_enc/ 的目錄下,盡管對應的宿主系統(tǒng)解碼器的源碼位于 $EMUGL_HOST/host/libs/<name>_dec/ 下。
然而,所有這些源文件使用位于解碼目錄下的相同的 spec 文件。
編碼器文件由位于 $EMUGL_HOST 下的 emugen 和 spec 文件構建,并被 gen-encoder.sh 腳本復制到位于 $EMUGL_GUEST 下的編碼器目錄內(nèi)。它們被 check in,以使給定的 Android 版本支持特定的協(xié)議版本,甚至是更新的渲染器版本(和未來的 Android 版本)支持了一個更新的協(xié)議版本。當協(xié)議改變時,這一步需要手動地完成;這些改變也需要伴隨著渲染器內(nèi)的改變,以處理老版本的協(xié)議。
系統(tǒng)庫
元 EGL/GLES 系統(tǒng)庫,和 egl.cfg
對有一點的理解很重要,即模擬器特有的 EGL/GLES 庫不是在運行時由應用程序直接鏈接的。相反,系統(tǒng)提供了一系列 “元” EGL/GLES 庫,它們將在第一次使用時加載適當?shù)挠布S脦臁?/p>
進一步來說,系統(tǒng) libEGL.so 包含了一個 “加載器”,其將嘗試加載:
- 硬件專用的 EGL/GLES 庫
- 基于軟件的渲染庫 (稱為 “l(fā)ibagl”)
系統(tǒng) libEGL.so 還能夠將硬件和軟件庫的 EGL 配置透明地合并到應用程序中。系統(tǒng) libGLESv1_CM.so 與 libGLESv2.so 與它一起工作,以確保線程的當前上下文根據(jù)所選擇的配置被鏈接到硬件或者軟件庫。
作為記錄,加載器的源碼位于 frameworks/base/opengl/libs/EGL/Loader.cpp。它依賴于名為 /system/lib/egl/egl.cfg 的文件,其包含了看起來像下面這樣的兩行:
0 1 <name>0 0 android每一行的第一個數(shù)字是顯示號,且必須為 0,因為系統(tǒng)的 EGL/GLES 庫不支持任何其它的值。
第二個數(shù)字必須用 1 表示硬件庫,用 0 表示軟件庫。對應于硬件庫的行,如果存在的話,必須總是出現(xiàn)在軟件庫對應的行的前面。
第三個字段是對應于共享庫后綴的名字。它實際意味著對應的庫名字為 libEGL_<name>.so,libGLESv1_CM_<name>.so 和 libGLESv2_<name>.so。此外,這些庫必須被放在 /system/lib/egl/ 下。
名字 android 為系統(tǒng)的軟件渲染器保留。
來自于本項目的 egl.cfg 為硬件庫使用名字 emulation。這意味著它提供一個 egl.cfg 文件,其中包含如下的行:
0 1 emulation0 0 android參考 $EMUGL_GUEST/system/egl/egl.cfg ,及更通常是下面的構建文件:
$EMUGL_GUEST/system/egl/Android.mk$EMUGL_GUEST/system/GLESv1/Android.mk$EMUGL_GUEST/system/GLESv2/Android.mk來了解庫如何命名并被構建系統(tǒng)放置于 /system/lib/egl/ 下。
模擬庫
模擬器專用的庫位于如下位置:
$EMUGL_GUEST/system/egl/$EMUGL_GUEST/system/GLESv1/$EMUGL_GUEST/system/GLESv2/GLESv1 和 GLESv2 的代碼量很小,因為它主要鏈接到靜態(tài)編碼庫。
EGL 的代碼有點復雜,因為它需要動態(tài)地處理擴展。比如,如果一個擴展在宿主機系統(tǒng)上不可用,則它不應該在運行時被暴露給庫。因此 EGL 代碼查詢宿主機的可用擴展列表以把它們返回給客戶端。類似地,它必須為當前的宿主機系統(tǒng)查詢有效的 EGLConfigs 的列表。
“gralloc” 模塊實現(xiàn)
除了 EGL/GLES 庫之外,Android 系統(tǒng)也需要硬件專用的庫來在比 EGL 更低的層次上管理圖形 Surfaces。這個庫必須是 Android 領域所謂的 “HAL 模塊”。
“HAL 模塊” 必須提供由 Android 的 HAL(硬件抽象庫,Hardware Abstraction Library)定義的接口。這些接口的定義可以在 $ANDROID/hardware/libhardware/include/ 下找到。
在所有可能的 HAL 模塊中,“gralloc” 被系統(tǒng)的 SurfaceFlinger 用于分配 framebuffers 和其它圖形內(nèi)存區(qū)域,以及最終在需要的時候 lock/unlock/swap 它們。
位于 $EMUGL/system/gralloc/ 下的代碼實現(xiàn)了GLES 模擬項目所需的該模塊。它不是很長,但這里有一些事情需要注意:
首先,它將探測客戶系統(tǒng)以確定運行虛擬設備的模擬器是否真的支持 GPU 模擬。在某些情況下,這也許是不可能的。
如果是這種情況,則該模塊將會把所有的調(diào)用重定向到 “默認的” gralloc 模塊,其通常在只啟用了軟件渲染時由系統(tǒng)使用。
探測發(fā)生在 fallback_init 函數(shù)中,當模塊首次打開時它會被調(diào)到。當需要時,它把 sFallback 變量初始化為指向默認的 gralloc 模塊的指針。
第二,這個模塊由 SurfaceFlinger 使用,來顯示 “軟件 Surfaces”,比如,那些由系統(tǒng)內(nèi)存像素緩沖區(qū)支持,并通過 Skia 圖形庫直接寫入的(比如,非硬件加速的那些)。
默認的模塊簡單地把像素數(shù)據(jù)從 Surface 拷貝到虛擬的 framebuffer i/o 內(nèi)存,但本項目的 gralloc 模塊通過 QEMU Pipe 把它發(fā)送給渲染器。
事實證明,這在整體上使得渲染/幀率更快,因為客戶系統(tǒng)內(nèi)的內(nèi)存拷貝很慢,而 QEMU pipe 傳輸是在模擬器中直接完成的。
宿主機渲染器
宿主機渲染器庫位于 $EMUGL_HOST/host/libs/libOpenglRender 下,它提供了一個由 $EMUGL_HOST/host/libs/libOpenglRender/render_api.h 下(比如用于模擬器)的頭文件描述的接口。
簡而言之,渲染庫負責以下內(nèi)容:
提供一個虛擬的離屏視頻 surface 用于在運行時渲染所有的東西。它的維度必須通過在庫初始化之后,緊接著調(diào)用 initOpenglRender() 來固定。
提供一種方式在一個宿主機應用程序的 UI 中顯示虛擬的視頻 Surface。這通過調(diào)用 createOpenGLSubWindow() 完成,它接收 window ID 或父 window 的句柄,一些顯示維度和一個旋轉角度作為參數(shù)。這允許 surface 在顯示時被放縮/旋轉,甚至是在視頻 surface 的維度沒有改變時。
提供一種方式監(jiān)聽從客戶系統(tǒng)進入的 EGL/GLES 命令。這通過給 initOpenglRender() 提供一個所謂的 “端口號” 完成。
默認情況下,端口號對應一個渲染器將綁定和監(jiān)聽的本地 TCP 端口號。到該端口的每個新連接將對應創(chuàng)建一個新的客戶系與統(tǒng)宿主系統(tǒng)間的連接,每個這樣的連接對應客戶系統(tǒng)中的一個不同的線程。
處于性能原因,監(jiān)聽 Unix sockets(在 Linux 或 OS X 上),或 Win32 命名管道(在 Windows 上)都是可能的。為了做到這一點,必須在庫初始化(比如initLibrary())和構建(比如 initOpenglRender())之間調(diào)用 setStreamType()。
注意在這些模式中,端口號依然被用于區(qū)分多個模擬器實例。這些細節(jié)通常由模擬器代碼處理,所以你不應該太在意。
注意更早的接口版本允許渲染器庫的客戶端提供它自己的 IOStream 實現(xiàn)。然而,因為許多原因這不是很方便。如果它有意義,這也許可以再次做到,但現(xiàn)在的性能數(shù)字是相當好的。
宿主機模擬器
位于 $QEMU/android/opengles.c 下的代碼負責動態(tài)地加載渲染庫并適當?shù)爻跏蓟?構造它。
到 opengles 服務的 QEMU pipe 連接通過位于 $QEMU/android/hw-pipe-net.c 下的代碼管道化。找到 openglesPipe_init() 函數(shù),它負責創(chuàng)建一個到渲染器庫的連接(依賴于具體配置,而通過一個 TCP socket,或一個 Unix 管道。模擬器中對 Win32 命名管道的支持還沒有實現(xiàn)),無論何時客戶系統(tǒng)進程通過 /dev/qemu_pipe 打開了 opengles 服務。
還有一些用于支持顯示 GLES framebuffer 的代碼(通過渲染器庫的 subwindow)位于 $QEMU/skin/window。
請注意,此刻,放縮和旋轉是得到支持的。然而,亮度模擬(用于在顯示之前修改來自于硬件 framebuffer 的像素值)還不起作用。
另一個問題是,此刻還不能在 GL subwindow 之上顯示任何東西。例如,這將掩蓋仿真的軌跡球圖像(通常在仿真期間通過Ctrl-T切換,或通過按Delete鍵啟用)。
原文 —- emu-2.4-release/external/qemu/android/android-emugl/DESIGN
總結
以上是生活随笔為你收集整理的Android 硬件 OpenGL ES 模拟设计概述的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Android 图形驱动初始化
- 下一篇: Android 图形系统之图形缓冲区分配