OpenGL ES 高級進階:EGL及GL線程

你們好,這是個人OpenGL ES 高級進階系列文章,在個人github上有一個與本系列文章對應的項目,歡迎關注,連接:github.com/kenneycode/…git

今天給你們介紹EGLGL線程,EGLOpenGL 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所起的做用,咱們來試試幾段代碼,一段是咱們很熟悉的GLSurfaceViewRenderer的代碼,咱們能夠在回調中作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是否是合法的texturespa

若是咱們把上述建立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 2OpenGL 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 Surfacebind()方法對應了eglMakeCurrent()

EGL實際上能玩的花樣不少,我封裝了一個EGLGL線程庫:GLKit(github.com/kenneycode/…),有了這個庫,能夠方便地使用EGL及自帶EGL環境的線程,快速實現用OpenGL ES渲染到SurfaceViewTextureView上,以及實現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
    ...           
}

複製代碼

看到這,你們知道爲何咱們在GLSurfaceViewRenderer回調方法中能使用OpenGL ES API了吧?由於在回調前,它就給你建立好EGL環境並綁定好了。

代碼在我githubOpenGLESPro項目中,本文對應的是SampleEGL,項目連接:github.com/kenneycode/…

感謝閱讀!

相關文章
相關標籤/搜索