question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

WebGLRenderer memory leak

See original GitHub issue
Description of the problem

Environment

  • Three.js 0.113.2
  • Chrome 80.0.3987.122
  • macOS 10.15.3 (19D76)

Test code

I did not supply test case in any online fiddles shmiddles, as they have background processes which leak memory (I tried 😄 ).

<style>
  body {
    height: 100vh;
  }
  canvas {
    border: 1px solid green;
  }
</style>

<h1>Hi there</h1>
<p>Lorem ipsum</p>
<canvas class="canvasStatic"></canvas>
'use strict';

import {
  REVISION as ThreeREVISION,
  WebGLRenderer as ThreeWebGLRenderer
} from 'three';

// Setting ENABLE_RENDERER to
// true: has WebGLRenderer creation and destruction in the cycle
// false: does not have WebGLRenderer creation and destruction in the cycle
// useful for A/B test
const ENABLE_RENDERER = true;

// Setting ENABLE_AUTOTICK to
// true: enable automatic cycling
// false: cycling is done by mouse clicking
// useful for heap snapshot stepping
const ENABLE_AUTOTICK = true;

const instance = {
  alive: false,
  canvasElStatic: document.querySelector('.canvasStatic'),
  canvasElOnFly: null
};

function setup () {
  console.log('setup()');
  createLargeScopedMemBlock();
  initCanvasDynamic();
  ENABLE_RENDERER && initRenderer();
}

function shutdown () {
  console.log('shutdown()');
  ENABLE_RENDERER && destroyRenderer();
  destroyCanvasDynamic();
}

function createLargeScopedMemBlock () {
  // create mem usage in order to tickle GC to fire
  // some large string
  new Array(1e6).join('x');
  // numbaaas
  const numArray = [];
  for (let i = 0; i < 1e6; ++i) {
    numArray.push(Math.random());
  }
}

function initCanvasDynamic () {
  console.log('initCanvasDynamic()');
  instance.canvasElOnFly = document.createElement('canvas');
  instance.canvasElOnFly.className = 'canvasOnFly';
  document.body.appendChild(instance.canvasElOnFly);
}

function destroyCanvasDynamic () {
  console.log('destroyCanvasDynamic()');
  instance.canvasElOnFly.parentNode.removeChild(instance.canvasElOnFly);
}

function initRenderer () {
  console.log('initRenderer()');
  instance.rendererObj = new ThreeWebGLRenderer({
    canvas: instance.canvasElStatic,
    // canvas: instance.canvasElOnFly,
    context: null,
    precision: 'highp',
    alpha: false,
    premultipliedAlpha: true,
    antialias: false,
    stencil: false,
    preserveDrawingBuffer: false,
    powerPreference: 'default',
    failIfMajorPerformanceCaveat: false,
    depth: false,
    logarithmicDepthBuffer: false
  });

  instance.rendererObj.autoClear = false;
  instance.rendererObj.debug.checkShaderErrors = true;
  instance.rendererObj.sortObjects = false;
}

function destroyRenderer () {
  console.log('destroyRenderer()');
  instance.rendererObj.renderLists.dispose();
  instance.rendererObj.dispose();
}

function tick () {
  console.log('TICK');
  instance.alive = !instance.alive;
  if (instance.alive) {
    setup();
  }
  else {
    shutdown();
  }
}

// use setTimeout instead of setInterval
function cycle () {
  window.clearTimeout(instance.timeoutObj);
  instance.timeoutObj = window.setTimeout(() => {
    tick();
    cycle();
  }, 1000);
}

console.log('Three REVISION', ThreeREVISION);
if (ENABLE_AUTOTICK) {
  cycle();
}
else {
  document.body.addEventListener('click', tick, false);
}

Case 1

Running for 400 seconds.

ENABLE_RENDERER = false
ENABLE_AUTOTICK = true

Results overview

01-main

A slice somewhere in the beginning

01-start

Param Value
JS heap 9 930 340
Nodes 78
Listeners 22

A slice somewhere in the end

01-end

Param Value
JS heap 9 930 160
Nodes 78
Listeners 22

Case 2

Running for 400 seconds.

ENABLE_RENDERER = true
ENABLE_AUTOTICK = true

Results overview

02-main

A slice somewhere in the beginning

02-start

Param Value
JS heap 10 476 604
Nodes 78
Listeners 26

A slice somewhere in the end

02-end

Param Value
JS heap 16 668 252
Nodes 78
Listeners 26

Case 3

Running for 1500 seconds.

ENABLE_RENDERER = true
ENABLE_AUTOTICK = true

Results overview

03-main

A slice somewhere in the beginning

03-start

Param Value
JS heap 10 813 088
Nodes 78
Listeners 26

A slice somewhere in the end

03-end

Param Value
JS heap 36 903 848
Nodes 78
Listeners 26

Observation

Well, JS Heap keeps going up forever if ENABLE_RENDERER === true. In Case 3 which was run for 25 minutes it increased more that 3 fold. In Case 1 where ENABLE_RENDERER === false memory stays in control excellently.


Bug?

A bug? Undesirable Chrome GC behaviour? Or I am missing the correct way to dispose WebGLRenderer object?


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

Issue Analytics

  • State:closed
  • Created 4 years ago
  • Comments:9 (5 by maintainers)

github_iconTop GitHub Comments

4reactions
gkjohnsoncommented, Feb 27, 2020

Just something that might be helpful for debugging this that I recently found out about – you can run chrome with the following flags to enable more precise memory information via window.performance.memory and expose a function for manually triggering garbage collection as window.gc():

chrome.exe --js-flags="--expose-gc" --enable-precise-memory-info

And I commend you for your thorough test cases!

0reactions
krokocommented, Feb 28, 2020

@jsantell answered in correct repo.

@makc Yeah, many possibilities for how to architecture it. Given that I am better versed in C/C++ than JavaScript has led to behavioural issues 😆 - when I do React stuff I behave as if classes were not just syntax sugar, I like my stuff scoped. /offt Anyways, I usually make Three.js to be part of the class with construction on componentDidMount() only after first meaningful paint render(), it may also depend on some props (i.e., glTF model is passed to component) and it uses React managed DOM canvas (using React.createRef() as you do), disposals are made on componentWillUnmount(). Const in module or part of class - the backstory is that I observed mem leakage in my app, and started puling out parts till I got to very beginning of the chain, namely WebGLRenderer, which seemingly leaked, hence the ticket and example code. As it currently stands, WebGLRenderer does not actually leak (JS Heap going from 8947492 to 9320696 after 1800 disposals is great).

Read more comments on GitHub >

github_iconTop Results From Across the Web

javascript - Memory leak in Three.js
Update Answer by Mr. Doob and WestLangley Memory leak with three.js and many shapes. In webGLRenderer, after removing a mesh with.
Read more >
Webgl texture memory leak - Linux
memory leak with webgl , driver version 440.82 card : Quadro RTX 8000 os: centos 7, 64bit. i try following code on Mac,...
Read more >
Possible memory leak with video texture in WebGL renderer
Hi, I'm brand new to p5.js and I'm enjoying it quite a bit but now I'm experiencing a memory leak using a video...
Read more >
How to dispose of objects
One important aspect in order to improve performance and avoid memory leaks in your application is the disposal of unused library entities.
Read more >
1137251 - Massive memory leak with Firefox 36+ (maybe ...
From there on the memory usage goes up without end - everything get's very ... 11 (OMTC) Subsys ID: 036e1025 Vendor ID: 0x1002...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found