你的这个思路非常棒!这就是所谓的 “延迟销毁 (Lazy/Deferred Destruction)” 或者 “Surface 缓存” 策略。
这在很多高性能渲染引擎(如游戏、地图SDK)中是常见的做法。
你的方案分析
核心逻辑:
- **切后台 (
surfaceDestroyed)**:- 停止渲染 (
swapBuffers)。 - 不销毁
EGLSurface。 - 不销毁
EGLContext。 - 线程进入等待。
- 停止渲染 (
- **切回前台 (
surfaceCreated)**:- 检测到已经存在旧的
EGLSurface。 - 销毁旧的
EGLSurface(因为它绑定的 NativeWindow 已经失效了)。 - 使用新的
Surface创建新的EGLSurface。 - 恢复渲染。
- 检测到已经存在旧的
- **彻底退出 (
quit)**:- 此时
EGLSurface还在(虽然底层的 Window 没了)。 - 尝试
makeCurrent。 - 调用
nativeDestroy(释放 C++ 资源)。 - 最后销毁 Surface 和 Context。
- 此时
这样做的好处:
- 解决了 Context 丢失问题:Context 一直活着,纹理不需要重新加载。
- 解决了
nativeDestroy的环境问题:在退出时,因为EGLSurface对象还在,调用eglMakeCurrent通常是成功的(取决于 GPU 驱动实现),这让你可以合法地调用glDelete*。 - 无缝切换:如果是屏幕旋转(导致 Surface 销毁重建),这种方式切换速度最快。
修改后的代码实现 (支持延迟释放)
我们需要修改 CustomGLRenderThread 的逻辑。
1. 状态控制变量
我们需要区分“是否应该渲染”和“是否持有 Surface”。
1 | // 只有当持有有效的 Android Window 时,才为 true,才能 swapBuffers |
2. 线程主循环 (Run Method)
1 | override fun run() { |
3. 外部回调的配合 (MyGLSurfaceView)
View 层的逻辑变得更简单了,只需要控制开关。
1 | // 外部调用:当 Surface 创建/重建 |
这个方案的唯一风险点
你需要注意一个底层的技术细节:**EGL_BAD_NATIVE_WINDOW**。
当你调用 onSurfaceDestroyed 时,Android 系统会销毁底层的 Window BufferQueue。
在你的方案中,EGLSurface 还活着,但它背后的 Window 死了。
在 quit() 阶段:
1 | if (hasEglSurface) { |
- **大多数 GPU 驱动 (Adreno, Mali)**:允许这么做。只要你不调用
swapBuffers,仅仅是makeCurrent和glDeleteTextures是可以的。 - 少数严格的驱动:可能会在
makeCurrent时检测到底层 Window 已失效,返回EGL_FALSE。
解决办法:
在 nativeDestroy 的 C++ 代码中,以及 Java 调用处,要做好容错。
即:如果 makeCurrent 失败了,也要强行调用 nativeDestroy 来释放 C++ 的 std::vector 等内存,只是跳过 glDelete 而已(反正 Context 马上要被销毁了,显存会自动回收,不会造成永久泄漏)。
总结
你的“延迟释放”思路是完全可行且推荐的。
- 暂停时:保留
EGLSurface,停止Swap,线程休眠。 - 恢复时:对比 Surface 对象,不同则
Release Old->Create New。 - 退出时:利用残留的
EGLSurface恢复环境进行清理。
这比每次都销毁重建要优雅得多,尤其是在处理屏幕旋转等配置变更时。