你們好,這是個人OpenGL ES
高級進階系列文章,在個人github
上有一個與本系列文章對應的項目,歡迎關注,連接:github.com/kenneycode/…git
今天給你們介紹EGL
和GL
線程,EGL
是OpenGL ES
開發中很重要的一部分,特別是當想實現一些比較複雜的功能時,就有必要去了解EGL
,另外,瞭解EGL
也對掌握渲染底層的基礎原理很重要,我認爲是OpenGL ES
開發者邁向一個新臺階必須要掌握的東西,而GL
線程則是一個與EGL
環境綁定了的線程,綁定後能夠在這個線程中執行GL
操做。github
EGL
它是個什麼東西呢?一句總結就是:EGL
是鏈接OpenGL ES
與本地窗口系統的橋樑。編程
如何理解這句話呢?咱們知道OpenGL
是跨平臺的,可是不一樣平臺上的窗口系統是不同的,它就須要一個東西幫助OpenGL
與本地窗口系統進行對接、管理及執行GL
命令等。多線程
這聽起來挺底層的,咱們爲何須要去了解這個呢?我舉幾個例子,好比你想把你的GL
邏輯多線程化,以提高效率,若是不瞭解EGL
,直接把GL
操做簡單地拆分到多個線程中執行,會發現有問題,後文也會提到,再好比,你想用MediaCodec
作視頻編解碼,你會發現,也經常須要了解EGL
,特別是當你想在編碼前、解碼後作OpenGL
特效處理時,好比將原視頻進行OpenGL ES
特效渲染而後編碼保存,或者是解碼原視頻而後進行OpenGL ES
特效渲染再顯示出來。編碼時須要將要編碼的幀渲染到MediaCodec
給你的一塊surface
上,而這些操做須要有EGL
才能作,而解碼時是解碼到一塊你本身指定的surface
上,此時你也沒有一個現成的EGL
環境,若是你想解碼出來先用OpenGL ES
作些特效處理再顯示出來,那麼這時也須要EGL
環境。ide
爲了讓你們直接感受一下EGL
所起的做用,咱們來試試幾段代碼,一段是咱們很熟悉的GLSurfaceView
的Renderer
的代碼,咱們能夠在回調中作GL
操做,好比這裏咱們建立一個texture
:編碼
glSurfaceView.setRenderer(object : GLSurfaceView.Renderer {
override fun onDrawFrame(gl: GL10?) {
}
override fun onSurfaceChanged(gl: GL10?, width: Int, height: Int) {
}
override fun onSurfaceCreated(gl: GL10?, config: EGLConfig?) {
val textures = IntArray(1)
GLES30.glGenTextures(textures.size, textures, 0)
val imageTexture = textures[0]
}
})
複製代碼
若是你查看這個texture
的值,會發現它大於0,也就是建立成功了,另外也能夠經過OpenGL ES
的方法GLES30.glIsTexture(imageTexture)
來判斷一個texture
是否是合法的texture
。spa
若是咱們把上述建立texture
的操做放到主線程中會怎樣?線程
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val textures = IntArray(1)
GLES30.glGenTextures(textures.size, textures, 0)
val imageTexture = textures[0]
}
}
複製代碼
咱們會發現,這時建立出來的texture
是0,也就是建立失敗了,其實不僅是建立texture
失敗,其它GL
操做一概會失敗。code
若是放到一個子線程中呢?cdn
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Thread {
val textures = IntArray(1)
GLES30.glGenTextures(textures.size, textures, 0)
val imageTexture = textures[0]
}.start()
}
}
複製代碼
效果是同樣的,也會失敗,爲何?緣由就是在一個沒有EGL環境的線程中調用了OpenGL ES API,那如何讓一個線程擁有EGL
環境?建立EGL
環境,步驟還很多,一共有如下幾個步驟:
獲取顯示設備
eglDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY)
複製代碼
這裏獲取的是default的顯示設備,大多數狀況下咱們都是獲取default,由於大多數狀況下設備只有一個屏幕,本文也只討論這種狀況。
初始化顯示設備
val version = IntArray(2)
EGL14.eglInitialize(eglDisplay, version, 0, version, 1)
複製代碼
這裏初始化完成後,會返回給咱們支持的EGL
的最大和最小版本號。
選擇config
val attribList = intArrayOf(
EGL14.EGL_RED_SIZE,
8,
EGL14.EGL_GREEN_SIZE,
8,
EGL14.EGL_BLUE_SIZE,
8,
EGL14.EGL_ALPHA_SIZE,
8,
EGL14.EGL_RENDERABLE_TYPE,
EGL14.EGL_OPENGL_ES2_BIT or EGLExt.EGL_OPENGL_ES3_BIT_KHR,
EGL14.EGL_NONE
)
val eglConfig = arrayOfNulls<EGLConfig>(1)
val numConfigs = IntArray(1)
EGL14.eglChooseConfig(
eglDisplay,
attribList,
0,
eglConfig,
0,
eglConfig.size,
numConfigs,
0
)
複製代碼
這步操做告訴系統咱們指望的EGL
配置,而後系統返回給咱們一個列表,按配置的匹配程度排序,由於系統不必定有咱們指望的配置,所以要經過查詢讓系統返回儘量接近的配置。
attribList
是咱們指望的配置,咱們這裏的配置是將RGBA
顏色深度設置爲8位,並將OpenGL ES
版本設置爲2和3,表示同時支持OpenGL 2
和OpenGL 3
,最後以一個EGL14.EGL_NONE
做爲結束符。
eglConfig
是返回的儘量接近咱們指望的配置的列表,一般咱們取第0個來使用,即最符合咱們指望配置。
建立EGL Context
eglContext = EGL14.eglCreateContext(
eglDisplay,
eglConfig[0],
EGL14.EGL_NO_CONTEXT,
intArrayOf(EGL14.EGL_CONTEXT_CLIENT_VERSION, 3, EGL14.EGL_NONE),
0
)
複製代碼
eglDisplay
便是以前建立的顯示設備,注意第三個參數,它是指定一個共享的EGL Context
,共享後,2個EGL Context
能夠相互使用對方建立的texture
等資源,默認狀況下是不共享的,但不是全部資源都能共享,例如program
就是不共享的。
建立EGL Surface
val surfaceAttribs = intArrayOf(EGL14.EGL_NONE)
eglSurface = EGL14.eglCreatePbufferSurface(
eglDisplay,
eglConfig[0],
surfaceAttribs,
0
)
複製代碼
EGL Surface
是什麼東西?能夠理解成是一個用於承載顯示內容的東西,這裏有2種EGL Surface
能夠選擇,一種是window surface
,一種是pbuffer surface
,若是咱們建立這個EGL
環境是爲了跟一塊Surface
綁定,例如但願給Surface View
建立一個EGL
環境,使用OpenGL ES
渲染到Surface View
上,那麼就要選擇window surface
,對應的建立方法爲:
EGL14.eglCreateWindowSurface(
eglDisplay,
eglConfig[0],
surface,
surfaceAttribs,
0
)
複製代碼
其中surface
就是這個Surface View
對應的surface
。若是咱們不須要渲染出來看,那麼就能夠建立一個pbuffer surface
,它不和surface
綁定,不須要傳surface
給它,這也稱爲離屏渲染,本文中將建立pbuffer surface
。
這裏提一個細節,如今的surface
所對應的buffer
通常都是雙buffer
,以便於一個buffer
正在顯示的時候,有另外一個buffer
可用於渲染, 正在顯示的buffer
稱爲front buffer
,正在渲染的buffer
稱爲back buffer
,若是要渲染到surface
上,必須在渲染後調用EGL14.eglSwapBuffers(eglDisplay, eglSurface)
將顯示buffer
和渲染buffer
進行交換纔會生效,不然會一直渲染到back buffer
上,這個buffer
沒法變成front buffer
顯示到surface
上。
綁定EGL
前面的幾個步驟,咱們把一些須要建立的東西都建立好了,下面就要將EGL
綁定到線程上讓它具體有EGL
環境:
EGL14.eglMakeCurrent(
eglDisplay,
eglSurface,
eglSurface,
eglContext
)
複製代碼
注意,一個線程只能綁定一個EGL
環境,若是以前綁過其它的,後面又綁了一個,那就會是最後綁的那個。
至此,就能讓一個線程擁有EGL
環境了,此後就能夠順利地作GL
操做了。
好,咱們看一下咱們的例子代碼:
Thread {
val egl = EGL()
egl.init()
egl.bind()
val textures = IntArray(1)
GLES30.glGenTextures(textures.size, textures, 0)
val imageTexture = textures[0]
assert(GLES30.glIsTexture(imageTexture))
egl.release()
}.start()
複製代碼
代碼很簡單,只是爲了驗證是否有了EGL
環境,就不寫複雜的操做了,EGL
就是對前文所述的操做的一個封裝類,init()
方法對應了獲取顯示設備、初始化顯示設備、選擇config
、建立EGL Context
和建立EGL Surface
,bind()
方法對應了eglMakeCurrent()
。
EGL
實際上能玩的花樣不少,我封裝了一個EGL
和GL
線程庫:GLKit(github.com/kenneycode/…),有了這個庫,能夠方便地使用EGL
及自帶EGL
環境的線程,快速實現用OpenGL ES
渲染到SurfaceView
、TextureView
上,以及實現OpenGL ES
多線程編程,歡迎有須要的朋友取用,我也爲這個庫寫了demo
,感興趣的朋友能夠去看看:
好,看到這,也許有朋友想問了,爲何咱們使用GLSurfaceView
的時候,就徹底不須要去管這堆東西呢?那是由於GLSurfaceView
幫你封裝好啦,你們若是去看過GLSurfaceView
的源碼,就會發現它裏面也是按前文所說的步驟一步一步先建立好EGL
環境的,它裏面有一個GLThread
類,就是一個綁定了EGL
環境的線程,源碼邏輯大體是這樣的:
while(...) {
...
mEglHelper.start(); // 獲取顯示設備、初始化顯示設備、選擇config、建立EGL Context
...
mEglHelper.createSurface(); // 建立EGL Surface並綁定EGL Context
...
回調Renderer的onSurfaceCreated()
...
回調Renderer的onSurfaceChanged ()
...
回調Renderer的onDrawFrame()
...
mEglHelper.swap(); // eglSwapBuffer
...
}
複製代碼
看到這,你們知道爲何咱們在GLSurfaceView
的Renderer
回調方法中能使用OpenGL ES API
了吧?由於在回調前,它就給你建立好EGL
環境並綁定好了。
代碼在我github
的OpenGLESPro
項目中,本文對應的是SampleEGL
,項目連接:github.com/kenneycode/…
感謝閱讀!