WebGLRenderer memory leak
See original GitHub issueDescription 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
A slice somewhere in the beginning
Param | Value |
---|---|
JS heap | 9 930 340 |
Nodes | 78 |
Listeners | 22 |
A slice somewhere in the 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
A slice somewhere in the beginning
Param | Value |
---|---|
JS heap | 10 476 604 |
Nodes | 78 |
Listeners | 26 |
A slice somewhere in the 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
A slice somewhere in the beginning
Param | Value |
---|---|
JS heap | 10 813 088 |
Nodes | 78 |
Listeners | 26 |
A slice somewhere in the 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:
- Created 4 years ago
- Comments:9 (5 by maintainers)
Top 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 >Top Related Medium Post
No results found
Top Related StackOverflow Question
No results found
Troubleshoot Live Code
Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start FreeTop Related Reddit Thread
No results found
Top Related Hackernoon Post
No results found
Top Related Tweet
No results found
Top Related Dev.to Post
No results found
Top Related Hashnode Post
No results found
Top GitHub Comments
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 aswindow.gc()
:And I commend you for your thorough test cases!
@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 paintrender()
, it may also depend on some props (i.e., glTF model is passed to component) and it uses React managed DOM canvas (usingReact.createRef()
as you do), disposals are made oncomponentWillUnmount()
. 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).