steveOw
steveOw

Reputation: 1021

How to free up memory after use by temporary THREE.js renderer and container?

I am using a temporary DOM div container and THREE.js renderer inside my javascript "Image SNAP Function" to produce a high-resolution image file of the current scene which the user can view outside the browser and/or save to disk as a .JPG file.

It all works fine except that another 0.14Gb of memory is used for each "snap" and not released. After a few snaps the PC performance degrades badly. The memory can be released by closing the "app" (browser tab window) but this is very inconvenient for the user.

I have tried various commands to release the memory within the Image Snap function, but they do not do the job.

Here is the code of the Image Snap function:-

function F_SNAP_JPG()           
{
    var bigContainer = document.createElement('div');
    bigContainer.style.cssText = 'id: "bigContainer", width: 4000, height:2200 '; 
    document.body.appendChild( bigContainer );
    bigContainer.innerHTML = "";

    var bigRenderer = new THREE.WebGLRenderer( {antialias:true , preserveDrawingBuffer: true} );    
    bigRenderer.setPixelRatio( window.devicePixelRatio );
    bigRenderer.setSize( 4000, 2200 );

    bigContainer.appendChild( bigRenderer.domElement ); 

//--------------------------------------------------------------------------------------------
// RENDER

    bigRenderer.render     ( scene, camera );
    
//--------------------------------------------------------------------------------------------
//... make an image of the rendition and allow user to view it outside the browser or save it as a file.
    var strMime = "image/jpg";
    var fileURL = bigRenderer.domElement.toDataURL( strMime );          

    //... after & thanks to http://muaz-khan.blogspot.co.uk/2012/10/save-files-on-disk-using-javascript-or.html
    var Anchor = document.createElement('a'); //... creates an html <a> anchor object.                      

    document.body.appendChild( Anchor ); //... for Firefox, not needed in Opera (?)
    Anchor.style    = "display: none";
    Anchor.href     = fileURL;
    Anchor.target   = '_blank';
    Anchor.download = filename || 'unknown';
    Anchor.click(); 
    document.body.removeChild( Anchor ); //... OK.
    
    //--------------------------------------------------------------------------------------------
    //...try various ways to release memory

    bigContainer.removeChild( bigRenderer.domElement ); //... removes container from screen.
    //... with no more instructions beyond here, memory increases 0.14Gb with every snap.

    bigRenderer.dispose();//... seems OK 
    //... but with no more instructions beyond here, memory increases 0.14Gb with every snap.

    document.body.removeChild( bigContainer );  //... seems OK 
    //... but with no more instructions beyond here, memory increases 0.14Gb with every snap.

    bigContainer.delete();  //... seems OK 
    //... but with no more instructions beyond here, memory increases 0.14Gb with every snap.

}//... End of function

What can be done to release the memory (short of closing down the browser tab window)?


Update (Solution)

I found a solution in this answer by Konstantin Eletskiy in this 2015 Stack Overflow post:- Clean up Threejs WebGl contexts.

The solution is to add a single line

bigRenderer.forceContextLoss();
//bigRenderer.context = null;    // not needed here.
//bigRenderer.domElement = null; // not needed here.
//bigRenderer = null;            // not needed here.

to the end of the F_SNAP_JPEG function. The other 3 lines were not needed here - possibly becuase I also removed all the DOM bigContainer stuff as suggested by George and theJim01. Now there is no detectable memory leakage for at least 9 snapshots.

Upvotes: 1

Views: 1021

Answers (1)

TheJim01
TheJim01

Reputation: 8866

I agree with George. Here's a re-write that might help reduce some of that build-up.

A couple things about the code below:

  • I used a closure to allow you to re-use the common elements.
  • I kept the parts where you create DOM structure (appendChild), but you really don't need it.
    • You actually don't need bigContainer at all.
const F_SNAP_JPG = ( function () { // TheJim01: Putting this in an IIFE closure for element re-use

    const bigContainer = document.createElement( 'div' );
    bigContainer.style.width = '4000px';
    bigContainer.style.height = '2200px';
    document.body.appendChild( bigContainer );

    const bigRenderer = new THREE.WebGLRenderer( { antialias: true, preserveDrawingBuffer: true } );
    bigRenderer.setPixelRatio( window.devicePixelRatio );
    bigRenderer.setSize( 4000, 2200 );
    bigContainer.appendChild( bigRenderer.domElement );

    const Anchor = document.createElement( 'a' );
    Anchor.style.display = 'none';
    Anchor.target = '_blank';
    document.body.appendChild( Anchor );

    return function () { // TheJim01: This is the actual function that gets called, so place breakpoints in here.

        bigRenderer.render( scene, camera );

        let fileURL = bigRenderer.domElement.toDataURL( 'image/jpg' );

        Anchor.href = fileURL;
        Anchor.download = filename || 'unknown'; // TheJim01: filename is always undefined! Mistake? Missing parameter?
        Anchor.click();
        Anchor.href = ''; // TheJim01: Removing the dataURL string.

    };

} )();

Upvotes: 2

Related Questions