Three.js: Maybe a memory leak when using canvas to show images.

Created on 23 May 2017  路  25Comments  路  Source: mrdoob/three.js

Description of the problem

JSFiddle here

I record the performance in Chrome when running this JSFiddle, it shows:
1024x1024
The memory peak value getting higher and higher, but the valley value always keeps a low value.

Then uncomment the two lines in line number 30 and 31:

width = 1000;
height = 1100;

The canvas size is not power of two, and the memory usage increases rapidly

the performance record shows:
1000x1100
The memory peak value and valley value both getting higher and higher.

I call the dispose method of material and texture explicitly, and remove the mesh from scene in every loop.

Three.js version
  • [ ] Dev
  • [x] r85
  • [ ] ...
Browser
  • [x] All of them
  • [ ] Chrome
  • [ ] Firefox
  • [ ] Internet Explorer
OS
  • [] All of them
  • [x] Windows
  • [ ] macOS
  • [ ] Linux
  • [ ] Android
  • [ ] iOS
Hardware Requirements (graphics card, VR Device, ...)

Most helpful comment

I think it should work with makePowerOfTwo() because only gets called on single map upload. But clampToMaxSize() is used by cubemaps and this approach would definitely break.

https://github.com/mrdoob/three.js/blob/dev/src/renderers/webgl/WebGLTextures.js#L248

I do like the solution though, I think it makes sense to only have one canvas.

I'll clean this all up.

All 25 comments

Please try again after properly disposing of your geometry:

s.geometry.dispose();

@WestLangley If I set the canvas's size not power of 2 (for example 1000 x 1100), the memory usage still getting higher and higher, until it reached 4GB, then browser crashed.

Do you mind correcting your fiddle, as I suggested?

Also, can you successfully duplicate the issue by modifying this three.js example to create a NPOT CanvasTexture?

Correcting as you suggested:
https://jsfiddle.net/liyuanqiu/wkdgt8h7/7/

Based on this three.js example
https://jsfiddle.net/liyuanqiu/j9Lzfu8g/1/
I only change the two lines:

canvas.width = 1000;
canvas.height = 1100;

in function createImage()

Both crashed.

OK. That is what I expected.

@mrdoob, I expect this is related to the instantiation of a secondary POT canvas in the NPOT case.

Here is my fix: https://github.com/tommytee/three.js/commit/a61af5a4613eb1e6e991e08863d67957677ae5a9

function makePowerOfTwo( image ) {

    if ( image instanceof HTMLImageElement || image instanceof HTMLCanvasElement ) {

      if ( ! POTCanvas )
        POTCanvas = document.createElementNS( 'http://www.w3.org/1999/xhtml', 'canvas' );

      POTCanvas.width = _Math.nearestPowerOfTwo( image.width );
      POTCanvas.height = _Math.nearestPowerOfTwo( image.height );

      var context = POTCanvas.getContext( '2d' );
      context.drawImage( image, 0, 0, POTCanvas.width, POTCanvas.height );

      console.warn( 'THREE.WebGLRenderer: image is not power of two (' + image.width + 'x' + image.height + '). Resized to ' + POTCanvas.width + 'x' + POTCanvas.height, image );

      return POTCanvas;

    }

    return image;

}

maybe POTCanvas should be declared somewhere else?

@tommytee Does your solution work if there are multiple, concurrent, NPOT textures in the scene?

Seems like it might be an issue.

a quick test worked fine. (3 textures)
https://vrview.io/branch/npot-multiple/examples/hotspots/index.html

I think it should work with makePowerOfTwo() because only gets called on single map upload. But clampToMaxSize() is used by cubemaps and this approach would definitely break.

https://github.com/mrdoob/three.js/blob/dev/src/renderers/webgl/WebGLTextures.js#L248

I do like the solution though, I think it makes sense to only have one canvas.

I'll clean this all up.

@tommytee Does your solution work if there are multiple, concurrent, NPOT textures in the scene?

Actually, I probably should check this with browsers...

Have you solved this problem?

@liyuanqiu did you try my fix?

@tommytee
An online little test using your fix:
http://139.196.28.110/npot/index.html

You can see the warning:
[Modified] THREE.WebGLRenderer: image is not power of two (240x260). Resized to 256x256
Notice that I added a "[Modified]" in front of this warning sentence to make sure that your fix is in use.

This page's memory usage increased rapidly.

When I go to that link my page memory stays stable and does not rise. ( latest Chrome, OSX )

Unless I open the console. I believe it is the console messages that are consuming the memory.
Try it with the console closed.

But the memory does go up for me when I use Firefox.
Could it be that Firefox is logging the console output even when the console is closed?

Try removing the warning from the code.

@liyuanqiu Is your code (fork of three.js) in a public repo?

I commented out the warning: //console.warn( 'THREE.WebGLRenderer: image is too big...

and can confirm there is no longer a memory issue with Firefox.

So far on OSX I have checked: Chrome, Safari, Opera and Firefox. None are having the memory issue.

And remember that demo is resizing 60 images per second, the warning-taking-up-memory issue won't be nearly as of as bad for normal use anyway.

@tommytee

Is your code (fork of three.js) in a public repo?

https://github.com/mrdoob/three.js/raw/dev/build/three.js
Replace the "makePowerOfTwo" function, add "var POTCanvas".

I change the two lines in index.html from:

canvas.width = 240;
canvas.height = 260;

to:

canvas.width = 2400;
canvas.height = 2600;

to use more memory, and commented the warning log.

The browser crashed in about 10 seconds, because of memory peak over than 4GB

Then I change to:

canvas.width = 1000;
canvas.height = 1100;

The memory peak keeps in about 1GB, and the valley is about 80MB, the memory usage curve is jagged in a high frequency.

Seems that your fix really taking effects.

@liyuanqiu Thanks. Are you getting the same basic results with all the browsers?

@mrdoob for the clampToMaxSize canvas I created a canvas array and pass the loop index when calling clampToMaxSize

https://github.com/tommytee/three.js/blob/d1269b972b581646242a46af25b5da32d0aa8584/src/renderers/webgl/WebGLTextures.js

This demo sets the maxSize to 500 (within clampToMaxSize) for testing:
https://vrview.io/branch/canvas-test/examples/?q=cube#webgl_panorama_cube

It seems like this issue is still exist in r92, I changed canvas size to 1000 * 1000 in webgl_test_memory.html , chrome crash the page after 10s

this issue will later be resolved in the next milestone? because am suffering from it in an application am working on

For those who need workaround before patch is released.
Add this code before your threejs code:

// See https://github.com/mrdoob/three.js/pull/14483
const consoleWarn = window.console.warn;
window.console.warn = function() {
  // filter "image is not power of two" and "image is too big" warnings from three.js
  if (typeof arguments[0] === 'string' &&
     (arguments[0].includes('image is not power of two') || arguments[0].includes('image is too big'))) {
    // log it to console without second argument, which contains reference to `image`
    consoleWarn.call(null, arguments[0]);
  } else {
    // pass other warnings without changes
    consoleWarn.apply(null, arguments);
  }
};
Was this page helpful?
0 / 5 - 0 ratings