You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.27 21:52:53