请问cocos creator 会释放prefab引用的图片吗?

我用 cc.textureCache.dumpCachedTextureInfo(); 输出所有的贴图,发现游戏中图是越来越多的,没有被释放。我所有的资源都是prefab,使用cc.loader.loadRes加载然后用cc.loader.releaseRes释放的,可以确认prefab是被释放的,但是prefab引用的texture我不知道有没有被释放,从dumpCachedTextureInfo看是没有释放的。
那么问题是?
到底是dumpCachedTextureInfo的输出有问题,还是releaseRes不能释放引用的图片呢?

另外textureCache里面没有removeUnusedTextures方法了,以前是可以用这个手动释放的,现在没有了。。

在线等,挺急的

@jare @panda

如果多个prefab引用了同一个texture,是不是这些prefab都被释放之后,texture就自动释放呢?是否需要手动再调用什么方法?我试过cc.loader文档里面的用法:
var deps = cc.loader.getDependsRecursively(prefab);
cc.loader.release(deps);
但如果这样做,只要释放一个prefab,贴图就会被释放,而其他的正在绘制的prefab就会出错了。
难道需要自己管理引用计数? 这难道不是引擎应该有的功能吗?我一直默认引擎是这样工作的,是我哪儿没get到吗?文档也都看了。求助求助!

003.zip (215.8 KB)
https://docs.cocos.com/creator/manual/zh/scripting/load-assets.html?h=releaseres#资源的依赖和释放
首先真正占用内存的贴图等基础资源并不会随着你释放 Prefab 或者 SpriteAtlas 而被释放。
调用releaseres只是把cc.loader中的缓存资源干掉而已
cc.textureCache.dumpCachedTextureInfo() 输出的项目中的纹理内存总量,不会被releaseres掉
在1.10.2中可以使用cc.textureCache.removeTextureForKey手动从内存中删除资源占用,这样做会导致本地资源目录下的资源不能再被正确引用,所以必须明确你不在使用该资源了才这样做(该方法在2.0中移除)

谢谢!明白了。也就是说cocos creator使用的贴图是不断增加的,不会被释放掉? 除非使用cc.textureCache.removeTextureForKey手动释放。在2.0中,这个方法也被移除了,那么就是说图片内存只能不断增加,没有释放的办法了?
那么内存暴了怎么办呢?
还是说引擎会在内存爆掉之前释放贴图?

2赞

这是两个东西。
纹理总量相当于你有多少钱,用了多少钱买了多少东西相当于游戏运行时内存(缓存)。
只有你真的不需要钱了你才会把钱减少,但是这不会导致你的东西被消化掉。
不会的 你比说你生成了100个helloworld的图片,你的纹理总量也不会一直提高。
游戏运行时大部分内存占用来自于cc.loader的缓存,针对它去做释放
或者对资源进行有效管理。压缩、批处理,也是提高游戏性能的方法
内存释放是有延时的

1赞

如果我有多个不同的prefab引用的同一张texture,webgl为了绘制这个texture,需要把它上传到显存,当所有引用这张texture的prefab使用完释放掉,需要从显存删除这个texture。
我想这个应该是非常正常的需求。
我不懂你说的钱的比喻。。我只知道显存里面的texture如果不释放,越来越多,只会挂掉。
如果连手动释放的接口都去了,我不知道怎么能让显存不爆掉

ps: 你可以算一算,一张1024*1024 32bit的贴图,如果不使用压缩纹理,在显存里面要占4M,如果这样的不同的图有100张,就是400M。可能游戏同时不会使用这么多图,但是切换关卡,换装,不同的敌人等等都会不断载入新的图片,对于内容比较多的游戏,玩一段时间后如果不释放,可想而知得需要多少显存啊。我觉得对于游戏来说,图片占的内存是主要的,这应该是常识吧

你试一试就知道了

我们当然是测试过的呢,内存一直不降低,然后才发现图片没释放。
好了,我知道了,我自己管理,至于2.0不升也罢

我自己实现了一个对图片的引用计数,可以正常释放,但是当有新的prefab载入后通过引用载入之前被释放的图片时,该图片是一个不可用的对象:

贴一下我管理图片引用的代码:
let ngTextureCache = {};

let TextureRes = function(textureUrl, texture){
this.textureUrl = textureUrl;
this.texture = texture;
this.refCount = 1;
cc.log(‘new texture’, this.textureUrl, this.refCount);
};

TextureRes.prototype.retain = function(){
this.refCount++;
cc.log(‘retain texture’, this.textureUrl, this.refCount);
};

TextureRes.prototype.release = function(){
this.refCount–;
if(this.refCount == 0){
//cc.textureCache.removeTexture(this.texture);
this.texture.destroy();
//cc.textureCache.removeTextureForKey(this.textureUrl);

}
cc.log('release texture', this.textureUrl, this.refCount);

};

ngTextureCache.textures = {};

ngTextureCache.retainTexture = function (textureUrl, texture) {
let textureRes = ngTextureCache.textures[textureUrl];
if(textureRes!==undefined){
textureRes.retain();
} else {
textureRes = new TextureRes(textureUrl, texture);
ngTextureCache.textures[textureUrl] = textureRes;
}
};

ngTextureCache.releaseTexture = function (textureUrl) {
let textureRes = ngTextureCache.textures[textureUrl];
if(textureRes!==undefined){
textureRes.release();
if(textureRes.refCount==0){
delete ngTextureCache.textures[textureUrl];
}
}
};

ngTextureCache.retainTexturesForPrefab = function(prefabPath){
let deps = cc.loader.getDependsRecursively(prefabPath);
for (let i = 0; i < deps.length; ++i) {
let item = cc.loader.getRes(deps[i]);
if (item instanceof cc.Texture2D) {
if(item.url==’’){
cc.error(‘bad item’,item);
}
ngTextureCache.retainTexture(item.url, item);
}
}
};

ngTextureCache.releaseTexturesForPrefab = function(prefabPath){
let deps = cc.loader.getDependsRecursively(prefabPath);
for (let i = 0; i < deps.length; ++i) {
let item = cc.loader.getRes(deps[i]);
if (item instanceof cc.Texture2D) {
ngTextureCache.releaseTexture(item.url);
}
}
};

1赞

不好意思,之前是我误会了。我使用同一张texture去做测试,与关键点相违背了。
在2.0中我们可以这样做
this.texture = testNode.getComponent(cc.Sprite).spriteFrame._texture;
this.texture.destroy();

    /**
     * !#en
     * Destory this texture and immediately release its video memory. (Inherit from cc.Object.destroy)<br>
     * After destroy, this object is not usable any more.
     * You can use cc.isValid(obj) to check whether the object is destroyed before accessing it.
     * !#zh
     * 销毁该贴图,并立即释放它对应的显存。(继承自 cc.Object.destroy)<br/>
     * 销毁后,该对象不再可用。您可以在访问对象之前使用 cc.isValid(obj) 来检查对象是否已被销毁。
     * @method destroy
     * @return {Boolean} inherit from the CCObject
     */
    destroy () {
        this._image = null;
        this._texture && this._texture.destroy();
        // TODO cc.textureUtil ?
        // cc.textureCache.removeTextureForKey(this.url);  // item.rawUrl || item.url
        this._super();
    },
1赞

是的 如果释放了这个texture对象,将不再可用。可以在访问对象之前使用 cc.isValid(obj) 来检查对象是否已被销毁。

mark

问题是我切换不同的关卡,需要使用不同的贴图,肯定是需要图片释放后然后过一段时间再载入,现在如果释放后就不可以再用,那就不能释放了。请问有什么解决方法吗?我现在用的是1.10.2

ps: 如果Texture2D对象被释放肯定是不能再用的,因为对象被销毁了嘛。现在的问题是对于同一张图片,之前的Texture2D对象被释放了,然后过一段时间再载入同一张图片,生成了一个新的Texture2D对象,这个对象是不可用的。
因为我没有直接去载入图片生成Textue2D对象,我是通过cc.loader.loadRes去载入prefab, prefab引用了图片,当prefab被载入成功时,发现它所依赖的Texture2D是不可用的,而不可用的Texture2D正是之前被释放过的同一张图片

Prefab 并不是直接引用了 Texture2D,而是通过了 SpriteFrame 引用的 Texture2D。因此当你释放了 Texture2D 时,旧的 SpriteFrame 仍然会引用到旧的纹理,导致纹理不可用。

正确的做法是把 SpriteFrame 也一起释放掉。这样下次加载 Prefab 时,就会重新加载 SpriteFrame,而不是仍然取的原有 SpriteFrame 的缓存。

1赞

请问SpriteFrame是否在某个地方有缓存呢?貌似现在没有cc.spriteFrameCache了。
如果SpriteFrame是在prefab内部的,prefab释放了SpriteFrame也应该释放了吧?我其实找出Prefab引用的Texture,也就是Prefab里面所有SpriteFrame引用的图片。当没有prefab引用某个图片时,释放这个图片,那也就说明引用这些图片的Prefab已经都释放了,包括里面的SpriteFrame.

现在的情况看起来是SpriteFrame在引擎某个地方有缓存,重新载入SpriteFrame的时候直接取的缓存,这样SpriteFrame指向的Texture就是被释放过的就不对了。
那么如果是这种情况我应该怎么释放掉被缓存的SpriteFrame呢?谢谢!

所有资源都在 cc.loader 中缓存,例如 prefab、texture、sprite frame,释放这些资源的方法都是统一的。cc.loader.getDependsRecursively 也能获得 prefab 依赖的 sprite frame

明白了,我刚才试了一下直接通过getDependsRecursively 获取到prefab依赖的SpriteFrame,然后使用cc.loader.releaseAsset释放,场景里节点引用的SpriteFrame就都出错了。说明他们是共享的SpriteFrame。看来我也要对SpriteFrame进行引用计数然后释放,我试试看~

谢谢,基本差不多了~

对于随场景加载的静态资源,也得去管理一下,保证引用正确,就可以正确释放SpriteFrame和Texture2D了

现在有个问题是我们的一个prefab上面挂了 sp.Skeleton组件,通过getDependsRecursively 没有找到SprieFrame,但是能找到Texture2D,如果释放了Texture2D,下次再载入这个prefab就不对了。
我想知道spine组件里面,引用Texture2D的是什么呢?是否也可以用getDependsRecursively获取?

谢谢~

是 SpineAtlas 对象,也可以用同样方法获取到的