[3.8.7/3.8.8]ImageAsset无法释放,导致微信小游戏内存过高

构建微信小游戏时,同一个 ImageAsset 会通过 _textureSource 和 _mipmaps[0] 被 Texture2D 引用两次,但 _assignImage() 上传完成后只清理一次依赖和引用,导致 ImageAsset 引用计数残留为 1, 就算CLEANUP_IMAGE_CACHE为true也无法释放。
在微信小游戏中,Image 对象会缓存解码后的图片数据,导致内存过高。

而 Preview 中没有生成_textureSource这条额外的构建依赖,对应图片 UUID 在 Texture2D 依赖列表中只有一次,所以这个问题只在构建产物中明显出现。

是由3.8.7的这条改动引起的:https://github.com/cocos/cocos-engine/pull/18569

    Texture2D._textureSource → ImageAsset
    Texture2D._mipmaps[0]    → ImageAsset

资源加载阶段的 setProperties() 会遍历所有依赖,并为每条依赖执行一次:
depend.owner[depend.prop] = dependAsset.addRef();
所以同一个 ImageAsset 被 addRef() 两次。
而SimpleTexture._assignImage() 只查找并删除第一条匹配的 UUID:

if (macro.CLEANUP_IMAGE_CACHE) {
    const deps = dependUtil.getDeps(this._uuid);
    const index = deps.indexOf(image._uuid);
    if (index !== -1) {
        js.array.fastRemoveAt(deps, index);
        image.decRef();
    }
}

这里存在数量不对称:
setProperties(): addRef × 2
_assignImage(): decRef × 1
结果就是:ImageAsset.refCount: 2 → 1,引用计数始终无法归零。

临时修复方案:
修改 _assignImage() 的依赖清理逻辑:不再只删除第一条对应依赖,而是删除当前 Texture2D 依赖列表中该 ImageAsset UUID 的所有条目,并针对每条被删除的依赖调用一次 decRef()

if (macro.CLEANUP_IMAGE_CACHE) {
    const dependencies = assetManager.dependUtil.getDeps(this.uuid);
    let dependencyIndex = dependencies.indexOf(image.uuid);

    // 每删除一条依赖,平衡 setProperties() 中对应的一次 addRef()。
    while (dependencyIndex !== -1) {
        js.array.fastRemoveAt(dependencies, dependencyIndex);
        image.decRef();
        dependencyIndex = dependencies.indexOf(image.uuid);
    }
}
11赞

3.8 这种严重的内存问题,现在才暴露出来?难以想象啊都

1赞

387 改动导致的新 bug 好多,我都不知道那时候他们怎么审核 PR 的 :sweat_smile: 是不是因为快被收购了所以掉以轻心……

@weibo 大佬能让技术组确认下是否真有这个问题吗?

确认存在该问题,已转载给相关开发同学在修复

1赞

感谢指出问题, 并提供详细的问题说明 :smiley:
你的方案很有启发性 :+1:
我们将在以下 pr 中跟进这个 issue
fix: balance Texture2D image dependency references by Changdw · Pull Request #267 · cocos/cocos4

4赞

3.8.9 也能带上吗

那必须上3.8.9 :wink:

1赞

3.8.9 有档期么~~ :sparkling_heart:

太好啦,期待3.8.9

希望3.8.9编辑器的bug能修一修

要是能有个hook的方案就更好了,那么 3.8.8 + 小游戏 + 分离引擎下,直接接入基于原型链hook的脚本,那么现在游戏就能直接升级了

3.8.9的版本又档期吗?