一 现象
CocosCreator3.x引擎的场景中开启阴影后,例如阴影贴图使用的是2048*2048,则每一次切换场景都会引起36M(贴图16M+深度20M)的内存泄漏,而且是必现。
这个BUG在3.x引擎一直存在,从3.6开始才没有这个问题,但没有找到专门针对这个BUG的fix说明。因为项目用的是3.4.2引擎,不想升级引擎,只能自己动手修改引擎源码来解决了。
二 原因分析
在ShadowFlow中有个名为shadowFrameBufferMap
的Map对象,Map对象存放着以light为key,值为frameBuffer的对象,frameBuffer创建了阴影和深度共两张纹理。每次切换场景会从 shadowFrameBufferMap 中检索light是否已存在,如果不存在则新创建一个frameBuffer。
现在不知问题在哪,切换场景后light对象属性被修改,导致检索不到,则每次都创建一个新的frameBuffer,也即创建两张新纹理,所以shadowFrameBufferMap
越来越大,并且原来的纹理没有释放。
三 临时修复方案
因为每次切换场景时,都会调用 _detachFromScene 来移除light,所以就在这里根据旧light
,从 shadowFrameBufferMap 中找到 frameBuffer 来释放,反复测试后没再出现内存泄漏问题。
修改Creator3.4.2版本的引擎源码如下。
在 core/pipeline/shadow/shadow-flow.ts 中增加一个方法:
public destroyFrameBufferByLight(light: Light) {
if (!light || !this._pipeline || !this._pipeline.pipelineSceneData) {
return;
}
const shadowFrameBufferMap = this._pipeline.pipelineSceneData.shadowFrameBufferMap;
const frameBuffer = shadowFrameBufferMap.get(light);
if (!frameBuffer) {
return;
}
const renderTargets = frameBuffer.colorTextures;
for (let i = 0; i < renderTargets.length; i++) {
const renderTarget = renderTargets[i];
if (renderTarget) {
renderTarget.destroy();
}
}
renderTargets.length = 0;
const depth = frameBuffer.depthStencilTexture;
if (depth) {
depth.destroy();
}
frameBuffer.destroy();
shadowFrameBufferMap.delete(light);
}
在 render-scene.ts 文件开头,增加:
import { ShadowFlow } from '../../pipeline/shadow/shadow-flow';
在 removeDirectionalLight 函数开头处,增加:
public removeDirectionalLight (dl: DirectionalLight) {
if (this._root && this._root.pipeline && this._root.pipeline.flows) {
let shadowFlow = this._root.pipeline.flows[0];
if (shadowFlow && shadowFlow instanceof ShadowFlow) {
shadowFlow.destroyFrameBufferByLight(dl);
}
}
// ...
}
说明:场景只用了 DirectionalLight ,所以只处理了 DirectionalLight,其它类型的灯光仅供参考。