浏览器贴图释放迷思

按我的理解,在 WebGL 环境下,贴图资源的释放存在天生的难点…… 我想和大家分享下我的看法,如果不对欢迎指正。

每个贴图(Image)都需要绑定到显存中(gl.bindTexture)才能渲染,绑定后会占用显存。显存是宝贵的,需要尽可能及时释放(gl.deleteTexture)。
用户可以在 JavaScript 中任意持有某贴图的引用(WebGLTexture),浏览器如果释放了该贴图,之后用户还要再次使用时就会出错。
为了避免出错,浏览器需要判断用户是否仍然持有该贴图。但是主流的 JS 引擎并不采用引用计数,无法自动判断出是引用什么时候为零。只有等到 GC 的时候才知道贴图没有引用了。但是有些游戏内存管理得好的,GC 可能一直不触发。而且出于安全性等原因,浏览器并不允许用户主动执行 GC(类似 Unity 的 UnloadUnusedAssets),所以浏览器最终还是没办法及时的知道该贴图什么时候能安全释放。

这就导致了贴图释放这件事情,完全需要用户来主动校验。因为浏览器都做不到的事情,Creator 也没办法做到。
一个比较合理的解决方法就是,当用户再一次使用一张已经释放了的贴图时,Creator 提供温和的报错信息,引导用户主动复查前期的释放操作。并且根据贴图 url,重新再下载一次,延迟到显存重新绑定好后才进行渲染。

1赞

感觉这样没什么问题的

我们是自己包装了一层做引用计数。。。

挺好的…… 引擎内部可能还是要做引用计数比较好。不过用户层就很这样强制要求了。

如果ccc当初采用retain/release的方式,现在就不会这么难堪了。
做游戏的,用户完全不用考虑内存,完全依赖JS的GC,哪有那么完美的事
初心太大,现在不上不下,扯得蛋疼

把引用计数暴露给 JS 层有点难啊。用户随便引用一个变量,或者错误释放你一个计数,你辛辛苦苦维护的计数就废了。而且引用计数一旦有内存泄露,查找起来也很难界定责任是在用户还是在引擎一方。

嗯,脚本语言的GC是内存管理的一大难点。几乎服务器和客户端都会因为GC的问题而导致内存问题。特别是系统资源的回收。
所以说了这么多,我也不知道有什么好办法:joy_cat:

权衡之下,之前cocos2d-x使用的引用计数比由GC控制的好。虽然引用计数用得不好会引发一些问题,但是开发者的可控性更多了,就拿原生结点释放的问题来说,现在是由JS对象的GC来控制的,这会导致GC的时候,有很明显的卡顿,因为大量的原生结点在此时被释放;另一方面内存也是不可控的,你完全不知道那些释放的结点会在什么时候释放,如果快速的的创建结点和释放结点,会因为GC不及时,内存暴涨,又会因为GC到来时,长时间的卡顿。

再细一点讨论,我们把对象(资源)划分一下,一种是外层对象,另一种是内部对象。

  • 内部对象是真正的系统资源,用引用计数来控制,如果要有缓存机制,可以用LRU cache,即目的是保证这些对象是可精确控制的。当然这样就使得这些对象的使用要比较小心。不过也不用担心,一般外部不必调用它们,只需调用外部对象即可。
  • 外层对象更像是资源的代理,只要持有它,就一定是有效的。外部一般就是操作外层对象,调用外层对象的方法去操作内部对象。当内部对象被回收时,外象对象也可给出温和的错误,而不至于整个出错,或者说外部对象会重新去加载内部对象,而这一些对开发者是透明的。代理对象的好处:一是对外简化系统资源的管理,二是让外部有机会控制系统资源的回收,三是延迟加载,当有需要用到的时候才去加载,或者重新加载。

就当做一点讨论。

1赞

个人觉得这样挺好的,至少能保证画面正确。
我们曾经遇到界面太多导致显存爆掉的问题,解决办法就是:一旦界面关掉,就把涉及到的所有纹理释放掉,再次打开的时候再全部加载。