美文网首页OpenGL多媒体科技Android技术知识
源码解析:Android源码GLSurfaceView源码解析

源码解析:Android源码GLSurfaceView源码解析

作者: 码农叔叔 | 来源:发表于2018-10-15 13:59 被阅读13次

    前言

    这篇文章就带着大家简单过一下Android的GLSurfaceView源码的一些主要的处理流程。

    GLSurfaceView怎么用

    在开始分析源码前,非常有必要先看看GLSurfaceView的基本使用方法:

    mGLView= (GLSurfaceView) findViewById(R.id.gl_view);
    mGLView.setEGLContextClientVersion(2);
    //在setRenderer之前,可以调用以下方法来进行EGL设置
    //mGLView.setEGLConfigChooser();    //颜色、深度、模板等等设置
    //mGLView.setEGLWindowSurfaceFactory(); //窗口设置
    //mGLView.setEGLContextFactory();   //EGLContext设置
    //设置渲染器,渲染主要就是由渲染器来决定
    mGLView.setRenderer(new GLSurfaceView.Renderer(){
        @Override
        public void onSurfaceCreated(GL10 gl, EGLConfig config) {
            //todo surface被创建后需要做的处理
        }
        @Override
        public void onSurfaceChanged(GL10 gl, int width, int height) {
            //todo 渲染窗口大小发生改变的处理
        }
        @Override
        public void onDrawFrame(GL10 gl) {
            //todo 执行渲染工作
        }
    });
    /*渲染方式,RENDERMODE_WHEN_DIRTY表示被动渲染,RENDERMODE_CONTINUOUSLY表示持续渲染*/
    mGLView.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
    

    源码分析

    入口方法

    我们先从setRenderer(Renderer renderer)这个入口开始。
    它主要做了两件事:
    1、检查环境和变量同步配置

    //检测环境
    checkRenderThreadState();
    //同步配置项,如果没有设置取默认项(懒加载模式)
    if (mEGLConfigChooser == null) {
        mEGLConfigChooser = new SimpleEGLConfigChooser(true);
    }
    if (mEGLContextFactory == null) {
        mEGLContextFactory = new DefaultContextFactory();
    }
    if (mEGLWindowSurfaceFactory == null) {
        mEGLWindowSurfaceFactory = new DefaultWindowSurfaceFactory();
    }
    mRenderer = renderer;
    

    函数checkRenderThreadState()检查了mRenderer是否已经存在,存在则抛出异常;换句话说,我们不能在同一个GLSurfaceView调用多次setRenderer(Renderer renderer),会挂!
    mEGLConfigChoosermEGLContextFactorymEGLWindowSurfaceFactory是用户在setRenderer之前,可以调用相关方法来进行EGL设置,如果没有设置则采用默认实现。
    mEGLConfigChooser主要是指定了OpenGL ES一些颜色深度、缓存区深度的一些设定。
    mEGLContextFactory主要是提供了创建和销毁EGL Context上下文的一些处理。
    mEGLWindowSurfaceFactory主要是提供了创建和销毁EGLSurface的一些处理。
    2、启动一个GL线程

    mGLThread = new GLThread(mThisWeakRef);
    mGLThread.start();
    

    GLThread就是我们一直所说的GL线程,主要是用于与OpenGL ES环境进行交互的线程。
    入参mThisWeakRef是一个弱引用,指向了GLSurfaceView本身。

    GL线程

    接下来我们需要分析一下GLThread这个GL线程,跟进到GLThreadrun()方法。
    我们发现run()里面有一个方法guardedRun(),这个也就是GL线程的主要逻辑函数,由两个while(true)循环组成。

    public void run() {
        try {
            //最最最重要的逻辑
            guardedRun();
        } 
        catch (InterruptedException e) {} 
        finally {}
    }
    

    转进去查看guardedRun()的代码。

    我们先来查看初始化创建相关的代码。
    在代码开头创建了一个EglHelperEglHelper是一个封装了一些EGL通用操作的工具类。

    mEglHelper = new EglHelper(mGLSurfaceViewWeakRef);
    

    首次循环,逻辑会走到这个位置:

    if (! mHaveEglContext) {
        if (askedToReleaseEglContext) {
            askedToReleaseEglContext = false;
        } else {
            try {
                //进行OpenGL ES环境初始化
                mEglHelper.start();
            } catch (RuntimeException t) {}
            mHaveEglContext = true;
            createEglContext = true;
        }
    }
    

    流程很明显,将会调用EglHelper.start()进行OpenGL ES环境的初始化。
    然后将标示量createEglContext设置为true。

    查看一下EglHelper.start()的实现:

    public void start() {
        //获取一个EGL实例
        mEgl = (EGL10) EGLContext.getEGL();
        //获取显示设备
        mEglDisplay = mEgl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
        //检测EglDisplay是否正常
        if (mEglDisplay == EGL10.EGL_NO_DISPLAY) {
            throw new RuntimeException("eglGetDisplay failed");
        }
        //初始化EGL的内部数据结构,返回EGL实现的主版本号和次版本号。
        int[] version = new int[2];
        if(!mEgl.eglInitialize(mEglDisplay, version)) {
            throw new RuntimeException("eglInitialize failed");
        }
        //获取GLSurfaceView的引用
        GLSurfaceView view = mGLSurfaceViewWeakRef.get();
        if (view == null) {
            mEglConfig = null;
            mEglContext = null;
        } else {
            //看到这里,大家还记得前面setEGLConfigChooser()、setEGLContextFactory()这两个方法么
            //就是让我们自己配置EGL参数或者自己创建EglContext的方法
            mEglConfig = view.mEGLConfigChooser.chooseConfig(mEgl, mEglDisplay);
            mEglContext = view.mEGLContextFactory.createContext(mEgl, mEglDisplay, mEglConfig);
        }
        mEglSurface = null;
    }
    

    如果我们没有调用setEGLContextFactory()这个方法(除非特殊需求,一般来说也没有用到),那么GLSurfaceView会默认取DefaultContextFactory()来代替。

    DefaultContextFactory中有一个创建EglContext的方法:

    //最简单的创建EGLContext的方式,如果需要用到多线程共享一个OpenGL ES环境的话,需要自己实现这个方法处理。
    public EGLContext createContext(EGL10 egl, EGLDisplay display, EGLConfig config) {
        int[] attrib_list = {EGL_CONTEXT_CLIENT_VERSION, mEGLContextClientVersion,
                EGL10.EGL_NONE };
        return egl.eglCreateContext(display, config, EGL10.EGL_NO_CONTEXT,
                mEGLContextClientVersion != 0 ? attrib_list : null);
    }
    

    OpenGL ES环境的初始化就完成了,但是我们知道应该还有一个EGL的Surface需要创建。

    后续的代码就是处理 EGL的Surface创建 的业务:

    //我们要记得这里将createEglSurface和createGlInterface都设置为true了
    if (mHaveEglContext && !mHaveEglSurface) {
        mHaveEglSurface = true;
        createEglSurface = true;
        createGlInterface = true;
        sizeChanged = true;
    }
    //由上面代码可知 mHaveEglSurface == true
    if (mHaveEglSurface) {
        //mSizeChanged是在SurfaceView回调surfaceChanged会设置会true
        //首次初始化mSizeChanged默认为true
        if (mSizeChanged) {
            sizeChanged = true;
            //更新宽高
            w = mWidth;
            h = mHeight;
            mWantRenderNotification = true;
            createEglSurface = true;
            mSizeChanged = false;
        }
    }
    

    我们需要关注这个变量createEglSurface,在下面有一段代码:

    if (createEglSurface) {
        //这里主要逻辑就是 mEglHelper.createSurface()
        if (mEglHelper.createSurface()) {
            synchronized(sGLThreadManager) {
                mFinishedCreatingEglSurface = true;
            }
        } else {
            synchronized(sGLThreadManager) {
                mFinishedCreatingEglSurface = true;
                mSurfaceIsBad = true;
            }
            continue;
        }
        createEglSurface = false;
    }
    

    代码又借助了EglHelper类,调用了其的createSurface()方法:

    public boolean createSurface() {
        //检测环境,由前面可知,这个都是有值的
        if (mEgl == null) {
            throw new RuntimeException("egl not initialized");
        }
        if (mEglDisplay == null) {
            throw new RuntimeException("eglDisplay not initialized");
        }
        if (mEglConfig == null) {
            throw new RuntimeException("mEglConfig not initialized");
        }
        //如果已经创建过EglSurface,这里先销毁掉呗
        destroySurfaceImp();
        GLSurfaceView view = mGLSurfaceViewWeakRef.get();
        if (view != null) {
            //调用mEGLWindowSurfaceFactory工程创建一个EglSurface
            //如果我们没有指定,会默认触发DefaultWindowSurfaceFactory的逻辑
            mEglSurface = view.mEGLWindowSurfaceFactory.createWindowSurface(mEgl,
                    mEglDisplay, mEglConfig, view.getHolder());
        } 
        //检测创建是否成功
        if (mEglSurface == null || mEglSurface == EGL10.EGL_NO_SURFACE) {
            return false;
        }
        //将EglContext上下文加载到当前线程环境
        if (!mEgl.eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)) {
            return false;
        }
        return true;
    }
    

    可以看看DefaultWindowSurfaceFactorycreateWindowSurface()是怎么创建EglSurface的:

    public EGLSurface createWindowSurface(EGL10 egl, EGLDisplay display,
                    EGLConfig config, Object nativeWindow) {
        EGLSurface result = null;
        try {
            result = egl.eglCreateWindowSurface(display, config, nativeWindow, null);
        } catch (IllegalArgumentException e) {
        }
        return result;
    }
    

    也就是说我们可以通过自己实现createWindowSurface()方法实现我们需要的EGLSurface创建方式。

    还有一个destroySurfaceImp(),也顺便瞧一瞧呗:

    private void destroySurfaceImp() {
        if (mEglSurface != null && mEglSurface != EGL10.EGL_NO_SURFACE) {
            //清理当前线程的EglContext上下文环境
            mEgl.eglMakeCurrent(mEglDisplay, EGL10.EGL_NO_SURFACE,
                    EGL10.EGL_NO_SURFACE,
                    EGL10.EGL_NO_CONTEXT);
            GLSurfaceView view = mGLSurfaceViewWeakRef.get();
            if (view != null) {
                //我们需要销毁前面创建出来的EGLSurface
                view.mEGLWindowSurfaceFactory.destroySurface(mEgl, mEglDisplay, mEglSurface);
            }
            mEglSurface = null;
        }
    }
    

    DefaultWindowSurfaceFactory的逻辑看完了。

    回归上面创建流程,紧接着可以看到createGlInterface这个标示量的代码逻辑块:

    if (createGlInterface) {
        gl = (GL10) mEglHelper.createGL();
        createGlInterface = false;
    }
    

    继续跟进mEglHelper.createGL()实现:

    GL createGL() {
        //获得OpenGL ES的编程接口
        GL gl = mEglContext.getGL();
        GLSurfaceView view = mGLSurfaceViewWeakRef.get();
        if (view != null) {
            //如果我们没调用setGLWrapper(GLWrapper glWrapper)这个接口,view.mGLWrapper == null
            if (view.mGLWrapper != null) {
                gl = view.mGLWrapper.wrap(gl);
            }
        return gl;
    }
    

    然后可以关注下createEglContext这个标示量的代码块:

    if (createEglContext) {
        GLSurfaceView view = mGLSurfaceViewWeakRef.get();
        if (view != null) {
            try {
                view.mRenderer.onSurfaceCreated(gl, mEglHelper.mEglConfig);
            } finally {
            }
        }
        createEglContext = false;
    }
    

    这个回调到上层自定义的Renderer的onSurfaceCreated(),说明GLSurfaceView的GL环境已经准备完毕了。

    顺便看看size变化的通知是怎么实现的:

    if (sizeChanged) { //在前面我们可以指定当surfaceview回调宽高变更时,sizeChanged会为true
        GLSurfaceView view = mGLSurfaceViewWeakRef.get();
        if (view != null) {
            try {
                view.mRenderer.onSurfaceChanged(gl, w, h);
            } finally {}
        }
        sizeChanged = false;
    }
    

    这个回调到上层自定义的Renderer的onSurfaceChanged(),标识GLSurfaceView的宽高已经发生改变。

    上面整个流程已经将GLSurfaceView的EGL环境准备完毕了,接下来我们看看是怎么触发渲染的。

    渲染业务

    还是在guardedRun()这个函数中,有这么一段代码:

    {
        GLSurfaceView view = mGLSurfaceViewWeakRef.get();
        if (view != null) {
            try {
                view.mRenderer.onDrawFrame(gl);
            } finally {}
        }
    }
    int swapError = mEglHelper.swap();
    

    如果逻辑能走到这里,就能触发到上层Renderer的onDrawFrame()实现,也就是说就能开始OpenGL ES的绘制工作。

    我们还记得开头说过,GLSurfaceView有两种刷新模式:RENDERMODE_WHEN_DIRTYRENDERMODE_CONTINUOUSLY
    那么下面我们就来分析一下这两种是怎么进行工作的。

    首先我们先看看RENDERMODE_CONTINUOUSLY循环刷新模式。
    先看看设置入口,嗯没啥重要逻辑。

    public void setRenderMode(int renderMode) {
        synchronized(sGLThreadManager) {
            mRenderMode = renderMode;
        }
    }
    

    搜索一下代码,我们发现mRenderMode在这个readyToDraw()函数使用到:

    private boolean readyToDraw() {
        //需要非暂停状态、EGLSurface已经创建、并宽高不为0
        return (!mPaused) && mHasSurface && (!mSurfaceIsBad)
            && (mWidth > 0) && (mHeight > 0)
            //这个就需要仔细看,如果我们设置RENDERMODE_CONTINUOUSLY的话 这条件是成立的!
            && (mRequestRender || (mRenderMode == RENDERMODE_CONTINUOUSLY));
    }
    

    接着我们看看哪里用到这个readyToDraw()函数,在guardedRun()中:

    while (true) {
        if (readyToDraw()) {
            if (! mHaveEglContext) {
                //......
            }
    
            if (mHaveEglContext && !mHaveEglSurface) {
                //......
            }
    
            if (mHaveEglSurface) {
                //......
                //触发一次后重置为false。
                mRequestRender = false;
                //......
                break;
            }
        }
        //......
        sGLThreadManager.wait();
    }
    //......
    GLSurfaceView view = mGLSurfaceViewWeakRef.get();
    if (view != null) {
        try {
            view.mRenderer.onDrawFrame(gl);
        } finally {}
    }
    

    可以得知,如果readyToDraw()返回true的话,逻辑会走到break退出当前while循环。
    自然而然会调用到我们上层OpenGL ES的渲染实现。

    如果mRenderMode == RENDERMODE_CONTINUOUSLY的话,那么readyToDraw()在成功初始化后就是true。
    也就是在子线程运行时间里会不断while循环调用view.mRenderer.onDrawFrame()这个回调。

    那么当mRenderMode的值为RENDERMODE_WHEN_DIRTY时,由于mRequestRender默认为false。
    那么readyToDraw()返回了false,然后逻辑走到了sGLThreadManager.wait()导致线程阻塞。

    很明显,我们需要看看mRequestRender这个变量被赋值的位置,也就是函数requestRender()中:

    public void requestRender() {
        synchronized(sGLThreadManager) {
            //重点这里,将mRequestRender设置为true,也就是说接下来readyToDraw()返回true
            mRequestRender = true;
            //通知guardedRun()里面的sGLThreadManager.wait()解除阻塞继续运行
            sGLThreadManager.notifyAll();
        }
    }
    

    可以得出结论,在RenderMode的值为RENDERMODE_WHEN_DIRTY时,我们调用一次requestRender()的话,
    相对应的就能触发一次onDrawFrame()进行OpenGL的渲染。

    关于初始化和渲染触发的源码流程分析,就是上面这些了,剩下来的是一些小功能的源码简单解释。

    暂停与恢复

    一般来说,GLSurfaceView需要跟随Activity或者Fragment的生命周期调用对应的
    onPause()onResume()方法,代码最终会调用到GLThead类的相关方法:
    请求暂停相关:

    public void onPause() {
        synchronized (sGLThreadManager) {
            //将要暂停的状态标示量
            mRequestPaused = true;
            //通知GL线程解除阻塞
            sGLThreadManager.notifyAll();
            //这里是为了保证onPause()调用完毕后,GL线程已经是Paused状态了
            while ((! mExited) && (! mPaused)) {
                try {
                    sGLThreadManager.wait();
                } catch (InterruptedException ex) {
                    Thread.currentThread().interrupt();
                }
            }
        }
    }
    

    解除暂停相关:

    public void onResume() {
        synchronized (sGLThreadManager) {
            //解除暂停的状态标示量
            mRequestPaused = false;
            //顺便请求重绘一次GL
            mRequestRender = true;
            mRenderComplete = false;
            sGLThreadManager.notifyAll();
            //这里是为了保证onResume()调用完毕后,GL线程已经是Resume状态了
            while ((! mExited) && mPaused && (!mRenderComplete)) {
                try {
                    sGLThreadManager.wait();
                } catch (InterruptedException ex) {
                    Thread.currentThread().interrupt();
                }
            }
        }
    }
    

    我们看看这个状态是怎么工作的,还是在guardedRun()这个函数里面:

    boolean pausing = false;
    //如果mPaused与mRequestPaused,肯定是上层修改了mRequestPaused
    if (mPaused != mRequestPaused) {
        pausing = mRequestPaused;
        mPaused = mRequestPaused;
        sGLThreadManager.notifyAll();
    }
    if (pausing && mHaveEglSurface) {
        //销毁了EGL Surface,并将mHaveEglSurface设置false
        stopEglSurfaceLocked();
    }
    if (pausing && mHaveEglContext) {
        //销毁了EGL Context,并将mHaveEglContext设置false
        GLSurfaceView view = mGLSurfaceViewWeakRef.get();
        boolean preserveEglContextOnPause = view == null ?
                false : view.mPreserveEGLContextOnPause;
        if (!preserveEglContextOnPause) {
            stopEglContextLocked();
        }
    }
    

    顺便看看释放了什么东西:
    stopEglSurfaceLocked()最终会回调到destroySurfaceImp()函数。

    private void destroySurfaceImp() {
        //将当前线程跟EGL环境解除绑定
        if (mEglSurface != null && mEglSurface != EGL10.EGL_NO_SURFACE) {
            mEgl.eglMakeCurrent(mEglDisplay, EGL10.EGL_NO_SURFACE,
                    EGL10.EGL_NO_SURFACE,
                    EGL10.EGL_NO_CONTEXT);
            GLSurfaceView view = mGLSurfaceViewWeakRef.get();
            if (view != null) {
                //调用EGL Surface的销毁逻辑
                view.mEGLWindowSurfaceFactory.destroySurface(mEgl, mEglDisplay, mEglSurface);
            }
            mEglSurface = null;
        }
    }
    

    stopEglContextLocked()最终会回调到EglHelperfinish()函数。

    public void finish() {
        if (mEglContext != null) {
            GLSurfaceView view = mGLSurfaceViewWeakRef.get();
            if (view != null) {
                //调用到Context销毁的工厂方法
                view.mEGLContextFactory.destroyContext(mEgl, mEglDisplay, mEglContext);
            }
            mEglContext = null;
        }
        if (mEglDisplay != null) {
            //销毁掉EglDisplay
            mEgl.eglTerminate(mEglDisplay);
            mEglDisplay = null;
        }
    }
    

    接着,我们怎么知道暂停状态,不然逻辑会跑到渲染回调那边,但是会在哪里阻塞住呢?看回到readyToDraw():

    private boolean readyToDraw() {
        return (!mPaused) && mHasSurface && (!mSurfaceIsBad)
            && (mWidth > 0) && (mHeight > 0)
            && (mRequestRender || (mRenderMode == RENDERMODE_CONTINUOUSLY));
    }
    

    因为mPaused的值是true,所以这里返回false,由上面可知逻辑将会阻塞住等待唤醒。

    关于onResume()就简单了,就是一次重新的EGL环境初始化过程,这里不做详细分析。

    同步与控制

    关于GLThreadManager这个类,其主要提供的是线程同步控制的功能,因为在GLSurfaceView里面存在两个线程,分别是GL线程和调用线程。
    所以我们需要引入synchronized来保证逻辑的一致性,涉及状态量变动例如mShouldExit、mRequestPaused、mRequestRender必须用synchronized
    还有一个主要是提供waitnotifyAll的功能,进行逻辑阻塞等待唤醒。

    外部调用GL线程

    由于操作OpenGL环境只能通过GL线程,所以GLSurfaceView帮我们提供了queueEvent(Runnable r)这个方法:

    public void queueEvent(Runnable r) {
        synchronized(sGLThreadManager) {
            mEventQueue.add(r);
            sGLThreadManager.notifyAll();
        }
    }
    

    如果我们queueEvent了一个Runnable,也就会在guardedRun()里面触发到这个逻辑:

    while(true) {
        //......
        if (! mEventQueue.isEmpty()) {
            event = mEventQueue.remove(0);
            break;
        }
        //......
    }
    if (event != null) {
        event.run();
        event = null;
        continue;
    }
    

    也就是在GL线程回调了我们传入的Runnable的run方法。

    释放销毁

    最后我们来简单看一下怎么销毁释放整个GLSurfaceView,我们可以定位到onDetachedFromWindow(),其会将mShouldExit设置为true。
    然后返回guardedRun()可以看到进行return退出线程逻辑:

    if (mShouldExit) {
        return;
    }
    

    但是我们需要关注到这块逻辑:

    finally {
        synchronized (sGLThreadManager) {
            stopEglSurfaceLocked();
            stopEglContextLocked();
        }
    }
    

    这里将EGL相关的Context、Surface这些进行释放,并且退出了GL线程的循环。

    结语

    GLSurfaceView不仅帮我们简化了OpenGL ES的使用流程,而且帮我们提供了一个使用EGL的开发范例,了解下源码的流程对于我们开发GL还是大有裨益的。

    本文同步发布于简书CSDN

    End!

    相关文章

      网友评论

        本文标题:源码解析:Android源码GLSurfaceView源码解析

        本文链接:https://www.haomeiwen.com/subject/gehiaftx.html