Android OpenGL ES中GL_TEXTURE_EXTERNAL_OES纹理多遍渲染失败排查
问题描述
尝试使用帧缓冲区(Framebuffer)进行多遍渲染,输入纹理来自相机,类型为GL_TEXTURE_EXTERNAL_OES。已确认着色器正常(使用普通GL_TEXTURE_2D时渲染效果完美),但当前代码无法正常工作,第一遍渲染似乎完全未执行,怀疑是GL_TEXTURE_EXTERNAL_OES纹理导致帧缓冲区异常。
帧缓冲区准备代码
val fboIds = IntArray(1) val texIds = IntArray(1) fun prepareFramebuffer() { GLES20.glGenFramebuffers(1, fboIds, 0) glBindFramebuffer(GL_FRAMEBUFFER, fboIds[0]) GLES20.glGenTextures(1, texIds, 0) glBindTexture(GL_TEXTURE_2D, texIds[0]) glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, videoResolution.width, videoResolution.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, null); glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST.toFloat() ) glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR.toFloat() ) glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE ) glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE ) glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texIds[0], 0) val status = glCheckFramebufferStatus(GL_FRAMEBUFFER) Timber.d("FRAMEBUFFER-STATUS is complete? ${status == GL_FRAMEBUFFER_COMPLETE}") //返回true glBindFramebuffer(GL_FRAMEBUFFER, 0) }
绘制函数代码
fun draw( mvpMatrix: FloatArray?, vertexBuffer: FloatBuffer?, firstVertex: Int, vertexCount: Int, coordsPerVertex: Int, vertexStride: Int, texMatrix: FloatArray?, texBuffer: FloatBuffer?, textureId: Int, texStride: Int, maskBitmapOverlay: Bitmap?, mainFrameMaskTextureId: Int ) { glUseProgram(programHandle) glUniformMatrix4fv(mvpMatrixLoc, 1, false, mvpMatrix, 0) glUniformMatrix4fv(texMatrixLoc, 1, false, texMatrix, 0) glEnableVertexAttribArray(positionLoc) glVertexAttribPointer( positionLoc, coordsPerVertex, GL_FLOAT, false, vertexStride, vertexBuffer ) glEnableVertexAttribArray(textureCoordLoc) glVertexAttribPointer( textureCoordLoc, 2, GL_FLOAT, false, texStride, texBuffer ) //textureId为GL_TEXTURE_EXTERNAL_OES类型 glActiveTexture(GL_TEXTURE3) glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, textureId) //mainFrameMaskTextureId为GL_TEXTURE_2D类型 glActiveTexture(GL_TEXTURE1) glBindTexture(mainFrameMaskTarget, mainFrameMaskTextureId) glEnable(GL_BLEND) glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA) GLUtils.texImage2D(mainFrameMaskTarget, 0, GL_RGBA, maskBitmapOverlay, 0) ... //其他uniform变量 ... glUniform1i(sTextureLoc, 3) glUniform1i(mainFrameMaskLoc, 1) glUniform1i(blurringDirectionLoc, 1)//第二遍渲染会修改此值,但修改后会覆盖第一遍的设置 //绑定帧缓冲区 glBindFramebuffer(GL_FRAMEBUFFER, fboIds[0]) //执行第一遍渲染 glDrawArrays(GL_TRIANGLE_STRIP, firstVertex, vertexCount) checkGlError("glDrawArrays") glBindFramebuffer(GL_FRAMEBUFFER, 0) glBindTexture(GL_TEXTURE_2D, texIds[0]) glUniform1i(blurringDirectionLoc, 0) //执行第二遍渲染 glDrawArrays(GL_TRIANGLE_STRIP, firstVertex, vertexCount) //清理操作 glDisableVertexAttribArray(positionLoc) glDisableVertexAttribArray(textureCoordLoc) glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, 0) glUseProgram(0) }
问题排查与修复
核心问题1:GL_TEXTURE_EXTERNAL_OES的采样限制
GL_TEXTURE_EXTERNAL_OES是为相机/视频流设计的特殊纹理,绝大多数移动GPU不支持在FBO渲染通路中直接采样该类型纹理,这是第一遍渲染无输出的根本原因——GPU无法读取OES纹理的内容写入FBO。
核心问题2:第二遍渲染的纹理绑定逻辑错误
第二遍渲染需要使用FBO输出的texIds[0]作为输入,但代码仅执行了glBindTexture(GL_TEXTURE_2D, texIds[0]),未将其绑定到纹理单元,也未更新对应的采样器uniform,导致GPU仍在采样原来的OES纹理。
核心问题3:掩码纹理的不合理更新
在每帧绘制函数中调用GLUtils.texImage2D更新掩码纹理,会导致频繁的纹理内存重分配,不仅影响性能,还可能干扰纹理状态稳定性。
修复后的关键调整
1. 添加OES纹理到普通2D纹理的中转
先将OES纹理内容拷贝到临时GL_TEXTURE_2D,再用这个2D纹理作为第一遍渲染的输入:
// 在prepareFramebuffer中额外创建中转纹理 val tempOesTexId = IntArray(1) GLES20.glGenTextures(1, tempOesTexId, 0) glBindTexture(GL_TEXTURE_2D, tempOesTexId[0]) glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, videoResolution.width, videoResolution.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, null) // 设置和帧缓冲区纹理一致的参数 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST.toFloat()) glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR.toFloat()) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE)
在draw函数第一遍渲染前执行拷贝:
// 拷贝OES纹理到中转2D纹理 glBindFramebuffer(GL_FRAMEBUFFER, 0) glBindTexture(GL_TEXTURE_2D, tempOesTexId[0]) glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, videoResolution.width, videoResolution.height) // 绑定中转纹理到纹理单元3,替换原OES纹理绑定 glActiveTexture(GL_TEXTURE3) glBindTexture(GL_TEXTURE_2D, tempOesTexId[0])
2. 修复第二遍渲染的纹理绑定
glBindFramebuffer(GL_FRAMEBUFFER, 0) // 将FBO输出纹理绑定到新的纹理单元(比如GL_TEXTURE2) glActiveTexture(GL_TEXTURE2) glBindTexture(GL_TEXTURE_2D, texIds[0]) // 更新采样器uniform指向该单元 glUniform1i(sTextureLoc, 2) glUniform1i(blurringDirectionLoc, 0) // 执行第二遍渲染 glDrawArrays(GL_TRIANGLE_STRIP, firstVertex, vertexCount)
3. 优化掩码纹理更新逻辑
将掩码纹理的初始化移到绘制函数外,仅在maskBitmapOverlay变化时调用GLUtils.texImage2D更新,避免每帧重复操作。
额外注意事项
- 确保FBO尺寸与相机输出的
videoResolution完全匹配,避免采样拉伸或坐标偏移。 - 每次切换纹理单元后,必须同步更新对应的uniform采样器值,避免纹理类型与单元绑定不匹配。
- 持续监控
glCheckFramebufferStatus的返回值,任何非GL_FRAMEBUFFER_COMPLETE的状态都需要排查配置问题。
内容的提问来源于stack exchange,提问作者Mohammad Elsayed
相关产品推荐
相关产品推荐

