问题
最近调试一个 OpenGL ES 问题,最终发现是临时纹理太多。
开发 iOS App 时,Xcode 带有工具测量内存占用和泄露。但应该如何测量 OpenGL ES 纹理占多少显存,是否有纹理泄露呢?GL 活在另一个世界,并非 CPU 和内存,而是 GPU 和显存。
我搜索网页,也试验 Instruments 带的测量模块。但遗憾的是,我找不到方法来直接测量显存占用,但似乎有个间接方法,我不能十分确定,只好用“似乎”这个模糊字眼。
记录
下面记录我试验的过程,使用 iPhone 7+ 测试,iOS 系统 11.3.1。
Xcode 自带 Memory 测量,这个图应该很熟悉了。
于是自然产生一个问题,这个 Memory Use 是否包括纹理显存?写代码试验。
local tex = context:createTexture(1024, 1024)
print(tex)
我们为了方便编写效果,已经导出了一批 lua Api。上面的 lua 代码创建了一个纹理不释放,每帧跑一次。纹理是 1024 * 1024, RGBA 格式,于是占用 4M。假如 Memory Use 统计了纹理显存,应该可以看到数值会飞快地涨。
实际结果 Memory Use 的数值增长很缓慢。
那是否就证实 Memory Use 不包括纹理显存呢?还不能立即下结论。假如每次泄露 4M,按道理 App 应该很快就崩溃掉,或者出现某些异常。但我注意到 App 一直运行正常,而 Windows 7 运行上述代码,很快就弄崩溃程序。
于是有猜测 1:在 iOS 上创建一定数量的纹理,到达了极限值后,之后创建纹理就会失败,不再泄露显存,因而 App 也不会崩溃。
基于上述猜测,在对应的 C++ 代码中加检测:
glTexImage2D(d._target, 0, format, width, height, 0, format, GL_UNSIGNED_BYTE, OF_NULL);
GL_CHECK_ERROR();
GL_CHECK_ERROR
的定义为
#if GL_DEBUG
#define GL_CHECK_ERROR() gl::checkGLError(__FILE__, __LINE__);
#else
#define GL_CHECK_ERROR()
#endif
void checkGLError(const char* file, int line) {
GLenum error = glGetError();
if (error) {
// xxxx
printf("%d\n", (int)error);
assert(false);
}
}
假如 glTexImage2D 创建纹理失败,GL_CHECK_ERROR
会触发 assert。
继续测试,实际结果是 App 一直稳定正常运行,没有触发 assert,这代表 glTexImage2D 是成功。但注意到 glTexImage2D 最后参数传的是 OF_NULL。
于是有猜测 2:假如 glTexImage2D 最后参数为 OF_NULL,iOS 系统会延迟分配显存,这样占用的资源会很少。因而纹理一直创建成功,没有触发 assert。
基于猜测 2,修改 lua 代码为:
local tex = context:createTexture(1024, 1024)
context:copyTexture(inTex, tex:toOFTexture())
context:copyTexture(tex:toOFTexture(), outTex)
这段代码,强迫真正使用 tex, 使得系统真正分配显存。实际结果是,
- 运行一段时间后,触发了
GL_CHECK_ERROR
中的 assert,证实 glTexImage2D 创建纹理失败。 - 假如注释掉 assert, App 可以继续运行不崩溃,但是出现花屏现象。
- 就算 App 已经花屏了,Memory Use 还是增长缓慢,显示数字为 67.8 MB。
于是我们可以证实,Memory Use 并不包括纹理显存。
VM Tracker
我继续一个个试验 Instruments 带的测量工具。Allocations, Leaks 工具只针对内存。GPU Hardware 可以统计 Framet Shader 或 Vertex Shader 的执行时间。Graphics Driver Activity 可以统计 Shader 编译时间,一些驱动事件。但都不支持测量显存。
试验到有个工具 VM Tracker, 可以统计 Virtual Size。
注意到其中的 IOKit(IOKit 是个驱动模块)。假如分配了纹理不释放,IOKit 的 Virtual Size 就会不断增长,当值为 2.4G 左右,iPhone 7+ 就开始出现花屏。但假如纹理正确释放,IOKit 的 Virtual Size 就会比较稳定。因而 IOKit 的 Virtual Size 可以作为纹理的间接测量值。
总结
- Xcode 带的 Memory 测量工具,Memory Use 数值并不包括纹理显存。
- 在 iOS 11.3.1,iPhone 7+(其它版本和机型没有试验)glTexImage2D 最后参数为 OF_NULL,iOS 系统会做优化,延迟分配显存。
- 创建一定数量的纹理后,到达了极限值,之后创建纹理就会直接失败。App 可能不会崩溃,但会出现异常现象,比如花屏。
- 显存可能会被映射到某虚拟内存。VM Tracker 工具的 IOKit 的 Virtual Size 可以作为纹理显存的间接测量值。假如一直增长,很可能出现纹理等 GPU 资源泄露。