Pixi.js: Memory leak in PIXI.Graphics

Created on 26 Mar 2019  路  9Comments  路  Source: pixijs/pixi.js

Current Behavior

I'm using PIXI to draw a star field so I'm doing lots and lots of drawCircle calls. Chrome runs out of memory after a few seconds. Here's a screenshot of a memory snapshot:

Memory snapshot

Environment

  • pixi.js version: _5.0.0-rc.3_
馃憤 Not A Bug

Most helpful comment

First, please feel free to submit a PR to add some more documentation about clear and destroy. That'd be great 馃憤 and sure other folks would appreciate.

Second, that's true! In general, the process of generating triangles can be expensive when it done over and over again (per frame)-- as is the case of your demo. There are two strategies that might allow you to achieve the same effect more efficiently:

  • Use Sprites and a single shared circle texture and resize the sprites. Generally speaking Sprites are super fast, you could even use ParticleContainer for extra performance since all Sprite would share the same Texture.
  • Alternatively, if you need Graphics, I would suggest that you share GraphicsGeometry between many Graphics objects. In your case, sharing a single circle geometry is going to be _way_ more efficient, because it get's around having to generate the triangles for all those circles every frame. Then simply scale/position the individual Graphics objects.
// Create the geometry to share
const circle = new PIXI.Graphics()
  .beginFill(0xffffff)
  .drawCircle(0, 0, 100, 100);

// Create a pool of Graphics objects
const pool = [];

// Add new Graphics per frame
app.ticker.add(() => {
  app.stage.removeChildren().forEach(child => pool.push(child));
  for (let i = 0; i < 100; i++) {
     const star = pool.pop() || new PIXI.Graphics(circle.geometry);
     // set the position and scale of each star
     app.stage.addChild(star);
  }
});

In both situations, you should pool and reuse your Sprite or Graphics objects and try not to destroy unless you no longer need them.

Hope that helps.

All 9 comments

OK, I like it, but I need a demo.

I'm still waiting for demo on previous issue btw

https://gist.github.com/btmorex/e09fc62e33c8da3c3b64cba3077d4dd3

The other issue no longer happens now that I'm using PIXI.Graphics more. It's still happening consistently in the unminified version I put up at https://voidshift.com/ though.

Oh, sorry, I forgot you posted it already :) I'll look when I have time, thanks!

I don't believe this is a memory leak. The main issue with your approach is that you are creating new PIXI.Graphics every frame without destroying them. This bug is likely to occur in that case.

You are better off creating a new PIXI.Graphics object _once_ and then calling clear() on the Graphics every frame, then redrawing your stars. The other option to is save reference to the Graphics outside the ticker and make sure to call destroy before creating a new version, however, you're still likely to get hiccups from the garbage collector if you do this.

Got it. I changed my code to call clear() and it's no longer leaking. It just wasn't clear from the docs or the code samples that I need to call clear() or destroy().

I'm still seeing heavy allocations in geometry code that (I think) are causing major GC's, which in turn are causing stutters. Any way to avoid those allocations?

First, please feel free to submit a PR to add some more documentation about clear and destroy. That'd be great 馃憤 and sure other folks would appreciate.

Second, that's true! In general, the process of generating triangles can be expensive when it done over and over again (per frame)-- as is the case of your demo. There are two strategies that might allow you to achieve the same effect more efficiently:

  • Use Sprites and a single shared circle texture and resize the sprites. Generally speaking Sprites are super fast, you could even use ParticleContainer for extra performance since all Sprite would share the same Texture.
  • Alternatively, if you need Graphics, I would suggest that you share GraphicsGeometry between many Graphics objects. In your case, sharing a single circle geometry is going to be _way_ more efficient, because it get's around having to generate the triangles for all those circles every frame. Then simply scale/position the individual Graphics objects.
// Create the geometry to share
const circle = new PIXI.Graphics()
  .beginFill(0xffffff)
  .drawCircle(0, 0, 100, 100);

// Create a pool of Graphics objects
const pool = [];

// Add new Graphics per frame
app.ticker.add(() => {
  app.stage.removeChildren().forEach(child => pool.push(child));
  for (let i = 0; i < 100; i++) {
     const star = pool.pop() || new PIXI.Graphics(circle.geometry);
     // set the position and scale of each star
     app.stage.addChild(star);
  }
});

In both situations, you should pool and reuse your Sprite or Graphics objects and try not to destroy unless you no longer need them.

Hope that helps.

I'm going to close this since it seems like the initial issue was addressed. But feel free to chat more about strategies for managing memory.

Thanks! I ended up going with the second solution and everything is running perfectly smooth at 60 fps now. Added a doc note and fixed a couple issues I ran into with GraphicsGeometry in #5544

This thread has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.

Was this page helpful?
0 / 5 - 0 ratings