大家好,这是我的OpenGL ES
高级进阶系列文章,在我的github
上有一个与本系列文章对应的项目,欢迎关注,链接:github.com/kenneycode/…
今天给大家介绍EGL
和GL
线程,EGL
是OpenGL ES
开发中很重要的一部分,特别是当想实现一些比较复杂的功能时,就有必要去了解EGL
,另外,了解EGL
也对掌握渲染底层的基础原理很重要,我认为是OpenGL ES
开发者迈向一个新台阶必需要掌握的东西,而GL
线程则是一个与EGL
环境绑定了的线程,绑定后可以在这个线程中执行GL
操作。
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
环境。
为了让大家直接感觉一下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
。
如果我们把上述创建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
操作一律会失败。
如果放到一个子线程中呢?
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
。eglSurface = EGL14.eglCreatePbufferSurface(eglDisplay, eglConfig[0], surfaceAttribs, 0)
这里提一个细节,现在的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/…
感谢阅读!
今天的文章OpenGL ES 高级进阶:EGL及GL线程分享到此就结束了,感谢您的阅读。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:http://bianchenghao.cn/19621.html