在英特尔® 凌动™ 处理器上将 OpenGL* 游戏移植到 Android* (第一部分)
將游戲和其他使用大量 3D 圖形的應用從 OpenGL 標準移植到 Google Android 設備(包括構建在英特爾? 凌動? 微架構上的設備)存在巨大的機遇,因為基于 OpenGL 的游戲、游戲引擎和其他傳統軟件易于獲得;OpenGL 便于移植;而且 Android 可提供對 OpenGL ES 和 C/C++ 的支持。 甚至,許多基于 OpenGL 的游戲和引擎的可用性與開源軟件一樣,如 Id Software 的?Quake?系列。 本文包括兩部分的內容,通過詳述在英特爾凌動處理器上將早期版本的 OpenGL 所構建的應用的渲染組件移植到 Android 中存在的障礙來介紹如何開始這樣的項目。 這些應用可以是游戲、游戲引擎或使用 OpenGL 構建 3D 場景或圖形用戶界面(GUI)的任何一種軟件。 此外,還包括從臺式機操作系統(如 Windows* 和 Linux* )以及使用嵌入式版本的 OpenGL ES(無論包括或不包括 windowing 系統)的應用移植 OpenGL 代碼。
本文的第一部分介紹了如何通過軟件開發套件(SDK)或 Android 原生開發工具套件(NDK)在 Android 平臺上使用 OpenGL ES,以及如何確定選擇何種方法。 本部分還介紹了各種 SDK 和 NDK 中的 OpenGL ES 示例應用,以及 Java* 原生接口(JNI),這支持您結合使用 Java 和 C/C++ 組件。 此外,還討論了如何確定使用 OpenGL ES 版本 1.1 還是 2.0。
第二部分將討論您在開始這樣的項目之前,應該了解的移植 OpenGL 游戲存在的障礙,包括 OpenGL 擴展、浮點支持、紋理壓縮格式和 GLU 庫的區別。 此外,還介紹了借助 OpenGL ES 如何為英特爾凌動處理器設置 Android 的開發系統,以及如何獲得 Android 虛擬設備模擬工具的最佳性能。
Android 上的圖形
您可以通過四種不同的方式在 Android 平臺上渲染圖形,每種方式都有其長處和不足之處(見表 1)。 本文并未對四種方式都進行介紹。 只有兩種方式適用于從其他平臺移植 OpenGL 代碼: 面向 OpenGL ES 的 SDK 包裝程序類,以及可用于在 C/C++ 中開發 OpenGL ES 的 NDK。 關于其他兩種方法,SDK Canvas 應用編程接口(API)是一款強大的 2D API,可支持您結合 OpenGL ES 使用,但是僅限于 2D 且需要使用新的代碼。
Android 的 Renderscript API 最初并不支持 OpenGL ES,已在 API 等級 16 (Jelly Bean) 中被棄用,因而不能在新的項目中使用。 現在,最適合 Renderscript 的是可以提升計算密集型算法的性能且不需要分配大量內存或傳輸大量數據的應用,如仿真游戲物理引擎的計算。
表 1.在 Android 上渲染圖形的四種方式
| SDK Canvas API | 僅支持 Java 和 2D 圖形 | 
| 面向 OpenGL ES 的 SDK 包裝程序類 | 可從 Java 調用 OpenGL ES 1.1 和 2.0(但是有 JNI 開銷) | 
| NDK OpenGL ES | OpenGL ES 1.1 和 2.0(包括從 Java 中調用原生 C/C++) | 
| 面向 OpenGL ES 的 Renderscript | OpenGL ES 支持已在 API 等級 16 中棄用 | 
將 OpenGL 應用移植到早期版本的 Android 較為困難,因為大多數傳統的 OpenGL 代碼是使用 C 或 C++ 進行編寫的,而且 Android 僅支持 NDK 在 Android 1.5 中發布之前的 NDK(Cupcake)。 OpenGL ES 1.0 和 1.1 從開始便可提供支持,但是性能不一致,因為加速為可選操作。 但是,近年來 Android 取得了重大的進步。 向 Android 2.2 (Froyo) 中的 SDK 和修訂版 3 中的 NDK 添加了 OpenGL ES 2.0 支持,在 NDK 修訂版 7 中的 OpenGL ES 擴展添加了擴展支持。 現在,在所有新的 Android 設備上,加速的 OpenGL ES 1.1 和 2.0 是必備配置 — 尤其隨著屏幕尺寸不斷增大。 今天,Android 可為 Java 或 C/C++ 中 OpenGL ES 1.1 或 2.0 上構建的 3D 密集型應用提供一致、可靠的性能,且開發人員可選擇多種方式讓植入流程更輕松。
使用采用 OpenGL ES 包裝程序類的 Android 框架 SDK
Android SDK 框架可為 Android 支持的三個版本的 OpenGL ES (1.0、1.1 和 2.0) 提供一套包裝程序類。 這些分類支持 Java 代碼在 Android 系統中輕松調用 OpenGL ES 驅動程序 — 即使驅動程序在本地執行。 如果您正在從新開始創建一個新的 OpenGL ES Android 游戲,或愿意將傳統的 C/C++ 代碼轉換為 Java,則這可能是最簡單的方法。 雖然 Java 的設計具備便攜性,但是移植 Java 應用卻較為困難,因為 Android 不能支持全套的現有 Java 平臺、標準版(Java SE)或 Java 平臺、微型版 (Java ME)分類、庫或 API。 雖然 Android 的 Canvas API 支持 2D API,但是它僅可在 Android 上使用且無法與傳統代碼兼容。
Android SDK 提供的多種其他分類讓使用 OpenGL ES 更加輕松,如?GLSurfaceView?和?TextureView。?GLSurfaceView?與配合 Canvas API 使用的 SurfaceView 類相類似,但是它還具備其他一些特性 — 尤其對 OpenGL ES。 它可處理所需的嵌入式系統圖形庫(EGL)的初始化,并可分配渲染接口以便 Android 在屏幕的固定位置顯示并進行渲染。 此外,它還具備一些追蹤和調試 OpenGL ES 調用的有用特性。 通過執行面向GLSurfaceView.Renderer()?接口的三種方法,您可以快速創建一個新的 OpenGL ES 應用,如表 2 所示。
表 2. 適用于 GLSurfaceView.Renderer 的基本方法
| onSurfaceCreated() | 在應用開始初始化時調用一次 | 
| onSurfaceChanged() | 當 GLSurfaceView 的尺寸或方向發生變化時進行調用 | 
| onDrawFrame() | 重復調用以渲染每幀圖形場景 | 
如果從 Android 4.0 開始,您可以使用?TextureView?類,不要使用?GLSurfaceView,以便為具備額外功能的 OpenGL ES 提供渲染接口,但是這需要使用更多代碼。?TextureView?接口的運行方式與普通 Views 相同,并可用于渲染至屏幕外接口。 當將?TextureViews?合成至屏幕時,借助該功能,您可以使其遷移、轉化、實現動畫效果或混合。 此外,您也可以使用?TextureView?以結合使用 OpenGL ES 渲染和 Canvas API。
The 借助位圖和?GLUtils?類,使用 Android Canvas API 為 OpenGL ES 創建紋理,或從 PNG、JPG 或 GIF 文件加載紋理將更輕松。 位圖可用于為 Android 的 Canvas API 分配渲染接口。?GLUtils?類可將影響從位圖轉換為 OpenGL ES 紋理。 該集成支持您使用 Canvas API 渲染 2D 映像,然后將其用作配合 OpenGL ES 使用的紋理。 這對于創建 OpenGL ES 未提供的圖形元素尤其有用 如 GUI widget 和文本字體。 但是當然,需要使用新的 Java 代碼來利用這些特性。
位圖類主要是配合 Canvas API 使用,當將它用于為 OpenGL ES 加載紋理時,有一些嚴重的限制。 Canvas API 遵循適用于 alpha 值混合處理的 Porter-Duff 規格,位圖通過以 premultiplied 格式(A、R*A、G*A、B*A)進行存儲來優化每個像素 alpha 值的映像。 這適合 Porter-Duff 但不適合 OpenGL,后者需要使用非 premultiplied(ARGB)格式。 這意味著位圖類僅可配合完全不透明(或沒有每個像素的 alpha 值)的紋理使用。 一般而言,三維游戲需要使用帶有每個像素 alpha 值的紋理,在這種情況下,您必須避免使用位圖,而從字節陣列或通過 NDK 來加載紋理。
另一個問題是位圖僅支持從 PNG、JPG 或 GIF 格式加載映像,但是一般情況下,OpenGL 游戲使用由 GPU 解碼的壓縮紋理格式,且通常僅專用于 GPU 架構,如 ETC1 和 PVRTC。?位圖和?GLUtils?不支持任何專用的壓縮紋理格式或 mipmapping。 因為這些紋理被大部分的 3D 游戲頻繁使用,這為使用 SDK 將傳統的 OpenGL 游戲移植到 Android 帶來嚴重的障礙。 直到 Google 解決了這些問題,最好的解決方法是避免使用位圖和GLUtils?類來加載紋理。 本文中將進一步討論紋理格式,"紋理壓縮格式。”
Android ApiDemos 包含一個名為?StaticTriangleRenderer?的示例應用,可證明如何使用面向 OpenGL ES 1.0 的?GLES10?封裝、GLSurfaceView、位圖和?GLUtils?類從 PNG 資源加載不透明紋理。 名為?GLES20TriangleRenderer?的類似版本使用面向 OpenGL ES 2.0 的?GLES20?封裝類。 如果您正在使用封裝類處理 Android 框架 SDK,這些示例應用可為開發 OpenGL ES 游戲奠定良好的基礎。 請勿使用名為?TriangleRenderer?的原始版本,因為它為名為?javax.microedition.khronos.opengles?的 Java 使用了面向較舊版本的 OpenGL ES 綁定的封裝。 Google 創建了新的綁定,從而能夠為專門面向 Android 的 OpenGL ES 提供靜態接口。 這些靜態綁定可提供更出色的性能,實現更多的 OpenGL ES 特性,并可提供更接近于 OpenGL ES 配合 C/C++ 使用的編程模型 — 這有益于代碼的重新使用。
Android 框架 SDK 可通過 Google 和 Khronos 提供的面向 Java 的多種綁定為 OpenGL ES 提供支持,如表 3 所示。
表 3. 面向 OpenGL ES 的封裝類總結和示例應用
| javax.microedition.khronos.egl | Khronos standard definition | – | 
| javax.microedition.khronos.opengles | Khronos standard definition | TriangleRenderer, Kube, CubeMap | 
| android.opengl | Android 專用靜態接口 | – | 
Android 專用靜態綁定可提供更出色的性能,如果可行,應使用它而非 Khronos 綁定。 靜態綁定可為 Android 上應用開發可用的所有版本的 OpenGL ES 提供相應的封裝類。 表 4 對這些類進行了總結。
表 4. 面向 OpenGL ES 的 Android 封裝類總結和示例應用
| OpenGL ES 1.0 | android.opengl.GLES10 | StaticTriangleRenderer, CompressedTexture | 
| OpenGL ES 1.0 | android.opengl.GLES10Ext | – | 
| OpenGL ES 1.1 | android.opengl.GLES11 | – | 
| OpenGL ES 1.1 | android.opengl.GLES11Ext | – | 
| OpenGL ES 2.0 | android.opengl.GLES20 | GLES20TriangleRenderer, BasicGLSurfaceView, HelloEffects | 
這些封裝類支持傳統的 C/C++ 代碼中的大部分 OpenGL ES 調用僅通過使用適當 API 版本的封裝類為 OpenGL ES 函數和符號名加前綴來轉換為 Java。 參閱表 5 中的示例。
表 5.將 OpenGL ES 調用從 C 編輯為 Java 的示例
| glDrawArrays(GL_TRIANGLES, 0, count) | GLES11.glDrawArrays(GLES11.GL_TRIANGLES, 0, count) | 
| glDrawArrays(GL_TRIANGLES, 0, count) | GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, count) | 
使用上述封裝類將 OpenGL 游戲移植至 Android 有三大局限: 需要使用大量 Java 虛擬機和 JNI 的開銷以及操作將傳統的 C/C++ 代碼轉換為 Java。 Java 是解釋語言,Android 上所有 Java 代碼都在 Dalvik 虛擬機上運行,因此比編譯的 C/C++ 代碼運行地更慢。 因為 OpenGL ES 驅動程序總是在本地運行,所以每次通過這些封裝調用 OpenGL ES 函數都會造成 JNI 開銷,這限制了游戲圖形渲染的性能。 您的應用發出的 OpenGL ES 調用越多,對 JNI 開銷造成的而影響越大。 所幸,OpenGL ES 的設計可幫助最大限度地減少一般在性能關鍵型渲染循環中需要的調用數量。 如果性能很重要,您可以隨時選擇使用 NDK 將性能關鍵型代碼遷移至 C/C++。 但是當然,如果您的代碼開始就是 C/C++,最好先使用 NDK。
是否需要將 C/C++ 代碼轉換為 Java 取決于您項目的具體情況。 如果 C/C++ 代碼的量相對較小且易于理解,而且您不希望提高性能功耗,那么可以將其轉換至 Java。 如果 C/C++ 代碼的量較大且不易理解,而且您不希望增加功耗,則可以考慮使用 NDK。
使用 Android NDK
Google 于 2009 年 6 月添加了 NDK 以允許應用使用本地運行的 C/C++ 代碼,這可比 Java 提供更高的性能。 因為大部分傳統的 OpenGL 代碼使用 C 或 C++ 編寫,NDK 可提供更簡單的路徑來移植應用,尤其當 C/C++代碼的量太大以至于將其全部轉換為 Java 不實際的時候。 這是 Google 決定公開釋放 NDK 的主要原因,這可讓將 OpenGL 游戲移植到 Android 更輕松。 由于這些優勢,NDK 成為在 Android 上實現需要較快圖形速度的應用的主要方法。
借助 NDK,您可以將 C/C++ 代碼編寫至 Linux 共享對象庫,這些庫靜態連接至您的 Android 應用。 庫使用 GNU 工具構建。這些工具包含在 Google 提供的 NDK 發行軟件包中,您可以使用 Eclipse* 集成開發環境或命令行接口在 Windows、Mac OS* X 或 Linux 開發系統上運行該工具。 該工具鏈支持三種處理器架構: ARM、英特爾凌動 (x86) 和 MIPS。 雖然 C/C++ 的全部性能可用,但是大部分的 Linux API 不可用。 事實上,直接支持的 API 僅包括 OpenGL ES、OpenMAX* AL、OpenSL ES*、zlib 和 Linux 文件 I/O,Google 稱其為穩定 API。 但是,根據需求將會提供有關如何將其他的 Linux 庫移植到您的 NDK 項目中的文檔。
NDK 允許您根據應用的情況靈活地在 Java 和 C/C++ 之間對代碼分區。 NDK 支持從名為?Java Native Interface?的 Java 調用 C/C++ 代碼。 但是,JNI 調用將出現大量的開銷,因此,使用原生代碼對應用分區以便最大程度地減少通過 JNI 的調用量非常重要。 一般而言,大部分的 OpenGL ES 應保持以 C/C++ 編寫以便獲得最佳性能和易于移植,但是如要使用?GLSurfaceView?和其他 SDK 類來管理應用生命周期事件和支持其他游戲函數,可以編寫新的 Java 代碼。 Android 開始支持面向英特爾凌動處理器的 JNI,以 NDK 修訂版 6b 開始。
NDK 支持 OpenGL ES 1.1 和 2.0 并可為兩個版本提供樣本應用,這些樣本應用還可演示如何使用 JNI 將 C 函數與 Java 結合。 這些應用區別在于其代碼在 Java 和 C 之間的分區以及線程化的方式。 它們均使用 NDK 和本地 C 代碼,但是,native-media?樣本應用的所有 OpenGL ES 代碼都是在 Java 中完成,而?san-angeles?和?native-activity?的所有 OpenGL ES 代碼都是在 C 中完成,hello-gl2?在 Java 和 C 之間分割了其 EGL 和 OpenGL ES 代碼。我們應避免使用?hello-gl2?樣本,不僅因為上述的分割,而且因為它無法為 OpenGL ES 2.0 接口優先配置GLSurfaceView,它負責調用?setEGLContextClientVersion(2)。 請參見表 6。
表 6. NDK 中的 OpenGL ES 樣本應用總結
| OpenGL ES 1.1 | san-angeles | 所有 EGL 和 OpenGL ES 代碼均為 C。 | 
| OpenGL ES 1.1 | native-activity | 所有代碼均為 C 并使用 NativeActivity 類。 | 
| OpenGL ES 2.0 | hello-gl2 | EGL 設置位于 Java,OpenGL ES 代碼為 C。 | 
| OpenGL ES 2.0 | native-media | 所有的 EGL 和 OpenGL ES 代碼均為 Java。 | 
雖然未使用 OpenGL ES,但是?bitmap-plasma?樣本也非常有趣,因為它示范了如何使用?jnigraphics?庫來執行本地函數,直接訪問 Android 位圖的像素。
注: 您可以從?http://developer.android.com/tools/sdk/ndk/index.html?下載 Android NDK。
活動生命周期事件和線程化
Android 要求所有對 OpenGL ES 的調用均從單線程執行,因為 EGL 環境僅可與單線程關聯,非常不建議在主 UI 線程上渲染圖形。 所以,最好的方法是專門為所有的 OpenGL ES 代碼創建單獨的線程并一直通過該線程執行。 如果您的應用使用了?GLSurfaceView,它可自動創建此專用 OpenGL ES 渲染線程。 在其他情況下,您的應用必須自己創建渲染線程。
san-angeles?和?native-activity?樣本應用的所有 OpenGL ES 代碼均在 C 中,但是?san-angeles?使用了一些 Java 和?GLSurfaceView?來創建渲染線程和管理活動生命周期,而?native-activity?樣本未使用任何 Java 代碼。 不要使用?GLSurfaceView,因為它在 C 中管理活動生命周期;使用?NativeActivity?類提供的渲染線程。?NativeActivity?是 NDK 提供的一種使用便捷的類,支持您使用本地代碼執行活動生命周期處理程序,如?onCreate()、onPause()?和?onResume()。 一些 Android 服務和內容提供商無法直接從本地代碼訪問,但可以通過 JNI 獲得。
native-activity?樣本適合導入 OpenGL ES 游戲,因為它示范了如何使用?NativeActivity?類和?android_native_app_glue?靜態庫用本地代碼處理生命周期活動。 該類提供了一個單獨的面向 OpenGL ES 代碼的渲染線程、一個渲染接口和一個界面上的窗口,因此,您無需使用GLSurfaceView?或?TextureView?類。 該本地應用的主要入口點是?android_main(),它可在自己的線程中運行并擁有自己檢索輸入事件的事件循環。 遺憾的是,NDK 無法提供面向 OpenGL ES 2.0 的樣本版本,但是您可以使用 2.0 代碼在該樣本中更換所有 1.1 代碼。
使用?NativeActivity?的應用必須在 Android 2.3 或更高版本上使用,并在其清單文件中發出專門的聲明,詳見本文的第二部分。
Java 原生接口
如果您選擇在 C/C++ 中實現大部分的應用,將很難避免為更大型且更專業的項目使用 Java 類。 例如,Android AssetManager 和 Resources API 僅可在 SDK 中使用,這是處理國際化和不同界面尺寸等的首選方式。 但是,JNI 可提供解決方法,因為它不僅允許 Java 代碼調用 C/C++ 函數,還允許 C/C++ 代碼調用 Java 類。 因此,雖然 JNI 會產生一些開銷,但是請不要完全避免使用它。 它是訪問僅可在 SDK 中使用的重要系統功能的最好方法 — 尤其當這些功能不是性能關鍵型功能時。 本文不對使用 JNI 進行完整介紹,但是下面列出了使用 JNI 從 Java 調用至 C/C++ 所需的三個基本步驟:
注: 關于借助 NDK 使用 JNI 的更多信息,請參閱?http://developer.android.com/training/articles/perf-jni.html。
選擇 OpenGL ES 1.1 還是 2.0?
您應在 Android 上使用哪個版本的 OpenGL ES? 1.0 版的 OpenGL ES 已被 1.1 版取代,因此,真正需要選擇的是 1.1 版和 2.0 版。 Khronos 和 Google 可能無限定地支持兩種版本,但是在大部分情況下,OpenGL ES 2.0 優于 1.1。 憑借其 OpenGL 著色語言(GLSL)ES 著色器編程特性,它功能更全面并可提供更高的性能。 甚至,它可能會需要更少的代碼和內存來處理紋理。 但是,Khronos 和 Google 仍然繼續支持 1.1 版的原因是,它更像臺式機和控制臺游戲世界數十年來一直使用的初始 OpenGL 1.x。 因此,將舊版的游戲移植到 OpenGL ES 1.1 比 2.0 更輕松;而且游戲版本越舊,這種情況越適用。
如果移植的游戲沒有著色器代碼,則您可以選擇 OpenGL ES 1.1 也可以選擇 2.0,但是使用 1.1 版可能更輕松。 如果您的游戲已經有了著色器代碼,那么肯定應該選擇 OpenGL ES 2.0,尤其是考慮到近來的 Android 版本都大量地使用了 2.0 版。 根據 Google,截至 2012 年 10 月,訪問 Google Play 網站的 Android 設備中有超過 90% 的設備支持 OpenGL ES 1.1 和 2.0 兩種版本。
注: 更多信息,請參閱?http://developer.android.com/about/dashboards/index.html。
結論
You can implement graphics rendering on Android with OpenGL ES through the Android SDK, the NDK, or a combination of both using the JNI. SDK 方法需要在 Java 中編碼,且最適合新應用的開發,但是 NDK 對以 C/C++ 移植傳統 OpenGL 更實用。 大部分的游戲移植項目需要結合使用 SDK 和 NDK 兩種組件。 對于新項目,應選擇 OpenGL ES 2.0 而非 1.1 版 — 除非您的傳統 OpenGL 代碼太舊而無法使用任何 GLSL 著色器代碼。
該系列的第二部分將討論您在開始這樣的項目之前必須了解的移植 OpenGL 游戲存在的障礙,包括 OpenGL 擴展的差別、浮點支持、紋理壓縮格式和 GLU 庫。 此外,還介紹了借助 OpenGL ES 如何為英特爾凌動處理器設置 Android 的開發系統,以及如何獲得 Android 虛擬設備模擬工具的最佳性能。
關于作者
Clay D. Montgomery 是在嵌入式系統上開發面向 OpenGL 的驅動程序和應用的主要開發人員。 他曾在 STB 系統、VLSI 技術、飛利浦半導體、諾基亞、德州儀器、AMX 以及作為獨立顧問從事跨平臺圖形加速器硬件、圖形驅動程序、APIs 和 OpenGL 應用的設計。 他曾參與 Freescale i.MX 和 TI OMAP* 平臺以及 Vivante、AMD 和 PowerVR* 首個 OpenGL ES、OpenVG* 和 SVG 驅動程序和應用的開發。 他開發了在嵌入式 Linux 上開發 OpenGL ES 并開設了研討班進行教授,且是 Khronos Group 多家公司的代表。
更多相關信息
- Google OpenGL ES 指南,“使用 OpenGL ES 展示顯卡 ”:?http://developer.android.com/training/graphics/opengl/index.html
- Vladimir Silva,專業 Android 游戲:?http://www.apress.com/9781430226475
- Dan Ginsburg,“為基于 OpenGL 和 OpenGL ES 的移動設備查找 3D 應用”:?http://software.intel.com/en-us/articles/targeting-3d-applications-for-mobile-devices-powered-by-opengl-and-opengl-es
- Chris Pruett,“面向 Android 的游戲開發: 快速入門”:?http://android-developers.blogspot.com/2010/06/game-development-for-android-quick.html
- Tewdew Software,“Android 紋理決策”:?http://blog.tewdew.com/post/7362195285/the-android-texture-decision
- Jack Palevich,“GLES Quake — Quake 向 Android 平臺的移植”:?http://grammerjack.blogspot.com/2009/10/gles-quake-port-of-quake-to-android.html
- Romain Guy 和 Chet Haase, “Android 4.0 圖形和動畫”:?http://android-developers.blogspot.com/2011/11/android-40-graphics-and-animations.html
總結
以上是生活随笔為你收集整理的在英特尔® 凌动™ 处理器上将 OpenGL* 游戏移植到 Android* (第一部分)的全部內容,希望文章能夠幫你解決所遇到的問題。
 
                            
                        - 上一篇: C# XML 添加,修改,删除Xml节点
- 下一篇: 使用Activator.CreateIn
