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 已可见,造成闪烁。
正确做法是:
这个方案类似于 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()
}
}
效果图

网硕互联帮助中心







评论前必须登录!
注册