GLSurfaceView原理深度剖析:从OpenGL ES到Android屏幕的渲染之旅

一、背景:为何需要GLSurfaceView?

在应用开发中,图形绘制通常通过各种高级API如Android Canvas的drawXXX方法完成。但这些接口通常经过了层层封装,底层复杂的图形库调用对开发者透明,我们不知道Canvas从哪来,也不知道绘制方法到底做了啥。

有没有办法可以窥探app底层调用图形库接口呢?------答案就是GLSurfaceView

GLSurfaceView虽然继承自SurfaceView,但两者却有本质区别:

  • SurfaceView: 通过Canvas高级接口绘制,经历HWUI收集指令→Skia图形库转换→OpenGL/Vulkan底层调用的复杂流程

  • GLSurfaceView: 提供直接的OpenGL原始绘制指令调用,让开发者能够直接操控底层图形渲染

本文将深入分析GLSurfaceView如何架起应用层与OpenGL ES之间的桥梁。

二、GLSurfaceView基础使用

在深入之前先看一下GLSurfaceView使用的简单例子,GLSurfaceView作为一个普通的View可以嵌入到Android的View树上,通过setRenderer接口设置一个Renderer对象,Renderer接口有3个方法:onSurfaceCrated、onSurfaceChanged、onDrawFrame,名字很清晰,分别表示:Surface初始化完成、Surface大小改变、绘制一帧,所以在应用端调用就是通过在onDrawFrame里直接调用GLE20提供的绘制方法进行绘制。

java 复制代码
// GLSurfaceView使用的简单例子
class GLSurfaceViewDemo : Activity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val glSurfaceView = GLSurfaceView(this)

        // 关键配置
        glSurfaceView.setEGLContextClientVersion(2)  // 使用OpenGL ES 2.0
        glSurfaceView.renderMode = GLSurfaceView.RENDERMODE_WHEN_DIRTY  // 按需渲染
        glSurfaceView.setRenderer(object : GLSurfaceView.Renderer {
            override fun onSurfaceCreated(gl: GL10?, config: EGLConfig?) {
                // 设置清屏颜色为黑色
                GLES20.glClearColor(0.0f, 0.0f, 0.0f, 1.0f)
            }

            override fun onSurfaceChanged(gl: GL10?, width: Int, height: Int) {
                // 设置视口大小
                GLES20.glViewport(0, 0, width, height)
            }

            override fun onDrawFrame(gl: GL10?) {
                // 执行清屏操作
                GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT)
                ...  
                // 其他GL绘制方法  
            }
        })
        setContentView(glSurfaceView)
    }
}

核心疑问: 为何GLES20的静态方法调用能够直接绘制到View上?答案隐藏在GLSurfaceView的内部实现中。

三、OpenGL ES渲染基础:从应用到屏幕的8个关键步骤

在深入GLSurfaceView源码前,先了解标准的OpenGL ES应用渲染流程。

同时我们还需要回顾一个名词:句柄,掌握这个名词的含义对后面内容的理解有很大的帮助。句柄一般表示一个系统资源返回给了用户端,但用户端只能获取其标识(一般为一个整形变量),但是每办法获取其类型,也不能访问内部属性,比如C里的FILE*、Java里的File Descriptor等都是句柄。OpenGL 这种深度和系统打交道的交互会多次使用句柄的概念。

从应用发起绘制到画面显示到屏幕,需要经历下面8个关键步骤:

c 复制代码
// 1. 获取显示设备句柄
EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY)

// 2. 初始化显示连接  
eglInitialize(display, nullptr, nullptr)

// 3. 选择配置(颜色、深度等参数)
eglChooseConfig(display, attributes, &config, 1, &num_config)

// 4. 创建渲染上下文(分配资源,维护状态机)
EGLContext context = eglCreateContext(display, config, nullptr, attrs)

// 5. 创建绘制表面(连接原生窗口系统)
EGLSurface surface = eglCreateWindowSurface(display, config, window, nullptr)

// 6. 绑定上下文到当前线程
eglMakeCurrent(display, surface, surface, context)

// 7. 执行OpenGL绘制命令
glClear(GL_COLOR_BUFFER_BIT)
glDrawArrays(...)
// 像素数据写入后缓冲区(Back Buffer)
 
// 8. 交换缓冲区,显示到屏幕
eglSwapBuffers(display, surface)

8个步骤构成了任何OpenGL ES应用的基础框架,GLSurfaceView正是在Java层实现了这一流程。

四、GLSurfaceView源码深度解析

4.1 初始化入口:setRenderer方法

java 复制代码
public void setRenderer(Renderer renderer) {
    checkRenderThreadState()  // 确保只设置一次

    // 初始化默认配置工厂
    if (mEGLConfigChooser == null) {
        mEGLConfigChooser = new SimpleEGLConfigChooser(true)
    }

    if (mEGLContextFactory == null) {
        mEGLContextFactory = new DefaultContextFactory()
    }

    if (mEGLWindowSurfaceFactory == null) {
        mEGLWindowSurfaceFactory = new DefaultWindowSurfaceFactory()
    }
   
    mRenderer = renderer
    mGLThread = new GLThread(mThisWeakRef)  // 创建专属渲染线程
    mGLThread.start()
}

这里创建了三个核心工厂对象,分别负责EGL配置选择、上下文创建和表面管理。

4.2 GLThread:渲染线程的核心引擎

GLThread的guardedRun()方法是整个渲染流程的调度中心:

java 复制代码
private void guardedRun() throws InterruptedException {
    mEglHelper = new EglHelper(mGLSurfaceViewWeakRef);
    mHaveEglContext = false;
    mHaveEglSurface = false;
    mWantRenderNotification = false;


    try {
        ...
        while (true) { // 渲染循环
            synchronized (sGLThreadManager) {
                while (true) { // 控制循环
                    ...
                    if (readyToDraw()) { // 准备绘制
                        ...
                        //创建EGLContext上下文
                        mEglHelper.start();
                        ...
                    }
                    ...                    
                }
                ...
                //创建EGLSurface,把View的Surface封装成EGLSurface给OpenGL绘制
                if (mEglHelper.createSurface()) {
                    ...
                }

                // 在这之后,当前线程便可以执行绘制操作了

                ...

                //获取GL对象,包装OpenGL API环境,这里使用GL10
                gl = (GL10) mEglHelper.createGL();
                ...

                //回调外部Renderer对象的`onSurfaceCreated()`方法
                GLSurfaceView view = mGLSurfaceViewWeakRef.get();
                view.mRenderer.onSurfaceCreated(gl, mEglHelper.mEglConfig);
                ...

                if (sizeChanged) {
                    ...
                    //回调外部Renderer对象的`onSurfaceChanged()`方法
                    view.mRenderer.onSurfaceChanged(gl, w, h);
                    ...
                }
               ...
               //回调外部Renderer对象的`onDrawFrame()`方法
               view.mRenderer.onDrawFrame(gl);
               ...

               //Egl交互内存,opengl使用的双内存缓冲,一个进行显示,另一个则后台进行绘制,绘制OK后,交互内存进行显示
               int swapError = mEglHelper.swap();
               ...
        }
    } finally {
        synchronized (sGLThreadManager) {
            stopEglSurfaceLocked();
            stopEglContextLocked();
        }
   }
}


// 是否可以绘制内容了
private boolean readyToDraw() {
    return (!mPaused)  // 没有暂停
            && mHasSurface  // Surface是否已经创建(注意Surface和EGLSurface是不一样的)
            && (!mSurfaceIsBad) // EGLSurface是否可用
            && (mWidth > 0)  // Surface的大小
            && (mHeight > 0)
            && (mRequestRender || (mRenderMode == RENDERMODE_CONTINUOUSLY)  // 手动刷新或者自动刷新
    );
}

guardedRun方法比较复杂,内部启动两个循环,外循环是渲染循环,每个循环都会绘制一帧, 内部循环状态检查各种状态。从readyToDraw()方法开始看,这个方法表示当前准备好 可以开始绘制了。readToDraw()检查当前是否可以进行绘制,如果可以进行绘制,则开始OpenGL上下文的初始化。

EglHelper.start()

java 复制代码
public void start() {
    ...  
    mEgl = (EGL10) EGLContext.getEGL()  // 获取EGL实例
    ...

    // 步骤1:获取显示设备
    mEglDisplay = mEgl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY)
     ...

    // 步骤2:初始化显示连接
    mEgl.eglInitialize(mEglDisplay, version)
     ...

    // 步骤3:选择配置
    mEglConfig = view.mEGLConfigChooser.chooseConfig(mEgl, mEglDisplay)
     ...

    // 步骤4:创建渲染上下文
    mEglContext = view.mEGLContextFactory.createContext(mEgl, mEglDisplay, mEglConfig)
}

这里的步骤就和前面介绍的app使用OpenGL的前步一样,先获取显示设备的句柄,然后初始化、接着进行配置,最后创建GL环境

到这里GL上下文就创建好了, 下一步就是创建EGLSurface, 这里先重点介绍下EGLSurface和Android系统里的Surface的区别,首先它们都是绘制表面的意思,也就是绘制目标,内部都包含了BufferQueue用来保存绘制的结果。Surface是Android自身的概念,EGLSurface是OpenGL的概念,是夸平台的和平台无关,所以在Android系统上想要通过OpenGL绘制内容的话,要将原生窗口(如 Android 的 ANativeWindow)包装成 EGL Surface,作为 OpenGL 的渲染目标,连接 OpenGL 和平台窗口系统。通俗点的理解就是OpenGL内部会创建一个EGLSurface,但是FrameBuffer SurfaceView已经有了,所以需要将SurfaceView里Surface的FrameBuffer设置给EGLSurface, 用来作为OpenGL的绘制目标。

Surface创建与绑定:EglHelper.createSurface()

java 复制代码
public boolean createSurface() {
    // 步骤5:创建EGLSurface(包装Android的Surface)
    mEglSurface = view.mEGLWindowSurfaceFactory.createWindowSurface(
        mEgl, mEglDisplay, mEglConfig, view.getHolder())

    
    // 步骤6:绑定到当前线程
    return mEgl.eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)
}

接下来mEgl.eglMakeCurrent将EGLSurface和EGLContext绑定到当前线程,用来在当前线程做绘制, 这步在内部会把Context和Surface设置到ThreadLocalStorage, 这样在后面的OpenGL绘制命令就不需要每次都传入context参数了。

这一步执行完后,前面介绍app使用OpenGL渲染8步的前步就做完了,剩下的就是绘制和缓存交换。

回调Renderer业务绘制内容

接下来就是调用了外部设置的Renderer三个接口:

java 复制代码
//回调外部Renderer对象的`onSurfaceCreated()`方法
GLSurfaceView view = mGLSurfaceViewWeakRef.get();
view.mRenderer.onSurfaceCreated(gl, mEglHelper.mEglConfig);
...
if (sizeChanged) {
    ...
    //回调外部Renderer对象的`onSurfaceChanged()`方法
    view.mRenderer.onSurfaceChanged(gl, w, h);
    ...
}
...

//回调外部Renderer对象的`onDrawFrame()`方法
view.mRenderer.onDrawFrame(gl);

这时用户一般就在onDrawFrame()里调用OpenGL的绘制,方法执行完之后绘制数据到了Surface里BufferQueue里后端,接着调用mEglHelper.swap()把数据显示给前端。

缓冲区交换:EglHelper.swap()

java 复制代码
public int swap() {
    // 步骤8:交换前后缓冲区
    if (!mEgl.eglSwapBuffers(mEglDisplay, mEglSurface)) {
        return mEgl.eglGetError()
    }
    return EGL10.EGL_SUCCESS
}

mEglHelper.swap()的代码 SurfaceView使用双内存缓冲机制,内部有两个Frambuffer,一个进行前台显示,另一个则后台进行绘制,绘制OK后,交互内存进行显示。eglSwapBuffers的目的是渲染好的FrameBuffer和前台的Framebuffer交互后显示出来

4.3 生命周期管理:暂停与恢复机制

  • 暂停实现
java 复制代码
mRequestPaused = true
sGLThreadManager.notifyAll()  // 唤醒等待的渲染线程

在渲染循环中检测到状态变化:

java 复制代码
if (mPaused != mRequestPaused) {
    pausing = mRequestPaused
    mPaused = mRequestPaused

    if (pausing) {
        // 释放EGLSurface和EGLContext(可选)
        stopEglSurfaceLocked()
        if (!preserveEglContextOnPause) {
            stopEglContextLocked()
        }
    }
}

暂停后,readyToDraw()返回false,渲染线程在内部循环中等待。

*恢复实现

onResume()将状态重置:

java 复制代码
mRequestPaused = false
mRequestRender = true
sGLThreadManager.notifyAll()

渲染线程检测到状态变化后重新初始化EGL环境并继续渲染循环。

4.4 线程退出机制

GLThread通过mShouldExit标志控制退出:

java 复制代码
public void requestExitAndWait() {
    synchronized(sGLThreadManager) {
        mShouldExit = true
        sGLThreadManager.notifyAll()
        while (!mExited) {
            sGLThreadManager.wait()  // 等待线程完全退出
        }
    }
}

退出触发点在onDetachedFromWindow()中调用,确保在View脱离窗口时安全停止渲染线程。

五、核心原理总结

  1. 线程分离:GLSurfaceView创建专用渲染线程,避免UI线程阻塞

  2. EGL桥梁作用:通过EGL连接OpenGL ES API与Android原生窗口系统

  3. 双缓冲机制:前缓冲区显示,后缓冲区渲染,通过交换实现流畅显示

  4. 状态机管理:完善的暂停/恢复/退出生命周期管理

  5. Surface封装:将Android Surface包装为EGLSurface,实现平台无关的OpenGL渲染

GLSurfaceView的本质是在Java层完整实现了OpenGL ES的标准渲染流程,为Android应用提供了直接调用底层图形库的能力。通过理解其内部机制,开发者能够更好地优化渲染性能,处理复杂的图形需求。

相关推荐
用户41659673693551 小时前
告别 XML ANR?Compose 的性能陷阱与重组优化实战指南
android
私人珍藏库1 小时前
[Android] 轻小说文库(1.23)
android·app·安卓·工具
FrameNotWork2 小时前
去掉XOSLauncher自带的widget组件图标
android
飞梦工作室2 小时前
PHP 中 php://input 的全面使用指南
android·开发语言·php
熬夜敲代码的小N2 小时前
Unity WebRequest高级操作:构建高效稳定的网络通信模块
android·数据结构·unity·游戏引擎
Android技术之家3 小时前
安卓对外发布工程源码:如何实现仅暴露 UI 层
android·ui
Digitally3 小时前
如何快速将iPhone上的图片发送到安卓手机(6种方法)
android·智能手机·iphone