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 的基本实现效果,我们一般的操作顺序就是
- onSurfaceCreated: 进行着色器的加载编译
- onSurfaceChanged:设置 gl 的大小,或者设置正交投影
- onDrawFrame:绘制
那你会不会好奇,为啥我们按照这样的顺序,弄完就可以正常显示了呢?你是否对OpenGL 绘制的 “完整流程” 感兴趣呢?你是否也遇到下面这些疑问呢?
- GL线程和普通线程有什么区别?
- texture所占用的空间是跟GL线程绑定的吗?
- 为什么通常一个GL线程的texture等数据,在另一个GL线程没法用?
- 为什么通常GL线程销毁后,为什么texture也跟着销毁了?
- 不同线程如何共享OpenGL数据
这一章,我们带着这些疑问,来探究 OpenGL 渲染的完整流程,今天要完成的效果,不使用GLSurfaceView,编写 EGL + SurfaceVIew 实现 3D 效果:
一. 渲染完整流程
为什么叫完整流程?前面也说道,Android 系统把复杂的过程都封装好了,我们只需要调用 GlSurfaceView 就可以很轻松的拿到OpenGL 的渲染环境,然后编写着色器代码和逻辑即可。
但实际上,OpenGL 的核心流程应该是这样的:
为了得到真相,我们通过查看GLSurfaceView 的源码去看。
二. GLSurfaceView 源码
从入口出发,我们都是调用 glSurface.setRenderer() 去设置的,进到源码后可以发现,它主要做了以下几件事情:
// EGLConfig 对象.用于指定OpenGL颜色、深度、模版等设置 if (mEGLConfigChooser == null) { mEGLConfigChooser = new SimpleEGLConfigChooser(true); }
<span class="token comment">// EGLContext 对象.用于提供EGLContext创建和销毁的处理</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>mEGLContextFactory <span class="token operator">==</span> <span class="token keyword">null</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span> mEGLContextFactory <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">DefaultContextFactory</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment">// EGLSurface窗口对象. 用于提供EGLSurface创建和销毁的处理</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>mEGLWindowSurfaceFactory <span class="token operator">==</span> <span class="token keyword">null</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span> mEGLWindowSurfaceFactory <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">DefaultWindowSurfaceFactory</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment">//开启 GLThread线程 </span> mRenderer <span class="token operator">=</span> renderer<span class="token punctuation">;</span> mGLThread <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">GLThread</span><span class="token punctuation">(</span>mThisWeakRef<span class="token punctuation">)</span><span class="token punctuation">;</span> mGLThread<span class="token punctuation">.</span><span class="token function">start</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
额,看完这个注释,你可能会一头雾水,EGL 是啥,EGLSurface 又是啥,看来我们需要先了解 EGL .
2.1 EGL
EGL 是 OpenGL 与本地窗口系统 Native Window System)之间的通信接口,它的主要作用是:
- 与设备的原生窗口系统通信
- 查询绘图表面的可用类型和配置
- OpenGL 和其他图形渲染 API 渲染
不同平台上EGL配置是不一样的,而OpenGL的调用方式是一致的,也就是说OpenGL的跨平台特性依赖于EGL接口。
EGL 通过创建 eglSurface,和上下文 eglContext,就可以通过相关 api 访问本地窗口系统,实现绘制。如下图,体现了 EGL ,display 和 context 三者之间的关系。
- Display(EglDisplay):实际显示设备的抽象,你可以理解成显示模块
- Surface(EglSurface):用来存储图像的内存区域 FrameBuffer 的抽象,包括 Color Buffer (颜色缓冲区),Stencil Buffer(模板缓冲区),Depth Buffer(深度缓冲区)
- Context(EglContext):存储 OpenGL ES 绘图的一些状态信息,比如存储 texture
在开发时,GLSurfaceView 已经帮我们对 Display,Surface,Context 进行封装管理,我们可以很方便的调用 GLSurfaceView.Render ,实现渲染绘制。
使用 EGL 渲染的一般步骤:
- 获取 EGLDisplay 对象,建立与本地窗口系统的连接:调用 eglGetDisplay 方法得到 EGLDisplay。
- 初始化 EGL 方法:打开连接之后,调用 eglInitialize 方法初始化。
- 获取 EGLConfig 对象,确定渲染表面的配置信息:调用 eglChooseConfig 方法得到 EGLConfig。
- 创建渲染表面 EGLSurface:通过 EGLDisplay 和 EGLConfig ,调用 eglCreateWindowSurface 或 eglCreatePbufferSurface 方法创建渲染表面,得到 EGLSurface,其中 eglCreateWindowSurface 用于创建屏幕上渲染区域,eglCreatePbufferSurface 用于创建屏幕外渲染区域。
- 创建渲染上下文 EGLContext :通过 EGLDisplay 和 EGLConfig ,调用 eglCreateContext 方法创建渲染上下文,得到 EGLContext。
- 绑定上下文:通过 eglMakeCurrent 方法将 EGLSurface、EGLContext、EGLDisplay 三者绑定,绑定成功之后 OpenGLES 环境就创建好了,接下来便可以进行渲染。
- 交换缓冲:OpenGLES 绘制结束后,使用 eglSwapBuffers 方法交换前后缓冲,将绘制内容显示到屏幕上,而屏幕外的渲染不需要调用此方法。
- 释放 EGL 环境:绘制结束后,不再需要使用 EGL 时,需要取消 eglMakeCurrent 的绑定,销毁 EGLDisplay、EGLSurface、EGLContext 三个对象。
原来如此,原来 GLSurfaceView 里面,已经帮我们把 EGL 初始化好了,回到 glSurface.setRenderer() 的方法:
// EGLConfig 对象.用于指定OpenGL颜色、深度、模版等设置 if (mEGLConfigChooser == null) { mEGLConfigChooser = new SimpleEGLConfigChooser(true); }
<span class="token comment">// EGLContext 对象.用于提供EGLContext创建和销毁的处理</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>mEGLContextFactory <span class="token operator">==</span> <span class="token keyword">null</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span> mEGLContextFactory <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">DefaultContextFactory</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment">// EGLSurface窗口对象. 用于提供EGLSurface创建和销毁的处理</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>mEGLWindowSurfaceFactory <span class="token operator">==</span> <span class="token keyword">null</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span> mEGLWindowSurfaceFactory <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">DefaultWindowSurfaceFactory</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment">//开启 GLThread线程 </span> mRenderer <span class="token operator">=</span> renderer<span class="token punctuation">;</span> mGLThread <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">GLThread</span><span class="token punctuation">(</span>mThisWeakRef<span class="token punctuation">)</span><span class="token punctuation">;</span> mGLThread<span class="token punctuation">.</span><span class="token function">start</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
我们就知道,原来注释是这个意思,管理关键还得看一下 GL 线程做了什么。
2.2 GL 线程
我们去看看它和其他线程有什么区别:
实际上,它跟普通线程没有什么区别,也是继承Thread,关键就是 guradedRun() 方法,它实际上把 EGL 给初始化好,然后回调相关方法,让你去绘制,在一个 while 循环中,它做了哪些操作呢?
while(true){
//初始化 egl,配置等信息
mEglHelper.start()
//回去 Surface,并配置到当前线程
mElgHeper.createSurface()
回调 onSurfaceCreated()
回调 onSurfaceChanged()
回调 onDrawFrame()
//egl交换缓冲区,呈现画面
mEglHelper.swap()
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
从这里看,guradedRun() 跟我们之前说的流程基本是一样的,还有非常熟悉的 onSurfaceCreated(), onSurfaceChanged() 和 onDrawFrame() 也是在 EGL 配置之后,再回调的。
这里我们也可以得出一个结论,就是 GL线程跟普通线程没什么区别,就是一个普通线程,只是按照 OpenGL 完整的绘图方式走了一遍,因此我们也可以自己自定义,也可以实现OpenGL 的绘制,从 glGenTextures 等绘图api 都是 native 方法也可以证明这一点。
2.3 为什么通常一个GL线程的texture等数据,在另一个GL线程没法用
回答这个问题之前,先看看纹理对象是怎么生成的,查看 GLES30.glGenTextures 方法:
// C function void glGenTextures ( GLsizei n, GLuint *textures )
public static native void glGenTextures(
int n,
int[] textures,
int offset
);
- 1
- 2
- 3
- 4
- 5
- 6
- 7
看来跟 EGL 没关系,那它是怎么知道是 GL 线程去调,还是普通线程去调呢?它又是怎么把 glGenTextures 和 glDeleteTextures 对应到正确的线程上的呢?看源码:
实际上,它会先通过 getGlThreadSpecific() 这个方法去拿到一个 context,这个 context 实际上就是 EGL Context ,它是比较特殊的。什么意思呢,就是不同线程去拿,得到的 EGL Context 可能都不一样,这取决于给这个 EGL Context 是什么,我的理解是相当于线程类自己的局部变量,每个线程存储的 context 都不一样。
那EGL Context 是什么时候被设置进去的呢?
还记得 前面说到的 eglMakeCurrent() 这个方法吗?
egl.eglMakeCurrent(display, eglSurface, eglSurface, eglContext)
- 1
实际上,第四个参数eglContext 最终会设置给 setGlThreadSpecific() 中的变量存储起来,给其 API 使用。
然后我们再看一下 eglMakeCurrent 做了什么:
可以得到以下总结:
- 获取当前线程 EGL context ,给底层使用
- 判断当前的 context 是否为 IS_CURRENT 状态,否则返回-1
- 如果 gl 是 IS_CURRENT 状态,但是不是当前线程,也return
- 如果 gl 不是 IS_CURRENT 状态,将 current 设置成非 IS_CURRENT 状态
- 将gl置为IS_CURRENT状态并将gl设置为当前线程的Thread Local的EGL Context
结论:
- 如果一个 EGL context 被一个线程使用 glMakeCurrent ,则它不能被另外一个线程 glMakeCurrent 了
- glMakeCurrent 之后, EGL Context 会跟当前的 EGL context 脱离关系
关于 texture 与 egl context 的关系,可以参考这篇文章 https://cloud.tencent.com/developer/article/1035505
因此,我们可以回答一下上面的问题:
- GL线程和普通线程有什么区别?: 没有区别,只是 GL线程 按照 OpenGL 的流程跑了一遍。
- texture所占用的空间是跟GL线程绑定的吗?:不是,跟 EGL context 绑定,跟 线程没关系。
- 为什么通常一个GL线程的texture等数据,在另一个GL线程没法用?:因为调用 OpenGL 接口,会先去获取 EGL context,而不同线程获取的状态是不一样的,而 texture 又放在 EGL context 中,因此无法在另一个 GL 线程使用。
- 为什么通常GL线程销毁后,为什么texture也跟着销毁了?:因为 GLSurfaceView 在销毁时,调用了 eglDestroyContext() 方法,销毁了 EGL context,从而 texture 也销毁了,跟 GL线程没关系
- 不同线程如何共享OpenGL数据:共享 EGL context,线程调用 eglCreateContext 时,传入另一个线程的 EGL Context。
三. 自定义 EGL 现成,使用SurfaceView+EGL 实现渲染
前面说到,GLSurfaceView 帮我们把 egl 的配置都弄好了,优点是使用简单,缺点是当我们想共享同个 EGL context,实现同个场景,不同 surface 的渲染时,GLSurfaceView 就使用了。
所以,我们自定义自己的 GLSurfaceView。
首先,创建一个类,让它也继承 SurfaceView,并创建一个线程,用来加载 EGL :
inner class EglSurfaceView(context: Context) : SurfaceView(context), SurfaceHolder.Callback {
init <span class="token punctuation">{<!-- --></span> holder<span class="token punctuation">.</span><span class="token function">addCallback</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">)</span> <span class="token punctuation">}</span>l override fun <span class="token function">surfaceCreated</span><span class="token punctuation">(</span>holder<span class="token operator">:</span> <span class="token class-name">SurfaceHolder</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span> <span class="token punctuation">}</span> override fun <span class="token function">surfaceChanged</span><span class="token punctuation">(</span>holder<span class="token operator">:</span> <span class="token class-name">SurfaceHolder</span><span class="token punctuation">,</span> format<span class="token operator">:</span> <span class="token class-name">Int</span><span class="token punctuation">,</span> width<span class="token operator">:</span> <span class="token class-name">Int</span><span class="token punctuation">,</span> height<span class="token operator">:</span> <span class="token class-name">Int</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span> <span class="token punctuation">}</span> override fun <span class="token function">surfaceDestroyed</span><span class="token punctuation">(</span>holder<span class="token operator">:</span> <span class="token class-name">SurfaceHolder</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span> <span class="token punctuation">}</span> inner <span class="token keyword">class</span> <span class="token class-name">EglThread</span><span class="token punctuation">(</span><span class="token keyword">private</span> val surface<span class="token operator">:</span> <span class="token class-name">Surface</span><span class="token punctuation">)</span> <span class="token operator">:</span> <span class="token class-name">Thread</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span> override fun <span class="token function">run</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span> <span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span>
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
接着,就需要创建 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") }
<span class="token comment">//3、初始化默认显示设备</span> val displayVersions <span class="token operator">=</span> <span class="token class-name">IntArray</span><span class="token punctuation">(</span><span class="token number">2</span><span class="token punctuation">)</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>egl<span class="token punctuation">.</span><span class="token function">eglInitialize</span><span class="token punctuation">(</span>display<span class="token punctuation">,</span> displayVersions<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span> <span class="token keyword">throw</span> <span class="token class-name">RuntimeException</span><span class="token punctuation">(</span><span class="token string">"eglInitialize failed"</span><span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token comment">//4、设置显示设备的属性</span> val attr <span class="token operator">=</span> <span class="token function">intArrayOf</span><span class="token punctuation">(</span> <span class="token constant">EGL14</span><span class="token punctuation">.</span><span class="token constant">EGL_RED_SIZE</span><span class="token punctuation">,</span> <span class="token number">8</span><span class="token punctuation">,</span> <span class="token constant">EGL10</span><span class="token punctuation">.</span><span class="token constant">EGL_GREEN_SIZE</span><span class="token punctuation">,</span> <span class="token number">8</span><span class="token punctuation">,</span> <span class="token constant">EGL10</span><span class="token punctuation">.</span><span class="token constant">EGL_BLUE_SIZE</span><span class="token punctuation">,</span> <span class="token number">8</span><span class="token punctuation">,</span> <span class="token constant">EGL10</span><span class="token punctuation">.</span><span class="token constant">EGL_ALPHA_SIZE</span><span class="token punctuation">,</span> <span class="token number">8</span><span class="token punctuation">,</span> <span class="token constant">EGL10</span><span class="token punctuation">.</span><span class="token constant">EGL_DEPTH_SIZE</span><span class="token punctuation">,</span> <span class="token number">8</span><span class="token punctuation">,</span> <span class="token constant">EGL10</span><span class="token punctuation">.</span><span class="token constant">EGL_STENCIL_SIZE</span><span class="token punctuation">,</span> <span class="token number">8</span><span class="token punctuation">,</span> <span class="token constant">EGL10</span><span class="token punctuation">.</span><span class="token constant">EGL_RENDERABLE_TYPE</span><span class="token punctuation">,</span> <span class="token number">4</span><span class="token punctuation">,</span> <span class="token constant">EGL10</span><span class="token punctuation">.</span><span class="token constant">EGL_NONE</span> <span class="token punctuation">)</span> <span class="token comment">//5、从系统中获取对应属性的配置</span> val num_config <span class="token operator">=</span> <span class="token class-name">IntArray</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span> <span class="token keyword">if</span><span class="token punctuation">(</span><span class="token operator">!</span>egl<span class="token punctuation">.</span><span class="token function">eglChooseConfig</span><span class="token punctuation">(</span>display<span class="token punctuation">,</span> attr<span class="token punctuation">,</span> <span class="token keyword">null</span><span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">,</span> num_config<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span> <span class="token keyword">throw</span> <span class="token class-name">RuntimeException</span><span class="token punctuation">(</span><span class="token string">"eglChooseConfig failed"</span><span class="token punctuation">)</span> <span class="token punctuation">}</span> val numConfigs <span class="token operator">=</span> num_config<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>numConfigs <span class="token operator"><=</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span> <span class="token keyword">throw</span> <span class="token class-name">RuntimeException</span><span class="token punctuation">(</span><span class="token string">"No configs match configSpec"</span><span class="token punctuation">)</span> <span class="token punctuation">}</span> val configs <span class="token operator">=</span> arrayOfNulls<span class="token generics"><span class="token punctuation"><</span><span class="token class-name">EGLConfig</span><span class="token punctuation">></span></span><span class="token punctuation">(</span>numConfigs<span class="token punctuation">)</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>egl<span class="token punctuation">.</span><span class="token function">eglChooseConfig</span><span class="token punctuation">(</span>display<span class="token punctuation">,</span> attr<span class="token punctuation">,</span> configs<span class="token punctuation">,</span> numConfigs<span class="token punctuation">,</span> num_config<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span> <span class="token keyword">throw</span> <span class="token class-name">RuntimeException</span><span class="token punctuation">(</span><span class="token string">"eglChooseConfig#2 failed"</span><span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token comment">//6、创建EglContext</span> val attrib_list <span class="token operator">=</span> <span class="token function">intArrayOf</span><span class="token punctuation">(</span> <span class="token constant">EGL14</span><span class="token punctuation">.</span><span class="token constant">EGL_CONTEXT_CLIENT_VERSION</span><span class="token punctuation">,</span> <span class="token number">3</span><span class="token punctuation">,</span> <span class="token constant">EGL10</span><span class="token punctuation">.</span><span class="token constant">EGL_NONE</span> <span class="token punctuation">)</span> val eglContext <span class="token operator">=</span> egl<span class="token punctuation">.</span><span class="token function">eglCreateContext</span><span class="token punctuation">(</span>display<span class="token punctuation">,</span> configs<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token constant">EGL10</span><span class="token punctuation">.</span><span class="token constant">EGL_NO_CONTEXT</span><span class="token punctuation">,</span> attrib_list<span class="token punctuation">)</span> <span class="token comment">//7、创建渲染的Surface</span> val eglSurface <span class="token operator">=</span> egl<span class="token punctuation">.</span><span class="token function">eglCreateWindowSurface</span><span class="token punctuation">(</span>display<span class="token punctuation">,</span> configs<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">,</span> surface<span class="token punctuation">,</span> <span class="token keyword">null</span><span class="token punctuation">)</span> <span class="token comment">//8、绑定EglContext和Surface到显示设备中</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>egl<span class="token punctuation">.</span><span class="token function">eglMakeCurrent</span><span class="token punctuation">(</span>display<span class="token punctuation">,</span> eglSurface<span class="token punctuation">,</span> eglSurface<span class="token punctuation">,</span> eglContext<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span> <span class="token keyword">throw</span> <span class="token class-name">RuntimeException</span><span class="token punctuation">(</span><span class="token string">"eglMakeCurrent fail"</span><span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token keyword">this</span><span class="token punctuation">.</span>egl <span class="token operator">=</span> egl <span class="token keyword">this</span><span class="token punctuation">.</span>eglDisplay <span class="token operator">=</span> display <span class="token keyword">this</span><span class="token punctuation">.</span>eglSurface <span class="token operator">=</span> eglSurface <span class="token keyword">this</span><span class="token punctuation">.</span>eglContext <span class="token operator">=</span> eglContext <span class="token punctuation">}</span> fun <span class="token function">swapBuffers</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span> egl<span class="token operator">?</span><span class="token punctuation">.</span><span class="token function">eglSwapBuffers</span><span class="token punctuation">(</span>eglDisplay<span class="token punctuation">,</span> eglSurface<span class="token punctuation">)</span> <span class="token punctuation">}</span> fun <span class="token function">destroy</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span> egl<span class="token operator">?</span><span class="token punctuation">.</span>apply <span class="token punctuation">{<!-- --></span> <span class="token function">eglMakeCurrent</span><span class="token punctuation">(</span> eglDisplay<span class="token punctuation">,</span> <span class="token constant">EGL10</span><span class="token punctuation">.</span><span class="token constant">EGL_NO_SURFACE</span><span class="token punctuation">,</span> <span class="token constant">EGL10</span><span class="token punctuation">.</span><span class="token constant">EGL_NO_SURFACE</span><span class="token punctuation">,</span> <span class="token constant">EGL10</span><span class="token punctuation">.</span><span class="token constant">EGL_NO_CONTEXT</span> <span class="token punctuation">)</span> <span class="token function">eglDestroySurface</span><span class="token punctuation">(</span>eglDisplay<span class="token punctuation">,</span> eglSurface<span class="token punctuation">)</span> eglSurface <span class="token operator">=</span> <span class="token keyword">null</span> <span class="token function">eglDestroyContext</span><span class="token punctuation">(</span>eglDisplay<span class="token punctuation">,</span> eglContext<span class="token punctuation">)</span> eglContext <span class="token operator">=</span> <span class="token keyword">null</span> <span class="token function">eglTerminate</span><span class="token punctuation">(</span>eglDisplay<span class="token punctuation">)</span> eglDisplay <span class="token operator">=</span> <span class="token keyword">null</span> egl <span class="token operator">=</span> <span class="token keyword">null</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
步骤比较简单,就是:
- 拿到Egl实例 :EGLContext.getEGL()
- 得到默认的显示设备(就是窗口):egl.eglGetDisplay
- 初始化默认显示设备:egl.eglInitialize
- 设置显示设备的属性和配置到 egl 中:egl.eglChooseConfig
- 创建EglContext:egl.eglCreateContext
- 创建渲染的Surface:egl.eglCreateWindowSurface
- 绑定EglContext和Surface到显示设备中:egl.eglMakeCurrent
- 交换缓冲区,渲染:egl.eglSwapBuffers
然后我们把 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
}
<span class="token keyword">if</span> <span class="token punctuation">(</span>isFirst<span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
isFirst <span class="token operator">=</span> <span class="token boolean">false</span>
<span class="token comment">//回调给主类</span>
<span class="token keyword">this</span><span class="token annotation punctuation">@L8_ShapeRender.onSurfaceCreated</span><span class="token punctuation">(</span><span class="token keyword">null</span><span class="token punctuation">,</span> <span class="token keyword">null</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span>isSizeChange<span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
isSizeChange <span class="token operator">=</span> <span class="token boolean">false</span>
<span class="token keyword">this</span><span class="token annotation punctuation">@L8_ShapeRender.onSurfaceChanged</span><span class="token punctuation">(</span><span class="token keyword">null</span><span class="token punctuation">,</span> width<span class="token punctuation">,</span> height<span class="token punctuation">)</span>
<span class="token punctuation">}</span>
eglHelper<span class="token operator">?</span><span class="token punctuation">.</span>let <span class="token punctuation">{<!-- --></span>
<span class="token keyword">this</span><span class="token annotation punctuation">@L8_ShapeRender.onDrawFrame</span><span class="token punctuation">(</span><span class="token keyword">null</span><span class="token punctuation">)</span>
it<span class="token punctuation">.</span><span class="token function">swapBuffers</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span>
<span class="token keyword">try</span> <span class="token punctuation">{<!-- --></span>
<span class="token function">sleep</span><span class="token punctuation">(</span><span class="token number">16</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span>e<span class="token operator">:</span> <span class="token class-name">Exception</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
fun <span class="token function">release</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
isExit <span class="token operator">=</span> <span class="token boolean">true</span>
eglHelper<span class="token operator">?</span><span class="token punctuation">.</span><span class="token function">destroy</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
surface<span class="token punctuation">.</span><span class="token function">release</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span>
fun <span class="token function">changeSize</span><span class="token punctuation">(</span>width<span class="token operator">:</span> <span class="token class-name">Int</span><span class="token punctuation">,</span> height<span class="token operator">:</span> <span class="token class-name">Int</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
<span class="token keyword">this</span><span class="token punctuation">.</span>width <span class="token operator">=</span> width
<span class="token keyword">this</span><span class="token punctuation">.</span>height <span class="token operator">=</span> height
isSizeChange <span class="token operator">=</span> <span class="token boolean">true</span>
<span class="token punctuation">}</span>
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
都比较好理解,可以看到,我们也通过 onSurfaceCreated,onSurfaceChanged 和 onDrawFrame 回调给外部,这样,着色器相关的,就不用改变啦。
看一下效果,可以正常运行:
参考:
https://cloud.tencent.com/developer/article/1035505
https://cloud.tencent.com/developer/article/1899820