Skip to content

Fix memory leak from WebGLState light/shadow data from texture generation#227

Merged
jsantell merged 1 commit intomasterfrom
memory-leak
Nov 20, 2018
Merged

Fix memory leak from WebGLState light/shadow data from texture generation#227
jsantell merged 1 commit intomasterfrom
memory-leak

Conversation

@jsantell
Copy link
Contributor

With the background-image demo often open on my machine, after long periods of time, my machine would hang, and had a hunch it had to do with the demo's infinite loading of new textures. In terms of severity, this would only occur when constantly changing textures (not a realistic scenario IMO) over a long period of time as small amounts of resources were being generated and not collected.

The leak wasn't terribly severe tracked leak down to cubemap generator, figured might as well update

three.js creates a WebGLState for every camera/scene combo rendered to store light and shadow data, and the three.equirectangular-to-cubemap component we use currently creates a new camera for every texture -- resulting in a new WebGLState created and retained, and does not get disposed unless calling dispose on the renderer, which will have other unintended side-effects in our case since we're not yet done with our renderer.

As a part of this change, I figured it'd be a good time to update to three's EquirectangularToCubeGenerator so we can ensure this works with the version of three we're using as a dependency (Fixes #220). Unfortunately, this generator similarly creates a new scene and camera for every texture, and not designed to handle multiple texture conversions, and the workarounds to cache these values are documented in the code.

Will file a follow up with three with changes to EquirectangularToCubeGenerator so that it can handle this scenario without creating extraneous light/shadow data, which would simplify a lot of this code when we next upgrade three.

--

Also included is a fuzzer page that, at random intervals, flips some values on the model. The element handles this surprisingly well, but a great test bed to ensure unexpected attribute combinations work well, and shake out any race conditions we have. Using memory tools, we can leave this run for quite some time and see that the memory doesn't grow.

@jsantell jsantell requested a review from cdata November 19, 2018 19:57
import '@polymer/iron-demo-helpers/demo-snippet.js';
import '@polymer/paper-button/paper-button.js';
import '@polymer/paper-slider/paper-slider.js';
import '@polymer/paper-radio-group/paper-radio-group.js';
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like using these nice components in our demos, but maybe these should be broken out per dependency as-needed in demos (e.g. only the PBR demo and fuzzer use paper-button)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There should a world where we have a build tool that figures out all of the bundle fragments across all examples without manual configuration and builds those for us (Polymer CLI does static analysis and supports configuration to do just this).

Filed #229 to track

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍


updated.style.transition = 'none';
updated.style.opacity = 1;
requestAnimationFrame(() => {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This "update" should flash and fade on every change; doesn't work too well for lower values. Could remove

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the value of the fade? Just ensuring that partial opacity doesn't produce unexpected side-effects?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to indicate to the viewer that a change occurred (more interesting for longer wait times), since sometimes the attribute changes do not result in a visible change

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Going to remove this for now

input: './lib/model-viewer.js',
output: {
file: './dist/model-viewer.js',
sourcemap: true,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Needed for debugging memory leaks, can remove and open another issue (valuable but we're building a lot of things per npm run build currently)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WFM our users would probably appreciate the source maps being published w/ the package anyway.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 !

* @return {THREE.Texture}
*/
equirectangularToCubemap(texture) {
// Each instance of EquirectangularToCubeGenerator creates a new camera,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This special handling could go away if we make upstream fixes in three's generator

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there an open issue against Three? Maybe link it in the comment with a note about when we can take this out?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jsantell jsantell changed the title Memory leak prog Fix memory leak from WebGLState light/shadow data from texture generation Nov 19, 2018
@cdata
Copy link
Contributor

cdata commented Nov 20, 2018

In terms of severity, this would only occur when constantly changing textures (not a realistic scenario IMO)

This scenario seems like it would be more common in a WYSIWYG tool that enabled GUI-based configuration of the <model-viewer>. So, probably really good to fix regardless.

Copy link
Contributor

@cdata cdata left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great, some minor changes requested

import '@polymer/iron-demo-helpers/demo-snippet.js';
import '@polymer/paper-button/paper-button.js';
import '@polymer/paper-slider/paper-slider.js';
import '@polymer/paper-radio-group/paper-radio-group.js';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There should a world where we have a build tool that figures out all of the bundle fragments across all examples without manual configuration and builds those for us (Polymer CLI does static analysis and supports configuration to do just this).

Filed #229 to track

input: './lib/model-viewer.js',
output: {
file: './dist/model-viewer.js',
sourcemap: true,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WFM our users would probably appreciate the source maps being published w/ the package anyway.


updated.style.transition = 'none';
updated.style.opacity = 1;
requestAnimationFrame(() => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the value of the fade? Just ensuring that partial opacity doesn't produce unexpected side-effects?

}];
attributes.forEach(attr => attr.values.push(null, ''));

function updateAttribute (attr, values) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know how to do this in a way that won't have potentially non-relevant memory/perf side-effects, but it would helpful to log what changes happened over time somehow.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(No action required, just observing)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking of a rolling log of the last 10 changes or so when stopping, to debug a scenario where going from state X to Y causes visual artifact Z


import * as THREE from 'three';

const EquirectangularToCubeGenerator = function ( sourceTexture, options ) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this modified from the Three version? If so, can you call out the modifications?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not other than the ES6 wrapper stuff, no


// Enable three's loader cache so we don't create redundant
// Image objects to decode images fetched over the network.
Cache.enabled = true;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

this.config = {...defaultConfig, ...config};
this.renderer = renderer;
this.cubemapGenerator = new EquirectangularToCubemap(this.renderer);
this.envMapGenerator = new EnvMapGenerator(this.renderer);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: initialize class field $cubeGenerator

* @return {THREE.Texture}
*/
equirectangularToCubemap(texture) {
// Each instance of EquirectangularToCubeGenerator creates a new camera,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there an open issue against Three? Maybe link it in the comment with a note about when we can take this out?

// the prototype since this is called in the constructor.
if (!this[$cubeGenerator]) {
const material = generator.boxMesh.material;
EquirectangularToCubeGenerator.prototype.getShader = () => material;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible to just create an extended class rather than monkey patching on the fly here? e.g.,

class NonLeakyEquirectangularToCubeGenerator extends EquirectangularToCubeGenerator {
  constructor(sharedShaderMaterial = null) {
    super();
    this.sharedShaderMaterial = sharedShaderMaterial;
  }

  getShader() {
    if (this.sharedShaderMaterial != null) {
      return this.sharedShaderMaterial;
    }
    return super.getShader();
  }
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call, trying this

scene/camera for every texture. Add fuzzer.html example to stress test
memory leaks, race conditions and uncommon attribute combinations. Switch to using three's
EquirectangularToCubeGenerator, and extend with a patched version until
an upstream fix in three. Fixes #220.
Copy link
Contributor

@cdata cdata left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM 👍 excited about the potential of this fuzzer

@jsantell jsantell merged commit 5ef9706 into master Nov 20, 2018
@jsantell jsantell deleted the memory-leak branch November 20, 2018 22:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants