云计算百科
云计算领域专业知识百科平台

Open GL ES -> 应用前后台、Recent切换,SurfaceView纹理贴图闪烁问题分析解决

Android SurfaceView + OpenGL 纹理贴图切换前后台闪烁问题解决方案

一、问题现象

在使用 SurfaceView + 自定义 OpenGL 渲染线程实现纹理贴图时,应用切换到 Recent 后台再回到前台,图片会出现明显的闪烁(黑屏一帧或白屏一帧)。

二、问题分析

2.1 Surface 生命周期回顾

当应用切到后台再回前台时,SurfaceHolder.Callback 的回调顺序如下:

切到后台: surfaceDestroyed()
回到前台: surfaceCreated() → surfaceChanged()

2.2 错误的实现方式

很多开发者(包括我最初)会这样写:

override fun surfaceCreated(holder: SurfaceHolder) {
// 每次都新建渲染线程
mRenderThread = RenderThread(holder.surface, renderData).apply {
start()
}
}

override fun surfaceDestroyed(holder: SurfaceHolder) {
// 销毁线程和所有 GL 资源
mRenderThread?.shutdown()
mRenderThread = null
}

2.3 根本原因

问题的核心在于:每次回前台都会销毁并重建整个 EGL 环境和 GL 资源!

操作耗时影响
销毁 EGLContext GL 资源(纹理、VAO、VBO)全部失效
重建 EGLContext 需要重新初始化
重新加载纹理 Bitmap 解码 + 上传 GPU

纹理重新加载需要时间,而此时 Surface 已经可见但内容还未准备好,系统合成器会显示空白/黑色缓冲区,导致用户看到闪烁。

三、解决方案

3.1 核心思路

保持渲染线程和 EGLContext 存活,只重建 EGLSurface。

GL 资源(纹理、VAO、VBO 等)是绑定在 EGLContext 上的,只要 Context 不销毁,这些资源就不会丢失。

切到后台: 暂停渲染(保留线程和 Context)
回到前台: 重建 EGLSurface,绑定到已有 Context,立即恢复渲染

3.2 关键代码改动

修改 SurfaceHolder.Callback

override val callback: SurfaceHolder.Callback = object : SurfaceHolder.Callback {
override fun surfaceCreated(holder: SurfaceHolder) {
val thread = mRenderThread
if (thread != null && thread.isAlive) {
// ✅ 复用已有线程,只更新 Surface
thread.updateSurface(holder.surface)
} else {
// 首次创建
mRenderThread = RenderThread(holder.surface, renderData).apply {
start()
}
}
}

override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {
mRenderThread?.updateSize(width, height)
}

override fun surfaceDestroyed(holder: SurfaceHolder) {
// ✅ 不销毁线程,只暂停渲染
mRenderThread?.pause()
}
}

RenderThread 新增 pause 和 updateSurface 方法

class RenderThread(
@Volatile private var surface: Surface,
private val renderData: OpenGLData
) : Thread() {

@Volatile
private var paused = false

@Volatile
private var surfaceUpdated = false

fun pause() {
synchronized(lock) {
paused = true
}
}

fun updateSurface(newSurface: Surface) {
synchronized(lock) {
surface = newSurface
surfaceUpdated = true
paused = false
lock.notifyAll()
}
}

private fun renderLoop() {
while (running) {
synchronized(lock) {
// 等待条件
while (running && (paused || ...)) {
lock.wait()
}

// 处理 Surface 更新
if (surfaceUpdated) {
mEGLEnvironment?.updateSurface(surface)
surfaceUpdated = false
requestRender = true
}

// … 渲染逻辑
}
}
}
}

EGLEnvironment 新增 updateSurface 方法

class EGLEnvironment(
private val egl: EGL10,
private val display: EGLDisplay,
private val config: EGLConfig, // 需要保存 config
private val context: EGLContext,
private var surface: EGLSurface
) {
/**
* 更新 Surface(保留 EGLContext,只重建 EGLSurface)
* 这是解决闪烁的关键:GL 资源绑定在 Context 上,不会丢失
*/

fun updateSurface(newSurface: Surface) {
// 1. 解绑当前 surface
egl.eglMakeCurrent(display, EGL10.EGL_NO_SURFACE,
EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_CONTEXT)
// 2. 销毁旧 surface
egl.eglDestroySurface(display, surface)
// 3. 创建新 surface
surface = egl.eglCreateWindowSurface(display, config, newSurface, null)
// 4. 重新绑定到已有的 context
egl.eglMakeCurrent(display, surface, surface, context)
}
}

View 销毁时释放资源

open class BaseSurfaceView : SurfaceView {

override fun onDetachedFromWindow() {
super.onDetachedFromWindow()
// View 真正销毁时才释放所有资源
renderEngine.release()
}
}

四、方案对比

方案是否有效原因
❌ 设置 alpha = 0f 隐藏 部分机型无效 SurfaceView 是独立合成层,alpha 不一定生效
❌ 设置 visibility = INVISIBLE 有延迟 仍然会有时序问题
❌ 使用 Callback2.surfaceRedrawNeededAsync 治标不治本 首帧仍需重新加载纹理
✅ 保留 EGLContext,只重建 EGLSurface 有效 GL 资源不丢失,无需重新加载

五、总结

SurfaceView 切换前后台闪烁的根本原因是:

每次 Surface 销毁重建时,都销毁了整个 EGL 上下文,导致 GL 资源(尤其是纹理)需要重新加载,而加载期间 Surface 已可见,造成闪烁。

正确做法是:

  • surfaceDestroyed 时只暂停渲染,不销毁线程和 EGLContext
  • surfaceCreated 时复用已有线程,只重建 EGLSurface 并绑定到已有 Context
  • onDetachedFromWindow 时才真正释放所有资源
  • 这个方案类似于 GLSurfaceView.setPreserveEGLContextOnPause(true) 的效果,但适用于自定义渲染线程的场景。


    参考资料:

    • Android SurfaceView 官方文档
    • EGL 规范

    EGL渲染环境、数据、引擎、线程的创建

    抽象工厂模式定义EGL环境接口

    // 抽象工厂模式:负责创建EGL相关组件族
    interface EGLComponentFactory {
    fun createEGL(): EGL10
    fun createEGLDisplay(egl: EGL10): EGLDisplay
    fun createEGLConfig(egl: EGL10, display: EGLDisplay): EGLConfig
    fun createEGLContext(egl: EGL10, display: EGLDisplay, config: EGLConfig): EGLContext
    fun createEGLSurface(egl: EGL10, display: EGLDisplay, config: EGLConfig, surface: Surface): EGLSurface
    }

    EGL环境接口的具体实现

    // 具体工厂实现
    class DefaultEGLFactory : EGLComponentFactory {
    override fun createEGL(): EGL10 = EGLContext.getEGL() as EGL10

    override fun createEGLDisplay(egl: EGL10): EGLDisplay {
    val eglDisplay = egl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY)
    if (eglDisplay == EGL10.EGL_NO_DISPLAY) {
    throw RuntimeException("eglGetDisplay failed")
    }

    val version = IntArray(2)
    if (!egl.eglInitialize(eglDisplay, version)) {
    throw RuntimeException("eglInitialize failed")
    }
    return eglDisplay
    }

    override fun createEGLConfig(egl: EGL10, display: EGLDisplay): EGLConfig {
    val attributes = intArrayOf(
    EGL_RED_SIZE, 8,
    EGL_GREEN_SIZE, 8,
    EGL_BLUE_SIZE, 8,
    EGL_ALPHA_SIZE, 8,
    EGL_DEPTH_SIZE, 8,
    EGL_STENCIL_SIZE, 8,
    EGL_NONE
    )

    val numConfigs = IntArray(1)
    egl.eglChooseConfig(display, attributes, null, 0, numConfigs)

    if (numConfigs[0] <= 0) {
    throw RuntimeException("No matching EGL configs")
    }

    val configs = arrayOfNulls<EGLConfig>(numConfigs[0])
    egl.eglChooseConfig(display, attributes, configs, numConfigs.size, numConfigs)

    return configs[0] ?: throw RuntimeException("No suitable EGL config found")
    }

    override fun createEGLContext(egl: EGL10, display: EGLDisplay, config: EGLConfig): EGLContext {
    val contextAttrs = intArrayOf(
    EGL_CONTEXT_CLIENT_VERSION, 3,
    EGL_NONE
    )
    val eglContext = egl.eglCreateContext(display, config, EGL10.EGL_NO_CONTEXT, contextAttrs)
    if (eglContext == EGL10.EGL_NO_CONTEXT) {
    throw RuntimeException("eglCreateContext failed")
    }
    return eglContext
    }

    override fun createEGLSurface(
    egl: EGL10,
    display: EGLDisplay,
    config: EGLConfig,
    surface: Surface
    ): EGLSurface {
    val eglSurface = egl.eglCreateWindowSurface(display, config, surface, null)
    if (eglSurface == EGL10.EGL_NO_SURFACE) {
    throw RuntimeException("eglCreateWindowSurface failed")
    }
    return eglSurface
    }
    }

    构造者实现EGL环境

    // 构建者模式:配置和构建EGL环境
    class EGLEnvironmentBuilder(private val factory: EGLComponentFactory = DefaultEGLFactory()) {
    private lateinit var mEGL: EGL10
    private lateinit var mEGLDisplay: EGLDisplay
    private lateinit var mEGLConfig: EGLConfig
    private lateinit var mEGLContext: EGLContext
    private lateinit var mEGLSurface: EGLSurface

    fun build(surface: Surface): EGLEnvironment {
    mEGL = factory.createEGL()
    mEGLDisplay = factory.createEGLDisplay(mEGL)
    mEGLConfig = factory.createEGLConfig(mEGL, mEGLDisplay)
    mEGLContext = factory.createEGLContext(mEGL, mEGLDisplay, mEGLConfig)
    mEGLSurface = factory.createEGLSurface(mEGL, mEGLDisplay, mEGLConfig, surface)

    if (!mEGL.eglMakeCurrent(mEGLDisplay, mEGLSurface, mEGLSurface, mEGLContext)) {
    throw RuntimeException("eglMakeCurrent failed")
    }

    return EGLEnvironment(mEGL, mEGLDisplay, mEGLConfig, mEGLContext, mEGLSurface)
    }

    // EGL环境类
    class EGLEnvironment(
    private val egl: EGL10,
    private val display: EGLDisplay,
    private val config: EGLConfig,
    private val context: EGLContext,
    private var surface: EGLSurface
    ) {

    /**
    * 更新 Surface(保留 EGLContext,只重建 EGLSurface)
    * 这是解决闪烁的关键:GL 资源绑定在 Context 上,不会丢失
    */

    fun updateSurface(newSurface: Surface) {
    // 解绑当前 surface
    egl.eglMakeCurrent(display, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_CONTEXT)
    // 销毁旧 surface
    egl.eglDestroySurface(display, surface)
    // 创建新 surface
    surface = egl.eglCreateWindowSurface(display, config, newSurface, null)
    if (surface == EGL10.EGL_NO_SURFACE) {
    throw RuntimeException("eglCreateWindowSurface failed on updateSurface")
    }
    // 重新绑定
    if (!egl.eglMakeCurrent(display, surface, surface, context)) {
    throw RuntimeException("eglMakeCurrent failed on updateSurface")
    }
    }

    fun swapBuffers() {
    if (!egl.eglSwapBuffers(display, surface)) {
    // 忽略 swap 失败(可能 surface 已失效)
    }
    }

    fun release() {
    egl.eglMakeCurrent(
    display,
    EGL10.EGL_NO_SURFACE,
    EGL10.EGL_NO_SURFACE,
    EGL10.EGL_NO_CONTEXT
    )
    egl.eglDestroySurface(display, surface)
    egl.eglDestroyContext(display, context)
    egl.eglTerminate(display)
    }
    }
    }

    渲染数据

    渲染数据接口定义

    /**
    * OpenGL渲染数据接口
    */

    interface OpenGLData {
    /**
    * Surface创建时调用,用于初始化OpenGL资源
    */

    fun onSurfaceCreated()

    /**
    * Surface尺寸变化时调用,用于更新视口
    */

    fun onSurfaceChanged(width: Int, height: Int)

    /**
    * 每帧渲染时调用,执行实际的绘制操作
    */

    fun onDrawFrame()

    /**
    * Surface销毁时调用,可以标记资源需要重新初始化
    */

    fun onSurfaceDestroyed()
    }

    渲染数据接口实现

    /**
    * OpenGL渲染数据默认实现类
    * 提供OpenGL渲染所需的基本数据结构和操作方法
    */

    class BaseOpenGLData(private val context: Context) : OpenGLData {

    // ⭐ 缩放配置
    companion object {
    const val MIN_SCALE = 0.5f // 最小缩放
    const val MAX_SCALE = 5f // 最大缩放
    const val BOUNCE_THRESHOLD = 0.8f // 回弹阈值
    const val ANIMATION_DURATION = 300L // 动画时长
    }

    private val NO_OFFSET = 0
    private val VERTEX_POS_DATA_SIZE = 3
    private val TEXTURE_POS_DATA_SIZE = 2
    private val STRIDE = (VERTEX_POS_DATA_SIZE + TEXTURE_POS_DATA_SIZE) * 4 // 每个顶点的总字节数

    // 着色器程序ID
    private var mProgram: Int = 1

    // 顶点和纹理坐标合并在一个数组中
    // 格式:x, y, z, u, v (顶点坐标后跟纹理坐标)
    val vertexData = floatArrayOf(
    // 顶点坐标 // 纹理坐标
    1.0f, 1.0f, 0.0f, 0.0f, 1.0f, // 左上
    1.0f, 1.0f, 0.0f, 0.0f, 0.0f, // 左下
    1.0f, 1.0f, 0.0f, 1.0f, 1.0f, // 右上
    1.0f, 1.0f, 0.0f, 1.0f, 0.0f // 右下
    )

    val vertexDataBuffer = ByteBuffer.allocateDirect(vertexData.size * 4)
    .order(ByteOrder.nativeOrder())
    .asFloatBuffer()
    .put(vertexData)
    .position(NO_OFFSET)

    val index = shortArrayOf(
    0, 1, 2, // 第一个三角形
    1, 3, 2 // 第二个三角形
    )

    val indexBuffer = ByteBuffer.allocateDirect(index.size * 2)
    .order(ByteOrder.nativeOrder())
    .asShortBuffer()
    .put(index)
    .position(NO_OFFSET)

    // VAO(Vertex Array Object), 顶点数组对象, 用于存储VBO
    private var mVAO = IntArray(1)

    // VBO(Vertex Buffer Object), 顶点缓冲对象,用于存储顶点数据和纹理数据
    private var mVBO = IntArray(1) // 只需要一个VBO

    // IBO(Index Buffer Object), 索引缓冲对象,用于存储顶点索引数据
    private var mIBO = IntArray(1)

    // 纹理ID
    private var mTextureID = IntArray(1)

    // 变换矩阵
    private var mMVPMatrix = FloatArray(16) // 最终变换矩阵
    private val mProjectionMatrix = FloatArray(16) // 投影矩阵
    private val mViewMatrix = FloatArray(16) // 视图矩阵
    private val mModelMatrix = FloatArray(16) // 模型矩阵

    // 视口尺寸
    private var mWidth = 0
    private var mHeight = 0

    private var animator: ValueAnimator? = null

    // ⭐ 变换参数(完整的变换属性)
    var translateX = 0f
    var translateY = 0f
    private var translateZ = 0f
    private var scaleX = 1f
    private var scaleY = 1f
    private var scaleZ = 1f
    private var rotationX = 0f
    private var rotationY = 0f
    private var rotationZ = 0f

    // ==================== 公开 API – 变换接口 ====================

    /**
    * 设置平移
    */

    fun setTranslation(x: Float, y: Float, z: Float = 0f) {
    translateX = x
    translateY = y
    translateZ = z
    computeMVPMatrix()
    }

    /**
    * 获取平移
    */

    fun getTranslation(): Triple<Float, Float, Float> {
    return Triple(translateX, translateY, translateZ)
    }

    /**
    * 设置缩放
    */

    fun setScale(sx: Float, sy: Float = sx, sz: Float = 1f) {
    scaleX = sx
    scaleY = sy
    scaleZ = sz
    computeMVPMatrix()
    }

    /**
    * 获取缩放(返回平均缩放值)
    */

    fun getScale(): Triple<Float, Float, Float> {
    return Triple(scaleX, scaleY, scaleZ)
    }

    /**
    * 设置旋转
    */

    fun setRotation(angleX: Float = 0f, angleY: Float = 0f, angleZ: Float = 0f) {
    rotationX = angleX
    rotationY = angleY
    rotationZ = angleZ
    computeMVPMatrix()
    }

    /**
    * 获取旋转
    */

    fun getRotation(): Triple<Float, Float, Float> {
    return Triple(rotationX, rotationY, rotationZ)
    }

    /**
    * 重置所有变换
    */

    fun reset() {
    animator?.cancel()
    translateX = 0f
    translateY = 0f
    translateZ = 0f
    scaleX = 1f
    scaleY = 1f
    scaleZ = 1f
    rotationX = 0f
    rotationY = 0f
    rotationZ = 0f
    computeMVPMatrix()
    }

    /**
    * 重置平移
    */

    fun resetTranslation() {
    translateX = 0f
    translateY = 0f
    translateZ = 0f
    computeMVPMatrix()
    }

    /**
    * 重置缩放
    */

    fun resetScale() {
    scaleX = 1f
    scaleY = 1f
    scaleZ = 1f
    computeMVPMatrix()
    }

    /**
    * 重置旋转
    */

    fun resetRotation() {
    rotationX = 0f
    rotationY = 0f
    rotationZ = 0f
    computeMVPMatrix()
    }

    // ==================== OpenGL 生命周期 ====================

    /**
    * Surface创建时调用,用于初始化OpenGL资源
    */

    override fun onSurfaceCreated() {
    initTexture()
    initShaderProgram()
    initVertexBuffer()
    resetMatrix()
    }

    /**
    * Surface尺寸变化时调用,用于更新视口
    */

    override fun onSurfaceChanged(width: Int, height: Int) {
    GLES30.glViewport(0, 0, width, height)
    mWidth = width
    mHeight = height
    computeMVPMatrix()
    }

    /**
    * 每帧渲染时调用,执行实际的绘制操作
    */

    override fun onDrawFrame() {
    clearBuffers()
    draw()
    }

    /**
    * Surface销毁时调用,可以标记资源需要重新初始化
    */

    override fun onSurfaceDestroyed() {
    release()
    }

    /**
    * 初始化着色器程序
    */

    private fun initShaderProgram() {
    val vertexShaderCode = """#version 300 es
    uniform mat4 uMVPMatrix; // 变换矩阵
    in vec4 aPosition; // 顶点坐标
    in vec2 aTexCoord; // 纹理坐标
    out vec2 vTexCoord;
    void main() {
    // 输出顶点坐标和纹理坐标到片段着色器
    gl_Position = uMVPMatrix * aPosition;
    vTexCoord = aTexCoord;
    }""".trimIndent()
    val fragmentShaderCode = """
    #version 300 es
    precision mediump float;
    uniform sampler2D uTexture_0;
    in vec2 vTexCoord;
    out vec4 fragColor;
    void main() {
    fragColor = texture(uTexture_0, vTexCoord);
    }""".trimIndent()

    // 加载顶点着色器和片段着色器, 并创建着色器程序
    val vertexShader = OpenGLUtils.loadShader(GLES30.GL_VERTEX_SHADER, vertexShaderCode)
    val fragmentShader = OpenGLUtils.loadShader(GLES30.GL_FRAGMENT_SHADER, fragmentShaderCode)
    mProgram = GLES30.glCreateProgram()
    GLES30.glAttachShader(mProgram, vertexShader)
    GLES30.glAttachShader(mProgram, fragmentShader)
    GLES30.glLinkProgram(mProgram)

    // 删除着色器对象
    GLES30.glDeleteShader(vertexShader)
    GLES30.glDeleteShader(fragmentShader)
    }

    /**
    * 初始化顶点缓冲区
    */

    private fun initVertexBuffer() {
    // 绑定VAO
    GLES30.glGenVertexArrays(mVAO.size, mVAO, NO_OFFSET)
    GLES30.glBindVertexArray(mVAO[0])

    // 绑定VBO – 只需要一个VBO存储所有数据
    GLES30.glGenBuffers(mVBO.size, mVBO, NO_OFFSET)
    GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, mVBO[0])
    GLES30.glBufferData(
    GLES30.GL_ARRAY_BUFFER,
    vertexData.size * 4,
    vertexDataBuffer,
    GLES30.GL_STATIC_DRAW
    )

    // 设置顶点属性指针 – 顶点坐标
    val positionHandle = GLES30.glGetAttribLocation(mProgram, "aPosition")
    GLES30.glEnableVertexAttribArray(positionHandle)
    GLES30.glVertexAttribPointer(
    positionHandle,
    VERTEX_POS_DATA_SIZE,
    GLES30.GL_FLOAT,
    false,
    STRIDE, // 步长,每个顶点5个float (x,y,z,u,v)
    NO_OFFSET // 偏移量,位置数据在前
    )

    // 设置顶点属性指针 – 纹理坐标
    val textureHandle = GLES30.glGetAttribLocation(mProgram, "aTexCoord")
    GLES30.glEnableVertexAttribArray(textureHandle)
    GLES30.glVertexAttribPointer(
    textureHandle,
    TEXTURE_POS_DATA_SIZE,
    GLES30.GL_FLOAT,
    false,
    STRIDE, // 步长,每个顶点5个float (x,y,z,u,v)
    VERTEX_POS_DATA_SIZE * 4 // 偏移量,纹理数据在位置数据之后
    )

    // 绑定IBO
    GLES30.glGenBuffers(mIBO.size, mIBO, NO_OFFSET)
    // 绑定索引缓冲区数据到IBO[0]
    GLES30.glBindBuffer(GLES30.GL_ELEMENT_ARRAY_BUFFER, mIBO[0])
    GLES30.glBufferData(
    GLES30.GL_ELEMENT_ARRAY_BUFFER,
    index.size * 2,
    indexBuffer,
    GLES30.GL_STATIC_DRAW
    )

    // 解绑VAO
    GLES30.glBindVertexArray(0)
    // 解绑VBO和IBO
    GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, 0)
    GLES30.glBindBuffer(GLES30.GL_ELEMENT_ARRAY_BUFFER, 0)
    }

    /**
    * 初始化纹理
    */

    private fun initTexture() {
    val textureId = IntArray(1)
    // 生成纹理
    GLES30.glGenTextures(1, textureId, 0)
    // 绑定纹理
    GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, textureId[0])
    // 设置纹理参数
    GLES30.glTexParameteri(
    GLES30.GL_TEXTURE_2D,
    GLES30.GL_TEXTURE_MIN_FILTER,
    GLES30.GL_LINEAR
    ) // 纹理缩小时使用线性插值
    GLES30.glTexParameteri(
    GLES30.GL_TEXTURE_2D,
    GLES30.GL_TEXTURE_MAG_FILTER,
    GLES30.GL_LINEAR
    ) // 纹理放大时使用线性插值
    GLES30.glTexParameteri(
    GLES30.GL_TEXTURE_2D,
    GLES30.GL_TEXTURE_WRAP_S,
    GLES30.GL_CLAMP_TO_EDGE
    ) // 纹理坐标超出范围时,超出部分使用最边缘像素进行填充
    GLES30.glTexParameteri(
    GLES30.GL_TEXTURE_2D,
    GLES30.GL_TEXTURE_WRAP_T,
    GLES30.GL_CLAMP_TO_EDGE
    ) // 纹理坐标超出范围时,超出部分使用最边缘像素进行填充
    // 加载图片
    val options = BitmapFactory.Options().apply {
    inScaled = false // 不进行缩放
    }
    val bitmap = BitmapFactory.decodeResource(context.resources, R.drawable.picture, options)
    // 将图片数据加载到纹理中
    GLUtils.texImage2D(GLES30.GL_TEXTURE_2D, 0, bitmap, 0)
    // 释放资源
    bitmap.recycle()
    // 解绑纹理
    GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, 0)
    Log.e(
    "yang",
    "loadTexture: 纹理加载成功 bitmap.width:${bitmap.width} bitmap.height:${bitmap.height}"
    )
    mImageWidth = bitmap.width
    mImageHeight = bitmap.height
    mTextureID[0] = textureId[0]
    }
    var mImageHeight = 0
    var mImageWidth = 0

    /**
    * 重置矩阵
    */

    private fun resetMatrix() {
    Matrix.setIdentityM(mProjectionMatrix, NO_OFFSET)
    Matrix.setIdentityM(mViewMatrix, NO_OFFSET)
    Matrix.setIdentityM(mModelMatrix, NO_OFFSET)
    Matrix.setIdentityM(mMVPMatrix, NO_OFFSET)
    }

    /**
    * 计算最终变换矩阵
    */

    private fun computeMVPMatrix() {
    val isLandscape = mWidth > mHeight
    val viewPortRatio =
    if (isLandscape) mWidth.toFloat() / mHeight else mHeight.toFloat() / mWidth

    // 计算包围图片的球半径
    val radius = sqrt(1f + viewPortRatio * viewPortRatio)
    val near = 0.1f
    val far = near + 2 * radius
    val distance = near / (near + radius)

    // 视图矩阵View Matrix
    Matrix.setLookAtM(
    mViewMatrix, NO_OFFSET,
    0f, 0f, near + radius, // 相机位置
    0f, 0f, 0f, // 看向原点
    0f, 1f, 0f // 上方向
    )

    // 投影矩阵Projection Matrix
    Matrix.frustumM(
    mProjectionMatrix, NO_OFFSET,
    if (isLandscape) (viewPortRatio * distance) else (1f * distance), // 左边界
    if (isLandscape) (viewPortRatio * distance) else (1f * distance), // 右边界
    if (isLandscape) (1f * distance) else (viewPortRatio * distance), // 下边界
    if (isLandscape) (1f * distance) else (viewPortRatio * distance), // 上边界
    near, // 近平面
    far // 远平面
    )

    // ⭐ 模型矩阵(完整的变换)
    Matrix.setIdentityM(mModelMatrix, NO_OFFSET)

    // 1. 平移
    Matrix.translateM(mModelMatrix, NO_OFFSET, translateX, translateY, translateZ)

    // 2. 旋转(顺序:X → Y → Z)
    if (rotationX != 0f) {
    Matrix.rotateM(mModelMatrix, NO_OFFSET, rotationX, 1f, 0f, 0f)
    }
    if (rotationY != 0f) {
    Matrix.rotateM(mModelMatrix, NO_OFFSET, rotationY, 0f, 1f, 0f)
    }
    if (rotationZ != 0f) {
    Matrix.rotateM(mModelMatrix, NO_OFFSET, rotationZ, 0f, 0f, 1f)
    }

    // 3. 缩放
    Matrix.scaleM(mModelMatrix, NO_OFFSET, scaleX, scaleY, scaleZ)
    // 最终变换矩阵,第一次变换,模型矩阵 x 视图矩阵 = Model x View, 但是OpenGL ES矩阵乘法是右乘,所以是View x Model
    Matrix.multiplyMM(
    mMVPMatrix,
    NO_OFFSET,
    mViewMatrix,
    NO_OFFSET,
    mModelMatrix,
    NO_OFFSET
    )

    // 最终变换矩阵,第二次变换,模型矩阵 x 视图矩阵 x 投影矩阵 = Model x View x Projection, 但是OpenGL ES矩阵乘法是右乘,所以是Projection x View x Model
    Matrix.multiplyMM(
    mMVPMatrix,
    NO_OFFSET,
    mProjectionMatrix,
    NO_OFFSET,
    mMVPMatrix,
    NO_OFFSET
    )

    // 纹理坐标系为(0, 0), (1, 0), (1, 1), (0, 1)的正方形逆时针坐标系,从Bitmap生成纹理,即像素拷贝到纹理坐标系
    // 变换矩阵需要加上一个y方向的翻转, x方向和z方向不改变
    Matrix.scaleM(
    mMVPMatrix,
    NO_OFFSET,
    1f,
    1f,
    1f,
    )
    }

    /**
    * 清除缓冲区
    */

    private fun clearBuffers() {
    // 清除颜色缓冲区
    GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT)
    }

    /**
    * 绘制图形
    */

    private fun draw() {
    val state = saveGLState()
    try {
    GLES30.glUseProgram(mProgram)
    enableTexture0(mProgram, mTextureID[0])
    // 解析变换矩阵
    val matrixHandle = GLES30.glGetUniformLocation(mProgram, "uMVPMatrix")
    GLES30.glUniformMatrix4fv(matrixHandle, 1, false, mMVPMatrix, NO_OFFSET)

    // 绑定VAO
    GLES30.glBindVertexArray(mVAO[0])
    // 绘制图形
    GLES30.glDrawElements(
    GLES30.GL_TRIANGLES,
    index.size,
    GLES30.GL_UNSIGNED_SHORT,
    NO_OFFSET
    )
    // 解绑VAO
    GLES30.glBindVertexArray(0)
    disableTexture0()
    } finally {
    restoreGLState(state)
    }
    }

    /**
    * 释放资源
    */

    private fun release() {
    // 删除着色器程序、VAO、VBO和纹理等OpenGL资源
    animator?.cancel()
    animator = null

    if (mVAO[0] != 0) {
    GLES30.glDeleteVertexArrays(1, mVAO, 0)
    }
    if (mVBO[0] != 0) {
    GLES30.glDeleteBuffers(1, mVBO, 0)
    }
    if (mIBO[0] != 0) {
    GLES30.glDeleteBuffers(1, mIBO, 0)
    }
    if (mTextureID[0] != 0) {
    GLES30.glDeleteTextures(1, mTextureID, 0)
    }
    if (mProgram != 1) {
    GLES30.glDeleteProgram(mProgram)
    }
    }

    // ==================== 新增:带中心点的缩放 API ====================

    /**
    * 以指定点为中心进行缩放(立即生效,无动画)
    * @param scaleFactor 缩放因子(相对于当前缩放)
    * @param centerX 缩放中心X(屏幕坐标)
    * @param centerY 缩放中心Y(屏幕坐标)
    */

    fun scaleWithCenter(
    scaleFactor: Float,
    centerX: Float,
    centerY: Float,
    requestCallback: () -> Unit
    ) {
    if (mWidth == 0 || mHeight == 0) {
    Log.w("BaseOpenGLData", "View size not set, cannot scale with center")
    return
    }

    // 1. 屏幕坐标 → OpenGL 归一化坐标
    val normalizedX = (centerX / mWidth) * 2f 1f
    val normalizedY = 1f (centerY / mHeight) * 2f

    // 2. 计算缩放前该点在模型空间的位置
    val pointBeforeX = (normalizedX translateX) / scaleX
    val pointBeforeY = (normalizedY translateY) / scaleY

    // 3. 应用新的缩放
    val newScaleX = scaleX * scaleFactor
    val newScaleY = scaleY * scaleFactor

    // 限制缩放范围
    scaleX = newScaleX.coerceIn(MIN_SCALE, MAX_SCALE)
    scaleY = newScaleY.coerceIn(MIN_SCALE, MAX_SCALE)

    // 4. 计算缩放后该点在模型空间的位置
    val pointAfterX = pointBeforeX * scaleX
    val pointAfterY = pointBeforeY * scaleY

    // 5. 调整平移量,使该点保持在原位置
    translateX = normalizedX pointAfterX
    translateY = normalizedY pointAfterY

    // 6. 更新矩阵
    computeMVPMatrix()
    requestCallback()
    }

    /**
    * 以指定点为中心进行缩放动画
    * @param targetScale 目标缩放值(绝对值)
    * @param centerX 缩放中心X(屏幕坐标)
    * @param centerY 缩放中心Y(屏幕坐标)
    * @param duration 动画时长
    */

    fun animateScaleWithCenter(
    targetScale: Float,
    centerX: Float,
    centerY: Float,
    duration: Long = ANIMATION_DURATION,
    requestCallback: () -> Unit
    ) {
    cancelAnimation()
    val startScaleX = scaleX
    val startScaleY = scaleY
    val startTranslateX = translateX
    val startTranslateY = translateY

    // 屏幕坐标 → OpenGL 归一化坐标
    val normalizedX = (centerX / mWidth) * 2f 1f
    val normalizedY = 1f (centerY / mHeight) * 2f

    // 计算缩放前该点在模型空间的位置
    val pointBeforeX = (normalizedX startTranslateX) / startScaleX
    val pointBeforeY = (normalizedY startTranslateY) / startScaleY

    // 限制目标缩放范围
    val clampedTargetScale = targetScale.coerceIn(MIN_SCALE, MAX_SCALE)

    // 计算目标平移量
    val pointAfterX = pointBeforeX * clampedTargetScale
    val pointAfterY = pointBeforeY * clampedTargetScale
    val targetTranslateX = normalizedX pointAfterX
    val targetTranslateY = normalizedY pointAfterY

    animator = ValueAnimator.ofFloat(0f, 1f).apply {
    this.duration = duration
    interpolator = DecelerateInterpolator()

    addUpdateListener { animator ->
    val fraction = animator.animatedValue as Float

    scaleX = startScaleX + (clampedTargetScale startScaleX) * fraction
    scaleY = startScaleY + (clampedTargetScale startScaleY) * fraction
    translateX = startTranslateX + (targetTranslateX startTranslateX) * fraction
    translateY = startTranslateY + (targetTranslateY startTranslateY) * fraction
    computeMVPMatrix()
    requestCallback()
    }

    start()
    }
    }

    /**
    * 重置到初始状态(带动画)
    */

    fun animateReset(
    targetScale: Float = 1f,
    duration: Long = ANIMATION_DURATION,
    requestCallback: () -> Unit
    ) {
    cancelAnimation()
    val startScaleX = scaleX
    val startScaleY = scaleY
    val startTranslateX = translateX
    val startTranslateY = translateY

    animator = ValueAnimator.ofFloat(0f, 1f).apply {
    this.duration = duration
    interpolator = DecelerateInterpolator()

    addUpdateListener { animator ->
    val fraction = animator.animatedValue as Float

    scaleX = startScaleX + (targetScale startScaleX) * fraction
    scaleY = startScaleY + (targetScale startScaleY) * fraction
    translateX = startTranslateX + (0f startTranslateX) * fraction
    translateY = startTranslateY + (0f startTranslateY) * fraction

    computeMVPMatrix()
    requestCallback()
    }

    start()
    }
    }

    fun cancelAnimation(){
    animator?.cancel()
    animator = null
    }
    }

    object OpenGLUtils {

    // OpenGL状态数据类
    data class GLState(
    val viewport: IntArray,
    val program: Int,
    val framebuffer: Int
    )

    // 保存OpenGL状态
    fun saveGLState(): GLState {
    val viewport = IntArray(4)
    val program = IntArray(1)
    val framebuffer = IntArray(1)
    GLES30.glGetIntegerv(GLES30.GL_VIEWPORT, viewport, 0)
    GLES30.glGetIntegerv(GLES30.GL_CURRENT_PROGRAM, program, 0)
    GLES30.glGetIntegerv(GLES30.GL_FRAMEBUFFER_BINDING, framebuffer, 0)
    return GLState(viewport, program[0], framebuffer[0])
    }

    // 恢复OpenGL状态
    fun restoreGLState(state: GLState) {
    GLES30.glViewport(
    state.viewport[0],
    state.viewport[1],
    state.viewport[2],
    state.viewport[3]
    )
    GLES30.glUseProgram(state.program)
    GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, state.framebuffer)
    }

    fun enableTexture0(program: Int, id: Int) {
    GLES30.glActiveTexture(GLES30.GL_TEXTURE0)
    GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, id)
    val textureSampleHandle = GLES30.glGetUniformLocation(program, "uTexture_0")
    if (textureSampleHandle != 1) {
    GLES30.glUniform1i(textureSampleHandle, 0)
    }
    }

    fun disableTexture0() {
    GLES30.glActiveTexture(GLES30.GL_TEXTURE0)
    GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, 0)
    }

    // 创建着色器对象
    fun loadShader(type: Int, source: String): Int {
    val shader = GLES30.glCreateShader(type)
    GLES30.glShaderSource(shader, source)
    GLES30.glCompileShader(shader)
    return shader
    }
    }

    渲染引擎

    渲染引擎接口

    /**
    * OpenGL渲染引擎接口
    */

    interface OpenGLEngine {
    /**
    * SurfaceHolder回调,用于绑定到SurfaceView
    */

    val callback: SurfaceHolder.Callback

    /**
    * 请求执行一次渲染
    */

    fun requestRender(mode : Int? = null)

    /**
    * 释放资源(在 View detach 时调用)
    */

    fun release()
    }

    渲染引擎接口实现

    open class BaseOpenGLEngine(private val renderData: OpenGLData) : OpenGLEngine {
    private var mRenderThread: RenderThread? = null

    override val callback: SurfaceHolder.Callback = object : SurfaceHolder.Callback {
    override fun surfaceCreated(holder: SurfaceHolder) {
    val thread = mRenderThread
    if (thread != null && thread.isAlive) {
    // 复用已有线程,只更新 Surface
    thread.updateSurface(holder.surface)
    } else {
    mRenderThread = RenderThread(holder.surface, renderData).apply {
    start()
    }
    }
    }

    override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {
    mRenderThread?.updateSize(width, height)
    }

    override fun surfaceDestroyed(holder: SurfaceHolder) {
    // 不销毁线程,只暂停渲染
    mRenderThread?.pause()
    }
    }

    override fun requestRender(mode: Int?) {
    mRenderThread?.requestRender(mode)
    }

    override fun release() {
    mRenderThread?.shutdown()
    mRenderThread = null
    }
    }

    渲染线程

    class RenderThread(
    @Volatile private var surface: Surface,
    private val renderData: OpenGLData
    ) : Thread() {
    private val TAG = "RenderThread"

    private var mEGLEnvironment: EGLEnvironmentBuilder.EGLEnvironment? = null

    @Volatile
    private var running = true

    @Volatile
    private var paused = false

    @Volatile
    private var surfaceUpdated = false

    @Volatile
    private var sizeChanged = false

    @Volatile
    private var renderMode = RENDERMODE_WHEN_DIRTY

    @Volatile
    private var requestRender = true
    private var mCacheWidth = 0
    private var mCacheHeight = 0
    private val lock = Object()

    fun updateSurface(newSurface: Surface) {
    synchronized(lock) {
    surface = newSurface
    surfaceUpdated = true
    paused = false
    lock.notifyAll()
    Log.d(TAG, "updateSurface")
    }
    }

    fun pause() {
    synchronized(lock) {
    paused = true
    Log.d(TAG, "pause")
    }
    }

    fun updateSize(width: Int, height: Int) {
    synchronized(lock) {
    if (width <= 0 || height <= 0) return
    mCacheWidth = width
    mCacheHeight = height
    sizeChanged = true
    requestRender = true
    lock.notifyAll()
    Log.d(TAG, "updateSize width = $width, height = $height")
    }
    }

    fun requestRender(mode: Int? = null) {
    synchronized(lock) {
    mode?.let { renderMode = it }
    requestRender = true
    lock.notifyAll()
    Log.d(TAG, "requestRender${mode?.let { " mode = $it" } ?: ""}")
    }
    }

    fun shutdown() {
    synchronized(lock) {
    running = false
    paused = false
    lock.notifyAll()
    Log.d(TAG, "shutdown")
    }
    join()

    renderData.onSurfaceDestroyed()
    mEGLEnvironment?.release()
    }

    override fun run() {
    mEGLEnvironment = EGLEnvironmentBuilder().build(surface)
    renderData.onSurfaceCreated()
    renderLoop()
    }

    private fun renderLoop() {
    while (running) {
    var shouldRender = false
    synchronized(lock) {
    // 等待条件:未暂停且有渲染请求
    while (running && (paused || (!requestRender && !sizeChanged && !surfaceUpdated && renderMode != RENDERMODE_CONTINUOUSLY))) {
    lock.wait()
    }

    if (!running) return

    // 处理 Surface 更新(重建 EGLSurface)
    if (surfaceUpdated) {
    mEGLEnvironment?.updateSurface(surface)
    surfaceUpdated = false
    requestRender = true
    }

    if (!paused) {
    // 处理尺寸变化
    if (sizeChanged) {
    renderData.onSurfaceChanged(mCacheWidth, mCacheHeight)
    sizeChanged = false
    }

    // 处理渲染
    if (requestRender || renderMode == RENDERMODE_CONTINUOUSLY) {
    shouldRender = true
    requestRender = false
    }
    }
    }

    // 渲染放在锁外执行,避免长时间持锁
    if (shouldRender) {
    renderData.onDrawFrame()
    mEGLEnvironment?.swapBuffers()
    }
    }
    }
    }

    activity_main的XML文件

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <!– 预览区Fragment容器 –>
    <FrameLayout
    android:id="@id/preview_fragment_container"
    android:layout_width="match_parent"
    android:layout_height="0dp"
    android:layout_weight="1"
    android:background="#000000">

    <com.example.render.opengl.BaseSurfaceView
    android:layout_width="match_parent"
    android:layout_height="match_parent"/>

    </FrameLayout>

    <!– 操作区Fragment容器 –>
    <FrameLayout
    android:id="@+id/operate_fragment_container"
    android:layout_width="match_parent"
    android:layout_height="230dp"
    android:background="#F5F5F5" />

    </LinearLayout>

    Activity的代码

    class MainActivity : AppCompatActivity() {\\
    override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    }
    }

    SurfaceView的代码

    open class BaseSurfaceView @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null
    ) : SurfaceView(context, attrs) {
    protected val renderData: OpenGLData by lazy { createRenderData() }
    protected val renderEngine: OpenGLEngine by lazy { createRenderEngine() }

    private val touchManager = BaseSurfaceTouchManager()
    private val touchListener = object : BaseSurfaceTouchManager.OnGestureListener {

    override fun onDoubleTap(centerX: Float, centerY: Float) {
    (renderData as? BaseOpenGLData)?.let { data ->
    if (data.getScale().first >= 1.5f) {
    data.animateReset(duration = 200) {
    requestRender()
    }
    } else {
    data.zoomWithAnchor(
    targetScale = 2f,
    centerX = centerX,
    centerY = centerY,
    duration = 200
    ) {
    requestRender()
    }
    }
    }
    }

    override fun onDoubleScale(
    scale: Float,
    centerX: Float,
    centerY: Float
    ) {
    (renderData as? BaseOpenGLData)?.let { data ->
    data.scaleWithCenter(scale, centerX, centerY) {
    requestRender()
    }
    }
    }

    override fun onScaleEnd() {
    (renderData as? BaseOpenGLData)?.let { data ->
    if (data.getScale().first < 1.0f) {
    data.animateReset {
    requestRender()
    }
    }
    }
    }
    }

    init {
    holder.addCallback(renderEngine.callback)
    touchManager.setOnGestureListener(touchListener)
    requestRender(RENDERMODE_WHEN_DIRTY)
    }

    protected open fun createRenderEngine(): OpenGLEngine = BaseOpenGLEngine(renderData)
    protected open fun createRenderData(): OpenGLData = BaseOpenGLData(context)

    fun requestRender(mode: Int? = null) {
    renderEngine.requestRender(mode)
    }

    override fun onTouchEvent(event: MotionEvent): Boolean {
    if (event.actionMasked == MotionEvent.ACTION_DOWN) {
    (renderData as? BaseOpenGLData)?.cancelAnimation()
    }
    val handled = touchManager.onTouchEvent(event)
    return handled || super.onTouchEvent(event)
    }

    override fun onDetachedFromWindow() {
    super.onDetachedFromWindow()
    renderEngine.release()
    }
    }

    效果图

    在这里插入图片描述

    赞(0)
    未经允许不得转载:网硕互联帮助中心 » Open GL ES -> 应用前后台、Recent切换,SurfaceView纹理贴图闪烁问题分析解决
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!