Open GL ES -> SurfaceView + 自定义EGL实现OpenGL渲染框架

SurfaceView + 自定义EGL实现OpenGL渲染

Android开发中,当需要灵活控制OpenGL渲染或在多个Surface间共享EGL上下文时,自定义EGL环境是必要的选择

核心实现流程

kotlin 复制代码
+--------------------+     +--------------------+     +--------------------+
| 1. 创建SurfaceView | --> | 2. 初始化EGL环境   | --> | 3. 渲染线程管理    |
+--------------------+     +--------------------+     +--------------------+

1. 创建SurfaceView

继承SurfaceView并实现SurfaceHolder.Callback接口,管理Surface生命周期:

kotlin 复制代码
class MySurfaceView(context: Context, attrs: AttributeSet) : SurfaceView(context, attrs), SurfaceHolder.Callback {

    init {
        holder.addCallback(this)
    }

    private var mEGLHelper = MyEGLHelper()
    private var mEGLRender = MyEGLRender(context)
    private var mEGLThread: MyEGLThread? = null

    override fun surfaceCreated(holder: SurfaceHolder) {
        // 创建并启动渲染线程
        mEGLThread = MyEGLThread(holder.surface).apply {
            start()
        }
    }

    override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {
        mEGLThread?.changeSize(width, height)
    }

    override fun surfaceDestroyed(holder: SurfaceHolder) {
        mEGLThread?.release()
    }
	
	// 渲染线程实现
	inner class MyEGLThread(private val mSurface: Surface) : Thread() {
        private var mWidth = 0
        private var mHeight = 0

        @Volatile
        private var mRunning = true

        @Volatile
        private var mSizeChanged = false
        override fun run() {
            super.run()
            try {
                mEGLHelper.initEGL(mSurface)
                mEGLRender.onSurfaceCreated()
                while (mRunning) {
                    // 宽高变化,回调渲染器的onSurfaceChanged方法
                    if (mSizeChanged) {
                        mEGLRender.onSurfaceChanged(mWidth, mHeight)
                        mSizeChanged = false
                    }
                    // 渲染一帧, 回调渲染器的onDrawFrame方法
                    mEGLRender.onDrawFrame()
                    mEGLHelper.swapBuffer()
                }
            } catch (e: Exception) {
                Log.e("yang", "EGL thread error ${e.message}")
            }
        }

        fun changeSize(width: Int, height: Int) {
            mWidth = width
            mHeight = height
            mSizeChanged = true
        }

        fun release() {
            mRunning = false
            mEGLRender.onSurfaceDestroyed()
            mEGLHelper.releaseEGL()
            interrupt()
        }
    }

2. 初始化EGL环境

负责EGL环境的初始化、配置和销毁:

kotlin 复制代码
class MyEGLHelper {
    private lateinit var mEGL: EGL10
    private lateinit var mEGLDisplay: EGLDisplay
    private lateinit var mEGLContext: EGLContext
    private lateinit var mEGLSurface: EGLSurface

    // 初始化EGL
    fun initEGL(surface: Surface) {
        if (::mEGL.isInitialized &&
            ::mEGLDisplay.isInitialized &&
            ::mEGLContext.isInitialized &&
            ::mEGLSurface.isInitialized) {
            Log.e("yang", "EGL already initialized")
            return
        }
        // 1. 获取EGL实例
        mEGL = EGLContext.getEGL() as EGL10

        // 2. 获取默认的显示设备(就是窗口)
        mEGLDisplay = mEGL.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY)
        takeIf { mEGLDisplay == EGL10.EGL_NO_DISPLAY }?.apply {
            throw RuntimeException("eglGetDisplay failed")
        }

        // 3. 初始化默认显示设备
        val version = IntArray(2)
        takeIf { !mEGL.eglInitialize(mEGLDisplay, version) }?.apply {
            throw RuntimeException("eglInitialize failed")
        }

        // 4. 设置显示设备的属性
        val display_attribute_list = intArrayOf(
            EGL_RED_SIZE, 8,
            EGL_GREEN_SIZE, 8,
            EGL_BLUE_SIZE, 8,
            EGL_ALPHA_SIZE, 8,
            EGL_DEPTH_SIZE, 8,
            EGL_STENCIL_SIZE, 4,
            EGL_NONE
        )

        // 5. 查找配置并进行 attribute_list 的匹配, 匹配成功返回一个数组
        val num_config = IntArray(1)
        takeIf {
            !mEGL.eglChooseConfig(
                mEGLDisplay,
                display_attribute_list,
                null,
                0,
                num_config
            )
        }?.apply {
            throw RuntimeException("eglChooseConfig failed")
        }
        // 匹配是否成功
        takeIf { num_config[0] <= 0 }?.apply {
            throw RuntimeException("eglChooseConfig#1 failed")
        }
        val eglConfigs = arrayOfNulls<EGLConfig>(num_config[0])

        takeIf {
            !mEGL.eglChooseConfig(
                mEGLDisplay,
                display_attribute_list,
                eglConfigs,
                num_config[0],
                num_config
            )
        }?.apply {
            throw RuntimeException("eglChooseConfig#2 failed")
        }

        // 6. 创建EGLContext
        val context_display_list = intArrayOf(
            EGL_CONTEXT_CLIENT_VERSION, 3,
            EGL_NONE
        )
        takeIf { ::mEGLContext.isInitialized == false }?.apply {
            mEGLContext = mEGL.eglCreateContext(
                mEGLDisplay,
                eglConfigs[0],
                EGL10.EGL_NO_CONTEXT,
                context_display_list
            )
        }
        takeIf { mEGLContext == EGL10.EGL_NO_CONTEXT }?.apply {
            throw RuntimeException("eglCreateContext failed")
        }

        // 7. 创建EGLSurface
        takeIf { ::mEGLSurface.isInitialized == false }?.apply {
            mEGLSurface = mEGL.eglCreateWindowSurface(mEGLDisplay, eglConfigs[0], surface, null)
        }
        takeIf { mEGLSurface == EGL10.EGL_NO_SURFACE }?.apply {
            throw RuntimeException("eglCreateWindowSurface failed")
        }

        // 8. 绑定EGLContext和EGLSurface
        takeIf { !mEGL.eglMakeCurrent(mEGLDisplay, mEGLSurface, mEGLSurface, mEGLContext) }?.apply {
            throw RuntimeException("eglMakeCurrent failed")
        }
    }

    // 释放EGL
    fun releaseEGL() {
        takeIf { ::mEGL.isInitialized }?.apply {
            // 解绑EGLContext和EGLSurface
            mEGL.eglMakeCurrent(
                mEGLDisplay,
                EGL10.EGL_NO_SURFACE,
                EGL10.EGL_NO_SURFACE,
                EGL10.EGL_NO_CONTEXT
            )
            // 释放EGLSurface
            mEGL.eglDestroySurface(mEGLDisplay, mEGLSurface)
            // 释放EGLContext
            mEGL.eglDestroyContext(mEGLDisplay, mEGLContext)
            // 释放EGLDisplay
            mEGL.eglTerminate(mEGLDisplay)
        }
    }

    // 交换渲染数据
    fun swapBuffer() {
        takeIf { ::mEGL.isInitialized && ::mEGLDisplay.isInitialized }?.apply {
            takeIf { !mEGL.eglSwapBuffers(mEGLDisplay, mEGLSurface) }?.apply {
                throw RuntimeException("eglSwapBuffers failed")
            }
        }
    }
}

3. 渲染线程管理

在独立线程中处理渲染循环:

kotlin 复制代码
inner class MyEGLThread(private val mSurface: Surface) : Thread() {
        private var mWidth = 0
        private var mHeight = 0

        @Volatile
        private var mRunning = true

        @Volatile
        private var mSizeChanged = false
        override fun run() {
            super.run()
            try {
                mEGLHelper.initEGL(mSurface)
                mEGLRender.onSurfaceCreated()
                while (mRunning) {
                    // 宽高变化,回调渲染器的onSurfaceChanged方法
                    if (mSizeChanged) {
                        mEGLRender.onSurfaceChanged(mWidth, mHeight)
                        mSizeChanged = false
                    }
                    // 渲染一帧, 回调渲染器的onDrawFrame方法
                    mEGLRender.onDrawFrame()
                    mEGLHelper.swapBuffer()
                }
            } catch (e: Exception) {
                Log.e("yang", "EGL thread error ${e.message}")
            }
        }

        fun changeSize(width: Int, height: Int) {
            mWidth = width
            mHeight = height
            mSizeChanged = true
        }

        fun release() {
            mRunning = false
            mEGLRender.onSurfaceDestroyed()
            mEGLHelper.releaseEGL()
            interrupt()
        }
    }

4. 渲染器接口

定义渲染器接口,类似于GLSurfaceView.Renderer

kotlin 复制代码
interface EGLRender {
    fun onSurfaceCreated()  // Surface创建时调用
    fun onSurfaceChanged(width: Int, height: Int)  // Surface尺寸变化时调用
    fun onDrawFrame()  // 每帧渲染时调用
    fun onSurfaceDestroyed()  // Surface销毁时调用
}

5. 渲染器实现

实现渲染器接口,处理具体的OpenGL渲染逻辑:

kotlin 复制代码
class MyEGLRender(private val mContext: Context) : EGLRender {

    override fun onSurfaceCreated() {
        GLES30.glClearColor(0.0f, 0.5f, 0.5f, 1.0f)
        // 加载纹理...
        // 初始化顶点缓冲区...
        // 初始化着色器程序...
    }

    override fun onSurfaceChanged(width: Int, height: Int) {
        GLES30.glViewport(0, 0, width, height)
        // 改变渲染数据的宽高...
    }

    override fun onDrawFrame() {
        GLES30.glEnable(GLES30.GL_DEPTH_TEST)
        GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT or GLES30.GL_DEPTH_BUFFER_BIT)
        // 具体绘制方法...
    }

    override fun onSurfaceDestroyed() {
        // 销毁渲染数据...
    }
}

完整流程图

整体渲染流程图

kotlin 复制代码
+----------------------+     +----------------------+
| SurfaceView创建      | --> | 注册SurfaceHolder    |
| MySurfaceView构造函数|     | Callback回调        |
+----------------------+     +----------------------+
           |
           v
+----------------------+     +----------------------+     +----------------------+
| Surface创建          | --> | 创建渲染线程        | --> | 初始化EGL环境        |
| surfaceCreated回调   |     | MyEGLThread.start() |     | mEGLHelper.initEGL() |
+----------------------+     +----------------------+     +----------------------+
           |                                                        |
           |                                                        v
+----------------------+     +----------------------+     +----------------------+
| Surface尺寸变化      | --> | 通知渲染线程        |     | 初始化渲染器         |
| surfaceChanged回调   |     | changeSize()        |     | onSurfaceCreated()   |
+----------------------+     +----------------------+     +----------------------+
           |                                                        |
           |                                                        v
+----------------------+                                 +----------------------+
| Surface销毁          |                                 | 渲染循环开始         |
| surfaceDestroyed回调 |                                 | 循环检查尺寸变化     |
+----------------------+                                 +----------------------+
           |                                                        |
           v                                                        v
+----------------------+     +----------------------+     +----------------------+
| 停止渲染线程        | <-- | 释放OpenGL资源       | <-- | 执行渲染操作        |
| mEGLThread.release() |     | onSurfaceDestroyed() |     | onDrawFrame()       |
+----------------------+     +----------------------+     +----------------------+
           |                                                        |
           v                                                        v
+----------------------+     +----------------------+     +----------------------+
| 释放EGL资源         |     | 线程退出            |     | 交换缓冲区          |
| mEGLHelper.releaseEGL()|     | 渲染循环结束        |     | swapBuffer()        |
+----------------------+     +----------------------+     +----------------------+

EGL初始化流程图

kotlin 复制代码
+------------------+     +------------------+     +------------------+
| 获取EGL实例      | --> | 获取显示设备     | --> | 初始化显示设备   |
| mEGL = EGLContext|     | eglGetDisplay    |     | eglInitialize    |
| .getEGL()        |     |                  |     |                  |
+------------------+     +------------------+     +------------------+
         |                                                 |
         v                                                 v
+------------------+     +------------------+     +------------------+
| 设置EGL属性      | --> | 选择EGL配置      | --> | 创建EGL上下文    |
| RGB/Alpha/Depth  |     | eglChooseConfig  |     | eglCreateContext |
+------------------+     +------------------+     +------------------+
         |                                                 |
         v                                                 v
+------------------+     +------------------+
| 创建渲染Surface  | --> | 绑定EGL组件      |
| eglCreateWindow  |     | eglMakeCurrent   |
| Surface          |     |                  |
+------------------+     +------------------+
渲染线程管理流程图
+------------------+     +------------------+     +------------------+
| 线程启动         | --> | 初始化EGL环境    | --> | 初始化渲染器     |
| Thread.start()   |     | initEGL(surface) |     | onSurfaceCreated |
+------------------+     +------------------+     +------------------+
         |                                                 |
         v                                                 v
+------------------+     +------------------+     +------------------+
| 渲染循环         | --> | 检查尺寸变化     | --> | 执行渲染         |
| while(mRunning)  |     | if(mSizeChanged) |     | onDrawFrame()    |
+------------------+     +------------------+     +------------------+
         |                          |                      |
         |                          v                      v
         |              +------------------+     +------------------+
         |              | 更新视口         |     | 交换缓冲区       |
         |              | onSurfaceChanged |     | swapBuffer()     |
         |              +------------------+     +------------------+
         |                                                 |
         v                                                 v
+------------------+     +------------------+     +------------------+
| 接收释放信号     | --> | 通知渲染器销毁   | --> | 释放EGL资源      |
| release()调用    |     | onSurfaceDestroy |     | releaseEGL()     |
+------------------+     +------------------+     +------------------+
         |
         v
+------------------+
| 线程结束         |
| interrupt()      |
+------------------+
相关推荐
ejinxian13 分钟前
PHP 超文本预处理器 发布 8.5 版本
开发语言·php
福柯柯19 分钟前
Android ContentProvider的使用
android·contenprovider
不想迷路的小男孩20 分钟前
Android Studio 中Palette跟Component Tree面板消失怎么恢复正常
android·ide·android studio
餐桌上的王子21 分钟前
Android 构建可管理生命周期的应用(一)
android
菠萝加点糖25 分钟前
Android Camera2 + OpenGL离屏渲染示例
android·opengl·camera
用户20187928316736 分钟前
🌟 童话:四大Context徽章诞生记
android
软件黑马王子40 分钟前
C#系统学习第八章——字符串
开发语言·学习·c#
阿蒙Amon41 分钟前
C#读写文件:多种方式详解
开发语言·数据库·c#
yzpyzp44 分钟前
Android studio在点击运行按钮时执行过程中输出的compileDebugKotlin 这个任务是由gradle执行的吗
android·gradle·android studio
Da_秀1 小时前
软件工程中耦合度
开发语言·后端·架构·软件工程