GLTFLoader crashing on iPad

memory-leak

#1

Hi all, I am trying to load a sizable model (10-15MB) on an ipad but the browser keeps crashing. I got it to load and it works ok, but it is meant to be some sort of configurator website so if a user clicks a button another model should be loaded. I achieved this by telling loader to load another .gltf file because the actual 3d model stays the same (.bin), only the colors change. So I have for example blue.gltf, green.gltf, yellow.gltf and red.gltf and they are all attached to model.bin. I remove previous object from the scene before loading the second one by calling scene.remove(OBJECT_NAME); and it removes from scene because I can see it disappears from the screen until the second gltf file is loaded, but after a couple of color switches the page crashes. Can anyone please help?

Here is my code:

if ( WEBGL.isWebGLAvailable() === false ) {
    document.body.appendChild( WEBGL.getWebGLErrorMessage() );
}

var button = document.getElementById('button');
var loader = new THREE.GLTFLoader().setPath( 'assets/3d/models/' );
var textureLoader = new THREE.TextureLoader();

var urls = [ 'px.png', 'nx.png', 'py.png', 'ny.png', 'pz.png', 'nz.png' ];
var loader2 = new THREE.CubeTextureLoader().setPath( 'assets/3d/background/' );
var background = loader2.load( urls );
var urls = [ 'px2.png', 'nx2.png', 'py2.png', 'ny2.png', 'pz2.png', 'nz2.png' ];
var loader3 = new THREE.CubeTextureLoader().setPath( 'assets/3d/background/' );
var background2 = loader3.load( urls );

var container, stats, controls;
var camera, scene, renderer, light;
init(1);
animate();
function init(texture) {
    container = document.createElement( 'div' );
    document.body.appendChild( container );
    camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 0.25, 200 );
    camera.position.set( 0.0, 0.0, 20.0 );
    controls = new THREE.OrbitControls( camera );
    controls.target.set( 0, - 0.2, - 0.2 );
    controls.update();

    scene = new THREE.Scene();
    light = new THREE.AmbientLight( 0x404040, 10 );
    light.position.set( 0, 1, 0 );
    scene.background = background;
    scene.add( light );

    // model
    loader.load( 'stellar-blue.gltf', function ( data ) {
        data.scene.traverse( function ( child ) {
            if ( child.isMesh ) {
                child.material.envMap = background2;
            }
        } );

        var object = data.scene;
        object.position.set(0, -6, 0);

        object.name = 'test_name';

        scene.add( data.scene );
    }, undefined, function ( e ) {
        console.error( e );
    } );
    renderer = new THREE.WebGLRenderer( { antialias: true, alpha: true } );
    renderer.setPixelRatio( window.devicePixelRatio );
    renderer.setSize( window.innerWidth, window.innerHeight );
    renderer.gammaOutput = true;
    renderer.setClearColor(0x000000, 0);

    var objTo = document.getElementById('app');
    objTo.appendChild( renderer.domElement );

    window.addEventListener( 'resize', onWindowResize, false );
}

function onWindowResize() {
    camera.aspect = window.innerWidth / window.innerHeight;
    camera.updateProjectionMatrix();
    renderer.setSize( window.innerWidth, window.innerHeight );
}

function animate() {
    requestAnimationFrame( animate );
    renderer.render( scene, camera );
}

$(window).on('load', function(event) {
    $( '.color-dot' ).on( 'click', function(e) {
        e.preventDefault();
        e.stopPropagation();

        var prev_object = scene.getObjectByName('test_name');
        scene.remove(prev_object);

        var color_name = $(this).data('color-name');
        var color_slug = $(this).data('color-slug');

        $(this).parents('.device-info').find($('.device-color-name')).html(color_name);

        // $('.color-container').removeClass('color-selected');
        $(this).parent().siblings().removeClass('color-selected');

        $(this).parent().addClass('color-selected');
        $(this).parents('.device-info').addClass('selection-active');

        renderer.renderLists.dispose();

        loader = new THREE.GLTFLoader().setPath( 'assets/3d/models/' );

        loader.load( color_slug+'.gltf', function ( data ) {
            data.scene.traverse( function ( child ) {
                if ( child.isMesh ) {
                    child.material.envMap = background2;
                }
            } );

            var object = data.scene;
            object.position.set(0, -6, 0);

            object.name = 'test_name';

            scene.add( object );
        }, undefined, function ( e ) {
            console.error( e );
        } );
    });
});

#2

After this line, please try to call .dispose() on the material and geometry of the previous object and see if things get better.

This call should not be necessary since you are going to replace objects in your scene.


#3

You mean like:

prev_object.geometry.dispose();
prev_object.material.dispose();

It doesn’t work, it just stops working and no other js code after that gets executed. I am guessing because prev_object geometry and material don’t exist so it fails to call .dispose() on null/undefined?

I ended up going with mrdoob’s answer to this question (https://stackoverflow.com/questions/14650716/deallocating-object3d) which iterates through all of the children of the object and disposes geometry and materials, and I can see that it disposes and iterates correctly, but it didn’t help. If I understand correctly it is because of js garbage collector? Even if I disposed of these geometries and materials it stays in memory until garbage collector finds out there is no reference to it anymore, and the memory fills up faster than garbage collector can remove things :frowning:


#4

What do you mean with “stops working”? Do have a runtime error? Do you see any errors or warnings in the browser console?

In general, providing a link to your app would make it easier to see if you dispose objects correctly.


#6

Believe me, I’d love to share the link to the actual thing I am developing but unfortunately I am not able to share the actual application link or anything else related to the nature of this project as this is for a client and the company I work for is under NDA.

I did the exact thing you advised me to do, disposed geometry and material after scene.remove(prev_object), but calling dispose like prev_object.geometry.dispose() doesn’t work because prev_object doesn’t have geometry, it’s children have geometry. So doing that iteration through all of the children (from mrdoob’s answer on the SO link) and disposing their geometry and materials worked, but it still crashes on iPad.

I might have worded that “stops working” part incorrectly, it doesn’t stop working, it’s just that it runs into an error because I tried to call .dispose() on null/undefined (because prev_object doesn’t have geometry and material, but it’s children have) and any js code after that .dispose() line didn’t work. If I put console.log before and after that line the first console.log would print out, and the second one wouldn’t.


#7

Okay, I see. I was not aware that prev_object has children. And yes, iterating through the objects and calling .dispose() should actually improve the situation.

Can you please check the renderer.info object and report how many geometries and shader programs are internally cached in your application? This information might be valuable in order to identify a memory leak.