iOS 中 OpenGL ES 纹理的显存占用

691
原文链接: zhuanlan.zhihu.com

问题

最近调试一个 OpenGL ES 问题,最终发现是临时纹理太多。

开发 iOS App 时,Xcode 带有工具测量内存占用和泄露。但应该如何测量 OpenGL ES 纹理占多少显存,是否有纹理泄露呢?GL 活在另一个世界,并非 CPU 和内存,而是 GPU 和显存。

我搜索网页,也试验 Instruments 带的测量模块。但遗憾的是,我找不到方法来直接测量显存占用,但似乎有个间接方法,我不能十分确定,只好用“似乎”这个模糊字眼。

记录

下面记录我试验的过程,使用 iPhone 7+ 测试,iOS 系统 11.3.1。

Xcode 自带 Memory 测量,这个图应该很熟悉了。

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, 使得系统真正分配显存。实际结果是,

  1. 运行一段时间后,触发了 GL_CHECK_ERROR 中的 assert,证实 glTexImage2D 创建纹理失败。
  2. 假如注释掉 assert, App 可以继续运行不崩溃,但是出现花屏现象。
  3. 就算 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。

VM Tracker

注意到其中的 IOKit(IOKit 是个驱动模块)。假如分配了纹理不释放,IOKit 的 Virtual Size 就会不断增长,当值为 2.4G 左右,iPhone 7+ 就开始出现花屏。但假如纹理正确释放,IOKit 的 Virtual Size 就会比较稳定。因而 IOKit 的 Virtual Size 可以作为纹理的间接测量值。

总结

  1. Xcode 带的 Memory 测量工具,Memory Use 数值并不包括纹理显存。
  2. 在 iOS 11.3.1,iPhone 7+(其它版本和机型没有试验)glTexImage2D 最后参数为 OF_NULL,iOS 系统会做优化,延迟分配显存。
  3. 创建一定数量的纹理后,到达了极限值,之后创建纹理就会直接失败。App 可能不会崩溃,但会出现异常现象,比如花屏。
  4. 显存可能会被映射到某虚拟内存。VM Tracker 工具的 IOKit 的 Virtual Size 可以作为纹理显存的间接测量值。假如一直增长,很可能出现纹理等 GPU 资源泄露。