Skip to content

Scene environment and scene background are leaking after deep clean #30516

Closed
@catalin-enache

Description

@catalin-enache

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions