OpenGL 学习教程
Android OpenGL ES 学习(一) – 基本概念
Android OpenGL ES 学习(二) – 图形渲染管线和GLSL
Android OpenGL ES 学习(三) – 绘制平面图形
Android OpenGL ES 学习(四) – 正交投影
Android OpenGL ES 学习(五) – 渐变色
Android OpenGL ES 学习(六) – 使用 VBO、VAO 和 EBO/IBO 优化程序
Android OpenGL ES 学习(七) – 纹理
Android OpenGL ES 学习(八) –矩阵变换
Android OpenGL ES 学习(九) – 坐标系统和。实现3D效果
Android OpenGL ES 学习(十) – GLSurfaceView 源码解析GL线程以及自定义 EGL
代码工程地址: https://github.com/LillteZheng/OpenGLDemo.git
前面我们已经学习了 GL 的基本实现效果,我们一般的操作顺序就是
那你会不会好奇,为啥我们按照这样的顺序,弄完就可以正常显示了呢?你是否对OpenGL 绘制的 “完整流程” 感兴趣呢?你是否也遇到下面这些疑问呢?
这一章,我们带着这些疑问,来探究 OpenGL 渲染的完整流程,今天要完成的效果,不使用GLSurfaceView,编写 EGL + SurfaceVIew 实现 3D 效果:
为什么叫完整流程?前面也说道,Android 系统把复杂的过程都封装好了,我们只需要调用 GlSurfaceView 就可以很轻松的拿到OpenGL 的渲染环境,然后编写着色器代码和逻辑即可。
但实际上,OpenGL 的核心流程应该是这样的:
为了得到真相,我们通过查看GLSurfaceView 的源码去看。
从入口出发,我们都是调用 glSurface.setRenderer() 去设置的,进到源码后可以发现,它主要做了以下几件事情:
// EGLConfig 对象.用于指定OpenGL颜色、深度、模版等设置
if (mEGLConfigChooser == null) {
mEGLConfigChooser = new SimpleEGLConfigChooser(true);
}
// EGLContext 对象.用于提供EGLContext创建和销毁的处理
if (mEGLContextFactory == null) {
mEGLContextFactory = new DefaultContextFactory();
}
// EGLSurface窗口对象. 用于提供EGLSurface创建和销毁的处理
if (mEGLWindowSurfaceFactory == null) {
mEGLWindowSurfaceFactory = new DefaultWindowSurfaceFactory();
}
//开启 GLThread线程
mRenderer = renderer;
mGLThread = new GLThread(mThisWeakRef);
mGLThread.start();
额,看完这个注释,你可能会一头雾水,EGL 是啥,EGLSurface 又是啥,看来我们需要先了解 EGL .
EGL 是 OpenGL 与本地窗口系统 Native Window System)之间的通信接口,它的主要作用是:
不同平台上EGL配置是不一样的,而OpenGL的调用方式是一致的,也就是说OpenGL的跨平台特性依赖于EGL接口。
EGL 通过创建 eglSurface,和上下文 eglContext,就可以通过相关 api 访问本地窗口系统,实现绘制。如下图,体现了 EGL ,display 和 context 三者之间的关系。
在开发时,GLSurfaceView 已经帮我们对 Display,Surface,Context 进行封装管理,我们可以很方便的调用 GLSurfaceView.Render ,实现渲染绘制。
使用 EGL 渲染的一般步骤:
原来如此,原来 GLSurfaceView 里面,已经帮我们把 EGL 初始化好了,回到 glSurface.setRenderer() 的方法:
// EGLConfig 对象.用于指定OpenGL颜色、深度、模版等设置
if (mEGLConfigChooser == null) {
mEGLConfigChooser = new SimpleEGLConfigChooser(true);
}
// EGLContext 对象.用于提供EGLContext创建和销毁的处理
if (mEGLContextFactory == null) {
mEGLContextFactory = new DefaultContextFactory();
}
// EGLSurface窗口对象. 用于提供EGLSurface创建和销毁的处理
if (mEGLWindowSurfaceFactory == null) {
mEGLWindowSurfaceFactory = new DefaultWindowSurfaceFactory();
}
//开启 GLThread线程
mRenderer = renderer;
mGLThread = new GLThread(mThisWeakRef);
mGLThread.start();
我们就知道,原来注释是这个意思,管理关键还得看一下 GL 线程做了什么。
我们去看看它和其他线程有什么区别:
实际上,它跟普通线程没有什么区别,也是继承Thread,关键就是 guradedRun() 方法,它实际上把 EGL 给初始化好,然后回调相关方法,让你去绘制,在一个 while 循环中,它做了哪些操作呢?
while(true){
//初始化 egl,配置等信息
mEglHelper.start()
//回去 Surface,并配置到当前线程
mElgHeper.createSurface()
回调 onSurfaceCreated()
回调 onSurfaceChanged()
回调 onDrawFrame()
//egl交换缓冲区,呈现画面
mEglHelper.swap()
}
从这里看,guradedRun() 跟我们之前说的流程基本是一样的,还有非常熟悉的 onSurfaceCreated(), onSurfaceChanged() 和 onDrawFrame() 也是在 EGL 配置之后,再回调的。
这里我们也可以得出一个结论,就是 GL线程跟普通线程没什么区别,就是一个普通线程,只是按照 OpenGL 完整的绘图方式走了一遍,因此我们也可以自己自定义,也可以实现OpenGL 的绘制,从 glGenTextures 等绘图api 都是 native 方法也可以证明这一点。
回答这个问题之前,先看看纹理对象是怎么生成的,查看 GLES30.glGenTextures 方法:
// C function void glGenTextures ( GLsizei n, GLuint *textures )
public static native void glGenTextures(
int n,
int[] textures,
int offset
);
看来跟 EGL 没关系,那它是怎么知道是 GL 线程去调,还是普通线程去调呢?它又是怎么把 glGenTextures 和 glDeleteTextures 对应到正确的线程上的呢?看源码:
实际上,它会先通过 getGlThreadSpecific() 这个方法去拿到一个 context,这个 context 实际上就是 EGL Context ,它是比较特殊的。什么意思呢,就是不同线程去拿,得到的 EGL Context 可能都不一样,这取决于给这个 EGL Context 是什么,我的理解是相当于线程类自己的局部变量,每个线程存储的 context 都不一样。
那EGL Context 是什么时候被设置进去的呢?
还记得 前面说到的 eglMakeCurrent() 这个方法吗?
egl.eglMakeCurrent(display, eglSurface, eglSurface, eglContext)
实际上,第四个参数eglContext 最终会设置给 setGlThreadSpecific() 中的变量存储起来,给其 API 使用。
然后我们再看一下 eglMakeCurrent 做了什么:
可以得到以下总结:
结论:
关于 texture 与 egl context 的关系,可以参考这篇文章 https://cloud.tencent.com/developer/article/1035505
因此,我们可以回答一下上面的问题:
前面说到,GLSurfaceView 帮我们把 egl 的配置都弄好了,优点是使用简单,缺点是当我们想共享同个 EGL context,实现同个场景,不同 surface 的渲染时,GLSurfaceView 就使用了。
所以,我们自定义自己的 GLSurfaceView。
首先,创建一个类,让它也继承 SurfaceView,并创建一个线程,用来加载 EGL :
inner class EglSurfaceView(context: Context) : SurfaceView(context), SurfaceHolder.Callback {
init {
holder.addCallback(this)
}l
override fun surfaceCreated(holder: SurfaceHolder) {
}
override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {
}
override fun surfaceDestroyed(holder: SurfaceHolder) {
}
inner class EglThread(private val surface: Surface) : Thread() {
override fun run() {
....
}
}
}
接着,就需要创建 EGL 了,这里我们通过模仿 GLSurfaceView 的 EGLHelper 方法,得到以下类:
inner class EglHelper {
private var egl: EGL10? = null
private var eglDisplay: EGLDisplay? = null
private var eglSurface: EGLSurface? = null
private var eglContext: EGLContext? = null
fun initEgl(surface: Surface) {
//1、得到Egl实例:
val egl = EGLContext.getEGL() as EGL10
//2、得到默认的显示设备(就是窗口)
val display = egl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY)
if (display === EGL10.EGL_NO_DISPLAY) {
throw RuntimeException("eglGetDisplay failed")
}
//3、初始化默认显示设备
val displayVersions = IntArray(2)
if (!egl.eglInitialize(display, displayVersions)) {
throw RuntimeException("eglInitialize failed")
}
//4、设置显示设备的属性
val attr = intArrayOf(
EGL14.EGL_RED_SIZE, 8,
EGL10.EGL_GREEN_SIZE, 8,
EGL10.EGL_BLUE_SIZE, 8,
EGL10.EGL_ALPHA_SIZE, 8,
EGL10.EGL_DEPTH_SIZE, 8,
EGL10.EGL_STENCIL_SIZE, 8,
EGL10.EGL_RENDERABLE_TYPE, 4,
EGL10.EGL_NONE
)
//5、从系统中获取对应属性的配置
val num_config = IntArray(1)
if(!egl.eglChooseConfig(display, attr, null, 1, num_config)) {
throw RuntimeException("eglChooseConfig failed")
}
val numConfigs = num_config[0]
if (numConfigs <= 0) {
throw RuntimeException("No configs match configSpec")
}
val configs = arrayOfNulls<EGLConfig>(numConfigs)
if (!egl.eglChooseConfig(display, attr, configs, numConfigs, num_config)) {
throw RuntimeException("eglChooseConfig#2 failed")
}
//6、创建EglContext
val attrib_list = intArrayOf(
EGL14.EGL_CONTEXT_CLIENT_VERSION, 3,
EGL10.EGL_NONE
)
val eglContext = egl.eglCreateContext(display,
configs[0],
EGL10.EGL_NO_CONTEXT, attrib_list)
//7、创建渲染的Surface
val eglSurface = egl.eglCreateWindowSurface(display, configs[0], surface, null)
//8、绑定EglContext和Surface到显示设备中
if (!egl.eglMakeCurrent(display, eglSurface, eglSurface, eglContext)) {
throw RuntimeException("eglMakeCurrent fail")
}
this.egl = egl
this.eglDisplay = display
this.eglSurface = eglSurface
this.eglContext = eglContext
}
fun swapBuffers() {
egl?.eglSwapBuffers(eglDisplay, eglSurface)
}
fun destroy() {
egl?.apply {
eglMakeCurrent(
eglDisplay,
EGL10.EGL_NO_SURFACE,
EGL10.EGL_NO_SURFACE,
EGL10.EGL_NO_CONTEXT
)
eglDestroySurface(eglDisplay, eglSurface)
eglSurface = null
eglDestroyContext(eglDisplay, eglContext)
eglContext = null
eglTerminate(eglDisplay)
eglDisplay = null
egl = null
}
}
}
步骤比较简单,就是:
然后我们把 EhlHeper 跟线程结合起来,完整的代码如下:
/**
* 自定义一个 SurfaceView,里面的线程模拟 EGL 环境
*/
inner class EglSurfaceView(context: Context) : SurfaceView(context), SurfaceHolder.Callback {
init {
holder.addCallback(this)
}
private var eglThread: EglThread? = null
override fun surfaceCreated(holder: SurfaceHolder) {
eglThread = EglThread(holder.surface)
eglThread?.start()
}
override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {
eglThread?.changeSize(width, height)
}
override fun surfaceDestroyed(holder: SurfaceHolder) {
eglThread?.release()
}
/**
* gl线程,里面包含 EGL 创建流程
*/
inner class EglThread(private val surface: Surface) : Thread() {
private var isExit = false
private var isFirst = true
private var isSizeChange = false
private var eglHelper: EglHelper? = null
private var width = 0
private var height = 0
override fun run() {
super.run()
eglHelper = EglHelper().apply {
initEgl(surface)
}
while (true) {
if (isExit) {
release()
break
}
if (isFirst) {
isFirst = false
//回调给主类
this@L8_ShapeRender.onSurfaceCreated(null, null)
}
if (isSizeChange) {
isSizeChange = false
this@L8_ShapeRender.onSurfaceChanged(null, width, height)
}
eglHelper?.let {
this@L8_ShapeRender.onDrawFrame(null)
it.swapBuffers()
}
try {
sleep(16)
} catch (e: Exception) {
}
}
}
fun release() {
isExit = true
eglHelper?.destroy()
surface.release()
}
fun changeSize(width: Int, height: Int) {
this.width = width
this.height = height
isSizeChange = true
}
}
}
都比较好理解,可以看到,我们也通过 onSurfaceCreated,onSurfaceChanged 和 onDrawFrame 回调给外部,这样,着色器相关的,就不用改变啦。
看一下效果,可以正常运行:
参考:
https://cloud.tencent.com/developer/article/1035505
https://cloud.tencent.com/developer/article/1899820