Android GLSurfaceView EGL_BAD_CONFIG 源码分析定位

Posted 新根

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android GLSurfaceView EGL_BAD_CONFIG 源码分析定位相关的知识,希望对你有一定的参考价值。

最近查看bugly ,发现存在一个多版本遗留 棘手的量级几十w的bug:

java.lang.RuntimeException
createContext failed: EGL_BAD_CONFIG
android.opengl.GLSurfaceView$EglHelper.throwEglException(GLSurfaceView.java:1245)
android.opengl.GLSurfaceView$EglHelper.throwEglException(GLSurfaceView.java:1236)
android.opengl.GLSurfaceView$EglHelper.start(GLSurfaceView.java:1086)
android.opengl.GLSurfaceView$GLThread.guardedRun(GLSurfaceView.java:1462)
android.opengl.GLSurfaceView$GLThread.run(GLSurfaceView.java:1299)

报错信息发生在Android framework api 中,因此考虑从源码入手,本案例是基于 android 7.0 。

1.查看源码走向

先来看下GLSurfaceView$EglHelper#start():

frameworks/base/opengl/java/android/opengl/GLSurfaceView.java

       /**
         * Initialize EGL for a given configuration spec.
         * @param configSpec
         */
        public void start() 
    
            mEgl = (EGL10) EGLContext.getEGL();
            mEglDisplay = mEgl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
			//.......
            GLSurfaceView view = mGLSurfaceViewWeakRef.get();
            if (view == null) 
                mEglConfig = null;
                mEglContext = null;
             else 
                mEglConfig = view.mEGLConfigChooser.chooseConfig(mEgl, mEglDisplay);
				// 通过eglContextFacotry 创建对应有效的egl context
                mEglContext = view.mEGLContextFactory.createContext(mEgl, mEglDisplay, mEglConfig);
            
            if (mEglContext == null || mEglContext == EGL10.EGL_NO_CONTEXT) 
			    //关键点,当创建egl context 失败时,会抛出异常
                mEglContext = null;
                throwEglException("createContext");
            
            mEglSurface = null;
        

结合bugly上的crash 信息,基本上可以确定是创建egl context 失败导致的异常。接下来看下,创建过程。

EGLContextFactory的实现类(即GLSurfaceView&DefaultContextFactory)中,看下createContext()方法如何实现的:

   private class DefaultContextFactory implements EGLContextFactory 
        private int EGL_CONTEXT_CLIENT_VERSION = 0x3098;

        public EGLContext createContext(EGL10 egl, EGLDisplay display, EGLConfig config) 
		    // 这是选择gl es 版本 ,开发者可自由配置 mEGLContextClientVersion 为 2 还是3 
            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);
        
		//.......
    

接下来看下EGLImpl 中如何创建对应的context:

frameworks/base/opengl/java/com/google/android/gles_jni/EGLImpl.java

    public EGLContext eglCreateContext(EGLDisplay display, EGLConfig config, EGLContext share_context, int[] attrib_list) 
        long eglContextId = _eglCreateContext(display, config, share_context, attrib_list);
        if (eglContextId == 0)  // 关键点,若是调用native 层 创建context 失败,则会返回EGL_NO_CONTEXT。
            return EGL10.EGL_NO_CONTEXT;
        
        return new EGLContextImpl( eglContextId );
    
	
    private native long _eglCreateContext(EGLDisplay display, EGLConfig config, EGLContext share_context, int[] attrib_list);

接下来看看下,jni层的代码走向:
frameworks/base/core/jni/com_google_android_gles_jni_EGLImpl.cpp

static jlong jni_eglCreateContext(JNIEnv *_env, jobject _this, jobject display,
        jobject config, jobject share_context, jintArray attrib_list) 
    if (display == NULL || config == NULL || share_context == NULL
        || !validAttribList(_env, attrib_list)) 
        jniThrowException(_env, "java/lang/IllegalArgumentException", NULL);
        return JNI_FALSE;
    
    EGLDisplay dpy = getDisplay(_env, display);
    EGLConfig  cnf = getConfig(_env, config);
    EGLContext shr = getContext(_env, share_context);
    jint* base = beginNativeAttribList(_env, attrib_list);
    EGLContext ctx = eglCreateContext(dpy, cnf, shr, base);
    endNativeAttributeList(_env, attrib_list, base);
    return reinterpret_cast<jlong>(ctx);

接下来看下 eglCreateContext():

frameworks/native/opengl/libs/EGL/eglApi.cpp

EGLContext eglCreateContext(EGLDisplay dpy, EGLConfig config,
                            EGLContext share_list, const EGLint *attrib_list)

    clearError();

    egl_connection_t* cnx = NULL;
	
    const egl_display_ptr dp = validate_display_connection(dpy, cnx);
    if (dp) 
        if (share_list != EGL_NO_CONTEXT) 
            if (!ContextRef(dp.get(), share_list).get()) 
                return setError(EGL_BAD_CONTEXT, EGL_NO_CONTEXT);
            
            egl_context_t* const c = get_context(share_list);
            share_list = c->context;
        
        EGLContext context = cnx->egl.eglCreateContext(
                dp->disp.dpy, config, share_list, attrib_list);
	
        if (context != EGL_NO_CONTEXT) 
            // figure out if it's a GLESv1 or GLESv2
            int version = 0;
            if (attrib_list) 
                while (*attrib_list != EGL_NONE) 
                    GLint attr = *attrib_list++;
                    GLint value = *attrib_list++;
                    if (attr == EGL_CONTEXT_CLIENT_VERSION) 
                        if (value == 1) 
                            version = egl_connection_t::GLESv1_INDEX;
                         else if (value == 2 || value == 3) 
                            version = egl_connection_t::GLESv2_INDEX;
                        
                    
                ;
            
            egl_context_t* c = new egl_context_t(dpy, context, config, cnx,
                    version);
            return c;
        
    
	//当获取不到有效的display 时,会返回egl_no_context;
    return EGL_NO_CONTEXT;

接下来继续看下:


egl_display_ptr validate_display_connection(EGLDisplay dpy,
       egl_connection_t*& cnx) 
    cnx = NULL;
    egl_display_ptr dp = validate_display(dpy);
    if (!dp)
        return dp;
    cnx = &gEGLImpl;
    if (cnx->dso == 0) 
	    //这里是关键信息
        return setError(EGL_BAD_CONFIG, egl_display_ptr(NULL));
    
    return dp;

接下来看下validate_display():

egl_display_ptr validate_display(EGLDisplay dpy) 
    egl_display_ptr dp = get_display(dpy);
    if (!dp)
        return setError(EGL_BAD_DISPLAY, egl_display_ptr(NULL));
    if (!dp->isReady())
        return setError(EGL_NOT_INITIALIZED, egl_display_ptr(NULL));

    return dp;

也就是获取不到 有效的diplay 时,会导致egl context 创建失败,从而抛出异常。

2.推断定位原因和解决方案

谷歌搜索:相关的报错,有些较老的机型确实存在问题,如下图所示:

参考链接:https://github.com/ofZach/inkSpace/issues/2

有些机型是可能不支持gl es 2 创建context ,可以通过以下代码来判断:

    private static class ContextFactory implements GLSurfaceView.EGLContextFactory 
        private static int EGL_CONTEXT_CLIENT_VERSION = 0x3098;
        public EGLContext createContext(EGL10 egl, EGLDisplay display, EGLConfig eglConfig) 
            Log.w(TAG, "creating OpenGL ES 2.0 context");
            checkEglError("Before eglCreateContext", egl);
            int[] attrib_list = EGL_CONTEXT_CLIENT_VERSION, 2, EGL10.EGL_NONE ;
            EGLContext context = egl.eglCreateContext(display, eglConfig, EGL10.EGL_NO_CONTEXT, attrib_list);
            checkEglError("After eglCreateContext", egl);
            return context;
        

        public void destroyContext(EGL10 egl, EGLDisplay display, EGLContext context) 
            egl.eglDestroyContext(display, context);
        
    

    private static void checkEglError(String prompt, EGL10 egl) 
        int error;
        while ((error = egl.eglGetError()) != EGL10.EGL_SUCCESS) 
            Log.e(TAG, String.format("%s: EGL error: 0x%x", prompt, error));
        
    

更多信息请阅读ndk-samples/hello-gl2

但比对了bugly上的机型发现,都是一些新机器,现在大部分都支持opengl es 2,因此排除了该可能性。

还有一种可能性是状态不对导致的,因触发EglHelper.start(),必须readyToDraw() 返回true:

frameworks/base/opengl/java/android/opengl/GLSurfaceView.java

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

解读下几个条件:

  • mPaused: 是非调用onPause()后状态;
  • mHasSurface 是指suface 创建成功;
  • mSurfaceIsBad是指suface 是否有效;
  • mRequestRender 是请求主动Render;
  • RENDERMODE_CONTINUOUSLY 是指循环渲染模式.

检查项目中发现,有一处老代码中存在严重的问题,没有调用GLSufaceView #onPause(),被注释掉了:

    @Override
    public void onPause() 
        Message msg = Message.obtain();
        msg.what = HANDLER_ON_NATIVE_PAUSE;
        mHandler.sendMessageAtFrontOfQueue(msg);
        //this.activity.startChkRoomTick();
        setRenderMode(RENDERMODE_WHEN_DIRTY);
        mRenderer.onPause();
        //super.onPause();
    

重点:Activity 的onResume()onPause() 必须调用GLSufaceView 的onResume()onPause()

3.OpenGl 创建context 过程中其他异常(EGL_BAD_ALLOC和EGL_BAD_DISPLAY)

当然也可以继续看下open gl es 端eglCreateContext的过程(可能抛出的其他异常):

frameworks/native/opengl/libagl/egl.cpp

EGLContext eglCreateContext(EGLDisplay dpy, EGLConfig config,
                            EGLContext /*share_list*/, const EGLint* /*attrib_list*/)

    // 检查EGLDisplay 是否有效的
    if (egl_display_t::is_valid(dpy) == EGL_FALSE)
        return setError(EGL_BAD_DISPLAY, EGL_NO_SURFACE);

    ogles_context_t* gl = ogles_init(sizeof(egl_context_t));
	//初始化失败,返回为0时,会抛出内存申请失败
    if (!gl) return setError(EGL_BAD_ALLOC, EGL_NO_CONTEXT);

    egl_context_t* c = static_cast<egl_context_t*>(gl->rasterizer.base);
    c->flags = egl_context_t::NEVER_CURRENT;
    c->dpy = dpy;
    c->config = config;
    c->read = 0;
    c->draw = 0;
    return (EGLContext)gl;

若是想了解opengl es 的更多信息,可以阅读OpenGL ES升级打怪之 GLSurfaceView源码分析

以上是关于Android GLSurfaceView EGL_BAD_CONFIG 源码分析定位的主要内容,如果未能解决你的问题,请参考以下文章

Android GLSurfaceView EGL_BAD_CONFIG 源码分析定位

GLSurfaceView 源码分析 & EGL 创建过程

GLSurfaceView 源码分析 & EGL 创建过程

Android OpenGL ES 学习 – GLSurfaceView 源码解析GL线程以及自定义 EGL

Android OpenGL ES 学习 – GLSurfaceView 源码解析GL线程以及自定义 EGL

android studio | openGL es 3.0增强现实(AR)开发 GLSurfaceView浅解