Description
Description
After deep cleaning the scene, the render info still reports geometries and textures depending on the scenario.
It looks like while traversing the scene we cannot reach certain geometries and textures so that we can dispose them.
Reproduction steps
When no scene.background and no scene.environment, all geometries and textures from scene meshes are disposed properly.
E.g. when using a custom mesh or GLTF without scene.background and scene.environments the scene is cleaned up properly.
When FBX + scene.environment all geometries and textures from scene meshes are disposed properly.
Background issue:
When using only the scene.background (not any meshes in the scene) it is not disposed after scene.background.dispose(); scene.background = null;
Environment issue:
When scene.environment is added in conjunction with custom mesh or GLTF, geometries and textures are still reported after cleaning up the scene.
Strange fact: When scene.environment is added in conjunction with FBX, geometries and textures are zeroed out after cleaning up the scene, which is expected behaviour whatever the scenario would be.
Note:
even if not setting the loaded texture on scene.environment but setting it directly to the mesh.texture.envMap
it triggers the same leaking behaviour after deepClean.
Tested with UltraHDRLoader, RGBELoader, and locally with CubeTexture.
The behaviour is the same, which looks like not being a loader issue.
Code
This is the function used to deep clean the scene.
const deepCleanScene = (scene) => {
const geometriesSet = new Set();
const materialsSet = new Set();
const texturesSet = new Set();
const skeletonsSet = new Set();
const disposableSet = new Set();
const renderTargetsSet = new Set();
scene.traverse((descendant) => {
if (descendant instanceof THREE.Scene) {
if (descendant.background instanceof THREE.Texture) {
console.log('disposing of scene.background');
descendant.background.dispose();
descendant.background = null;
}
if (descendant.environment instanceof THREE.Texture) {
console.log('disposing of scene.environment');
descendant.environment.dispose();
descendant.environment = null;
}
}
if ('dispose' in descendant) {
disposableSet.add(descendant);
}
if (descendant.isLight && descendant.shadow && descendant.shadow.map) {
renderTargetsSet.add(descendant.shadow.map);
}
if (descendant.renderTarget) {
renderTargetsSet.add(descendant.renderTarget);
const _texture = descendant.renderTarget.texture;
const _textures = descendant.renderTarget.textures;
const texture = !_texture ? [] : Array.isArray(_texture) ? _texture : [_texture];
const textures = !_textures ? [] : Array.isArray(_textures) ? _textures : [_textures];
const allTextures = [...texture, ...textures];
allTextures.forEach((tex) => {
texturesSet.add(tex);
});
}
if (descendant.material) {
const materials = Array.isArray(descendant.material)
? descendant.material
: [descendant.material];
materials.forEach((mat) => {
materialsSet.add(mat);
Object.keys(mat).forEach((key) => {
if (mat[key] instanceof THREE.Texture) {
texturesSet.add(mat[key]);
mat[key] = null;
}
});
if (mat.uniforms) {
console.log('Cleaning up material found uniforms', { uniforms: mat.uniforms, obj: descendant });
for (const value of Object.values(mat.uniforms)) {
if (value) {
const uniformValues = Array.isArray(value.value) ? value.value : [value.value];
uniformValues.forEach((uniformValue) => {
if (uniformValue instanceof THREE.Texture) {
console.log('Cleaning up material disposing uniformValue as Texture', {
obj: descendant,
uniformValue
});
texturesSet.add(uniformValue);
}
});
}
}
}
// mat.needsUpdate = true;
});
}
if (descendant.geometry) {
geometriesSet.add(descendant.geometry);
}
if (descendant.skeleton) {
skeletonsSet.add(descendant.skeleton);
}
if (descendant.skeleton && descendant.skeleton.boneTexture) {
texturesSet.add(descendant.skeleton.boneTexture);
}
});
console.log('Disposing of', {
geometries: geometriesSet.size,
materials: materialsSet.size,
textures: texturesSet.size,
skeletons: skeletonsSet.size,
disposables: disposableSet.size,
renderTargets: renderTargetsSet.size
});
const childrenToRemove = [...scene.children];
childrenToRemove.forEach((child) => {
child.removeFromParent();
});
geometriesSet.forEach((geometry) => {
geometry.dispose();
});
geometriesSet.clear();
texturesSet.forEach((texture) => {
if (texture instanceof ImageBitmap) {
texture.close();
}
texture.dispose();
});
texturesSet.clear();
materialsSet.forEach((material) => {
material.dispose();
});
materialsSet.clear();
disposableSet.forEach((disposable) => {
disposable.dispose();
});
disposableSet.clear();
skeletonsSet.forEach((skeleton) => {
skeleton.dispose();
});
skeletonsSet.clear();
renderTargetsSet.forEach((renderTarget) => {
renderTarget.dispose();
});
renderTargetsSet.clear();
};
Live example
https://jsfiddle.net/catalin_enache/Lc319amo/433/
There are some flags in the fiddle to play with in order to trigger some scenario from the described scenarios
Screenshots
No response
Version
0.173.0
Device
Desktop
Browser
Chrome
OS
MacOS