2018年10月29日 星期一

從OpenGL跨到OpenGLES的心得

從OpenGL跨到OpenGLES的心得

前言

  最近將C++的繪圖引擎跨到Android,PC端使用的是OpenGL4.6,Android端使用的是OpenGLES3.2,目前只完成初步的測試,就是只能用基本的Shader畫三角形。這次我做了個挑戰,由於手機有支援OpenGLES3.2,所以直接跨到OpenGLES3.2,果不其然碰到了不少問題,這裡做個紀錄。

內容

  從OpenGL跨到OpenGLES的差異這裡分成"Context的差異"與"API的差異"兩部分。Context的差異是必然的,因為PC端的OpenGL為Windows與Linux分別準備了WGL與GLX,自然也別期望android平台可以用一樣的方法,而API的差異部分比想像來的少,透過docs.gl可以查詢OpenGL與OpenGLES的差異。

  Context的差異的部分主要有兩大問題,"執行序(Thread)的問題"與"視窗可能被銷毀的問題",執行序(Thread)的問題主要是因為OpenGL與OpenGLES的Context都會認執行序,也就是用哪個執行序創建Context就只能用該執行序來執行繪圖,這個規則在PC時問題不大,但在Android時會有麻煩,由於Android的Activity不提供類似main()的Function,所以要自己做一個執行序(Thread)來當作main(),這時候就可能發生創建Context與繪圖的執行序(Thread)不是同一個,而且不幸的是這件事情發生時不會有任何的報錯!只會得到一個黑畫面而已,一定要確保創建Context、執行繪圖與銷毀Context都是用C++創建的執行序(Thread),接合Android的Activity的事件只能用來通知有事件發生,不能執行任何EGL與OpenGLES的API。視窗可能被銷毀的問題是Android跟Windows與Linux很大的不同,在Windows與Linux把視窗縮到最小是將視窗Hide的意思,但Android很不一樣,當進入休眠或跳出程式時,都會去銷毀視窗!還記得EGL創建Context時用的eglMakeCurrent()嗎?裡面的Surface參數會在進入休眠或跳出程式時失效,所以必須要對該事件做處理,處理的流程可以參考Android 前后台切换与OpenGL(EGL)创建销毁的周期,裡面有個ResetSurface()可以在事件發生時喚起。

  API的差異部分如下:
1.不支援glDepth(),請用glDepthf()來替代
2.不支援GL_TEXTURE_1D,如果需要1D的Texture請用2D替代
3.不支援glMapBuffer(),寫UBO的話請用glBufferSubData()替代
4.GLSL編譯的時候請將"#version 300 es"放在最開頭
5.Extension不互通,如在OpenGL判斷是否支援UBO可以透過Extension檢查,OpenGLES則是版本來決定(OpenGLES3以後就支援)

前4點都還好很容易克服,第五點就比較頭痛了,必須知道各個版本的規格,只能用經驗來克服了。

  最後再說個關於include的問題,如果查詢Android NDK Native APIs時會發現以下畫面
OpenGLES3.2的include
這個include是錯的!如果去NDK把"GLES3/gl3ext.h"打開的話,就會發現這個Header是個只有註解的空檔案,正確的include如下
#include "GLES3/gl32.h"
#include "GLES2/gl2.ext.h"

雖然看起來有些奇怪,但如果不這麼做的話,會沒辦法創建壓縮格式的Texture,如ASTC、PVR...等,因為Marco都定義在"GLES2/gl2ext.h",由於OpenGLES目前只有跨到Andoird,不知這是不是OpenGLES的標準用法,只能日後再確認了。

參考資料

Android NDK Native APIs
docs.gl
Android 前后台切换与OpenGL(EGL)创建销毁的周期

沒有留言:

張貼留言