GLSurfaceView共享EGL上下文给Native库后出现GL_FRAMEBUFFER_UNDEFINED异常
原本正常运行的Android应用,通过GLSurfaceView将外部OES纹理渲染到帧缓冲,转换为普通OpenGL纹理后再输出到屏幕。为了将普通OpenGL纹理传入Native库做TFLite GPU delegate后处理,在onSurfaceCreated中把当前EGL上下文作为共享上下文初始化Native库(Native库运行在GLSurfaceView线程外)。但添加初始化代码后,渲染画面全黑,解绑帧缓冲时调用GLES20.glCheckFramebufferStatus(GLES20.GL_FRAMEBUFFER)返回GL_FRAMEBUFFER_UNDEFINED,尽管eglGetCurrentContext显示上下文状态正常。
相关代码
MainActivity.kt
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) supportActionBar?.hide() surfaceView = GLSurfaceView(this) surfaceView.setPreserveEGLContextOnPause(true); surfaceView.setEGLContextClientVersion(3); surfaceView.setEGLConfigChooser(8, 8, 8, 8, 16, 0); surfaceView.setRenderer(this); surfaceView.setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY); getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); setContentView(surfaceView); } override fun onSurfaceCreated(p0: GL10?, p1: EGLConfig?) { val glContext: Long = eglGetCurrentContext().nativeHandle // Initialize the library and share the context, before generating any buffers // If I comment this out, everything works well library = NativeLib.initialize(glContext) val textures = IntArray(1) GLES20.glGenTextures(1, textures, 0) cameraTextureId = textures[0] val textureTarget = GLES11Ext.GL_TEXTURE_EXTERNAL_OES GLES20.glBindTexture(textureTarget, cameraTextureId) GLES20.glTexParameteri(textureTarget, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE) GLES20.glTexParameteri(textureTarget, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE) GLES20.glTexParameteri(textureTarget, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR) GLES20.glTexParameteri(textureTarget, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR) surfaceTexture = SurfaceTexture(cameraTextureId) ... // Initialize shaders and attributes // create glTexture ... GLES20.glDisable(GLES20.GL_DEPTH_TEST) GLES20.glDisable(GLES20.GL_CULL_FACE) val values = IntArray(1) GLES20.glGenFramebuffers(1, values, 0) frameBuffer = values[0] } override fun onDrawFrame(p0: GL10?) { bindFramebuffer(glTexture) GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT); GLES20.glActiveTexture(GLES20.GL_TEXTURE0); surfaceTexture?.updateTexImage() GLES20.glTexParameteri( GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR); GLES20.glTexParameteri( GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR); GLES20.glTexParameteri( GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE); GLES20.glTexParameteri( GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE); .... // setup coordinates and attribute pointers ... GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4); unbindFrameBuffer() // Now draw to screen GLES20.glDisable(GLES20.GL_DEPTH_TEST); GLES20.glDepthMask(false); GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4); ShaderUtil.checkGLError(TAG, "glDrawArrays"); GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, 0); } private fun _bindFrameBuffer(frameBuffer_: Int, texture: Int?, width: Int, height: Int) { GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, frameBuffer_) texture?.apply { GLES20.glFramebufferTexture2D( GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0, GLES20.GL_TEXTURE_2D, this, 0 ) } val status = GLES20.glCheckFramebufferStatus(GLES20.GL_FRAMEBUFFER) if (status != GLES20.GL_FRAMEBUFFER_COMPLETE) { throw java.lang.RuntimeException("Framebuffer not complete, status=$status") } GLES20.glViewport(0, 0, width, height) } fun bindFramebuffer(texture: Int) { _bindFrameBuffer(frameBuffer, texture, targetSurfaceWidth, targetSurfaceHeight) } fun unbindFrameBuffer() { _bindFrameBuffer(0, null, screenSurfaceWidth, screenSurfaceHeight) }
Native库代码
create_egl_context(EGLContext share_context) { m_egl_display = eglGetDisplay(EGL_DEFAULT_DISPLAY); eglInitialize(m_egl_display, nullptr, nullptr); const EGLint config_attr[] = { // clang-format off EGL_RENDERABLE_TYPE, EGL_OPENGL_ES3_BIT_KHR, // Allow rendering to pixel buffers or directly to windows. EGL_SURFACE_TYPE, EGL_PBUFFER_BIT | EGL_WINDOW_BIT, EGL_RED_SIZE, 8, EGL_GREEN_SIZE, 8, EGL_BLUE_SIZE, 8, EGL_ALPHA_SIZE, 8, // if you need the alpha channel EGL_DEPTH_SIZE, 16, // if you need the depth buffer EGL_NONE // clang-format on }; EGLint w, h, format; EGLint numConfigs; EGLConfig config = nullptr; /* Here, the application chooses the configuration it desires. * find the best match if possible, otherwise use the very first one */ eglChooseConfig(m_egl_display, config_attr, &config,1, &numConfigs); assert(numConfigs); if (config == nullptr) { std::cerr << "Failed getting config" << std::endl; return EGL_NO_CONTEXT; } const EGLint context_attr[] = { // clang-format off EGL_CONTEXT_CLIENT_VERSION, 3, EGL_NONE // clang-format on }; auto egl_context = eglCreateContext(m_egl_display, config, share_context, context_attr); eglMakeCurrent(m_egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, egl_context); return egl_context; }
问题根源
问题出在Native库初始化时的eglMakeCurrent调用:
- GLSurfaceView的渲染线程原本绑定了对应屏幕surface的EGL上下文,Native库中调用
eglMakeCurrent(m_egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, egl_context)会抢占当前线程的上下文绑定,导致后续GLSurfaceView线程中创建纹理、帧缓冲等OpenGL操作,实际是在Native的无surface上下文中执行的,而非原来绑定到屏幕的上下文。 - 后续创建的帧缓冲、纹理属于Native上下文,GLSurfaceView的渲染上下文无法正确访问,最终出现帧缓冲状态异常、渲染全黑的问题。
解决方案
1. 不在GLSurfaceView线程中激活Native上下文
修改Native库初始化逻辑,只创建共享上下文,不在GLSurfaceView线程中调用eglMakeCurrent,而是在Native库自己的工作线程中激活上下文:
// 修改create_egl_context,只创建上下文不激活 create_egl_context(EGLContext share_context) { m_egl_display = eglGetDisplay(EGL_DEFAULT_DISPLAY); eglInitialize(m_egl_display, nullptr, nullptr); const EGLint config_attr[] = { EGL_RENDERABLE_TYPE, EGL_OPENGL_ES3_BIT_KHR, EGL_SURFACE_TYPE, EGL_PBUFFER_BIT | EGL_WINDOW_BIT, EGL_RED_SIZE, 8, EGL_GREEN_SIZE, 8, EGL_BLUE_SIZE, 8, EGL_ALPHA_SIZE, 8, EGL_DEPTH_SIZE, 16, EGL_NONE }; EGLint numConfigs; EGLConfig config = nullptr; eglChooseConfig(m_egl_display, config_attr, &config, 1, &numConfigs); assert(numConfigs); if (config == nullptr) { std::cerr << "Failed getting config" << std::endl; return EGL_NO_CONTEXT; } const EGLint context_attr[] = { EGL_CONTEXT_CLIENT_VERSION, 3, EGL_NONE }; return eglCreateContext(m_egl_display, config, share_context, context_attr); } // 在Native库的独立工作线程中执行上下文激活和后续操作 void init_native_worker(EGLContext native_context) { // 确保此函数在Native库的专属线程中调用 eglMakeCurrent(m_egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, native_context); // 初始化TFLite GPU delegate等操作放在此处 }
2. 恢复GLSurfaceView线程的上下文绑定(可选)
如果无法避免在GLSurfaceView线程中初始化Native库,初始化后重新绑定原上下文:
override fun onSurfaceCreated(p0: GL10?, p1: EGLConfig?) { val glContext = eglGetCurrentContext() val display = eglGetCurrentDisplay() val surface = surfaceView.holder.surface val nativeGlContext = glContext.nativeHandle library = NativeLib.initialize(nativeGlContext) // 重新绑定GLSurfaceView的原上下文 eglMakeCurrent(display, surface, surface, glContext) // 后续创建纹理、帧缓冲的逻辑不变 val textures = IntArray(1) GLES20.glGenTextures(1, textures, 0) cameraTextureId = textures[0] // ... 其他初始化代码 }
3. 确保EGL配置一致性
确认Native库使用的EGL配置与GLSurfaceView完全匹配:GLSurfaceView的配置是EGL_RED_SIZE=8, EGL_GREEN_SIZE=8, EGL_BLUE_SIZE=8, EGL_ALPHA_SIZE=8, EGL_DEPTH_SIZE=16,Native库的config_attr已经匹配,但可以通过打印配置参数验证实际选中的配置是否一致。
额外注意事项
- 共享上下文仅共享纹理、缓冲区等对象,每个线程必须绑定独立的上下文,不能在同一线程中随意切换上下文。
- TFLite GPU delegate的操作必须在Native库的专属线程中执行,避免干扰GLSurfaceView的渲染流程。
内容的提问来源于stack exchange,提问作者La bla bla

