Jump to content

What's the most performant way to work with textures in PIXI.js? [SOLVED]


MrBill
 Share

Recommended Posts

I couldn't seem to find a clear solution online that would apply to me anywhere but if this has been discussed elsewhere feel free to link me there.

My game's fps dips after running for only a few minutes. Shortly after my os slows down because my game uses up all the memory of the browser. I suspect the culprit is how I currently draw sprites because my "game" currently only involves placing a few sprites/text on the screen. I have my custom game framework setup to only really use Pixi.js primitives like PIXI.Texture, PIXI.Sprite, PIXI.Text, etc.

My Spritesheet class currently contains a single texture attribute that refers to the texture of the entire spritesheet. When it's time to take a single sprite from the Spritesheet, the Spritesheet object creates a copy of its texture attribute and creates a new PIXI.Sprite object using that copy. I create a copy of the texture because changing rect of the Spritesheet's texture attribute would affect all sprites that use that Spritesheet.

Drawing involves only adding the PIXI.Sprite object to the stage and clearing just removes all children from the app stage (I don't currently use containers).

Here's the method that creates a sprite from the Spritesheet:

SpriteSheet.prototype.getSprite = function(index_X, index_Y){
  let rect = this._getRectFromIndex(index_X, index_Y);
  let texture = new PIXI.Texture(this.texture);
  texture.frame = rect;
  return new PIXI.Sprite(texture);
};

this._getRectFromIndex is just an internal calculation that returns the PIXI.Rectangle object for the frame

Here's the draw method:

Renderer.prototype.draw = function(child){
  this.app.stage.addChild(child);
};

And here's the clear method:

Renderer.prototype.clear = function(){
  this.app.stage.removeChildren();
};

I'm pretty sure creating new Textures/Sprites every frame and only clearing the stage is the cause so I'm wondering what's the best way to improve performance with what I've given. Would just iterating and destroying these textures in clear every frame suffice?

Link to comment
Share on other sites

Well, optimal way is to make your own stage tree because i see that Sprites/Texts of pixi are not enough for you. You surely store something between frames, right? Those things exist with a reason, they cache some info that has to be calculated. If you cant maintain it with pixiJS API, you have to do it on your own. Look at https://github.com/pixijs/pixi-batch-renderer/ how to make your own sprites and stuff :)

If its less than 50 objects your way may work actually. But texts - they are big problem, they hold CANVAS'es. You have to deal with that somehow.

Edited by ivan.popelyshev
Link to comment
Share on other sites

3 hours ago, ivan.popelyshev said:

But texts - they are big problem, they hold CANVAS'es. You have to deal with that somehow.

Could you elaborate on this? Not too well versed with the graphics back-end so I'm attempting my proposed solution first. I set it to call destroy() on every object that is drawn in a single frame.

Renderer.prototype.addToGarbage = function(object){
  this.garbage.push(object);
};
Renderer.prototype.garbageCollect = function(){
  this.garbage.forEach(e => e.destroy());
  this.garbage = [];
};
Renderer.prototype.draw = function(child){
  this.addToGarbage(child);
  this.app.stage.addChild(child);
};
Renderer.prototype.clear = function(){
  this.app.stage.removeChildren();
  this.garbageCollect();
};

Where this.garbage is an array that contains all the objects being drawn.

It kinda works. There's some decrease in the amount of memory being leaked but it's still leaking. By snooping around the Firefox developer settings I was able to find that indeed the PIXI.Text constructor is partly responsible. You mentioned something about canvases? How can I account for those?

Edited by MrBill
Link to comment
Share on other sites

1 hour ago, MrBill said:

 

Where this.garbage is an array that contains all the objects being drawn.

It kinda works. There's some decrease in the amount of memory being leaked but it's still leaking. By snooping around the Firefox developer settings I was able to find that indeed the PIXI.Text constructor is partly responsible. You mentioned something about canvases? How can I account for those?

Your array logic is more like a POOL , GC is not same thing, it based on nested ref.
PIXI.TEXT , GRAFICS will also create texture referenceS in `PIXI.utils.TextureCache`
So yes you memory will increase because the textures still existe as reference.

Try it , create a pixi text and grafics, remove childs, destroy(), and look in PIXI.utils.TextureCache, Textures ref still existe !
https://www.pixiplayground.com/
Also `.destroy(true)` can be a solution, but cost a lot of ressources for a game logic, so you need think when to use it in your app or game context.

In most cases you may prefer to build your own textures manager and your own method for destroy. (for a game or app logic, but maybe not for a website)
Pixi is thinked for revolution the web rendering, before the rendering of video games or software!
You must therefore sometimes remove some unnecessary stuff and do it yourself. (As for the textures manager and GC manager)
If you able produce a basic playground, i can look .

Edited by jonforum
Link to comment
Share on other sites

Could you elaborate on this? Not too well versed with the graphics back-end so I'm attempting my proposed solution first.

that means you have a model from somewhere, probably multiplayer server, and you want to show it with commands without actually caching anything. I recommend to try simple canvas2d first instead of pixi, you might actually save time with it. In 2013 i made a game with full-HD 60FPS only on 2d without webgl, so it should work. When you get the feel of API then pixi will become more sensible for you :)

If you want to actually understand something, look in the source code, documentation is never enough in that case. Open it in separate IDE window (whether its vscode or intellij), search classes with shortcut, classes that you are using. The problem is that explaining this all stuff in a few sentences on forums is very hard, and we dont have free books, especially for your case.

I told you that Text creates Canvas - well, look in the source, find "document.createElement("canvas")", of certain size, its at least 4 * width * height bytes array in both CPU and maybe GPU sync because we dont really know what will browser do with it.

Edited by ivan.popelyshev
Link to comment
Share on other sites

To anyone having similar issues, I've cut down significantly on memory leaking by reusing sprites & textures for persistent entities like the player, and storing all disposable pixi graphics objects (like sprites for individual tiles to be drawn) in a pool (represented as a JavaScript Set object). Every frame the disposable objects are destroyed and the pool is cleared. I also implemented view culling for the tiles.

I will probably look into combining all the tiles textures into a single texture next to keep the size of the pool low to lower the amount of iterations each frame.

Link to comment
Share on other sites

Can't figure out how to deal with memory leaks for the (I believe, but not 100% certain) PIXI.Text and PIXI.Graphics objects. Currently I create and .destroy() these every frame but it's still being referenced somewhere and I don't know where. I think it's the PIXI.Text objects mainly because Firefox's memory snapshots in Tree Map show the Canvas continuously increasing in memory size. Do PIXI.Graphics also use the canvas?

I created this function that will call whenever I want to remove a reference from the Cache. I only call this for objects with generic ids like "pixiid_7". I get the ids from the PIXI.Text.canvas._pixiid

TextureManager.prototype.removeFromTextureCache = function(id){
  let cache = PIXI.utils.TextureCache;
  if(cache[id]){
    delete cache[id];
  } else console.error(`Error while trying to delete from TextureCache: ${id} does not exist as a property.`);
};

I'm not sure if this works at the moment because I still can't correctly pinpoint which objects are added to TextureCache and which are not (the error message fires off). Additionally, TextureCache doesn't have any documentation to work with.

My destroy function currently looks like this:

TextureManager.prototype.destroyObject = function(object){
  switch(object.constructor){
    case PIXI.Text:
      let cachedId = object.canvas._pixiId;
      object.destroy();
      this.removeFromTextureCache(cachedId);
      break;
    default:
      object.destroy();
  };
};

Basically I need help figuring out how to remove all traces of the different types of objects. I want to rework my game framework to reuse text like how I did for sprites. But without knowing how to properly delete them memory leaks are inevitable.

Link to comment
Share on other sites

Try play and learn the memory head snap tool. 
Is help you to found all deeps references of objets constructor in your projets.
At bottom (Retainer) is the most important debug info, its all refs who retain yours objets and who prevent GC release (memory leaks culprits).
So you can understand why some ref signature persiste in memory ,and why the GC no do is jobs.

This tool is in my opinion important to mastery for all devellopers!
You can also compare 2 snap and see the diff.
AMXbe58Q_o.png

I see you also use console log on texture, watch out, if i remember, the log can also prevent GC, and will appear in retainer. (but with special color).
Also look if is not a closures somewhere who retains your refs, sometimes they are difficult to debug.
Here a good example of hell of closures MemLeak, if this can help.
https://www.lambdatest.com/blog/eradicating-memory-leaks-in-javascript/#:~:text=Closures&text

Edited by jonforum
Link to comment
Share on other sites

Looks like I fixed it by doing .destroy(true) for PIXI.Text and PIXI.Graphics objects and clearing the Texture cache with PIXI.utils.clearTextureCache(). I really appreciate all the responses I got! Thank you!

EDIT: Though I wonder if there's a good way to only remove certain textures.

EDIT 2: I think I understand now. Here's what my code looks like. Currently I only remove the generic pixiid textures being cached that are leading to a memory leak. Good thing the API documentation has links to the source code!

TextureManager.prototype.clearTextureCache = function(clearAll=false){
  let generic = "pixiid";
  if(clearAll === true){
    PIXI.utils.clearTextureCache();
  } else {
    let cache = PIXI.utils.TextureCache;
    Object.keys(cache).forEach(key => {
      if(key.startsWith(generic) === true){this.removeFromTextureCache(key)};
    });

    cache = PIXI.utils.BaseTextureCache;
    Object.keys(cache).forEach(key => {
      if(key.startsWith(generic) === true){this.removeFromTextureCache(key, cache)};
    });
  };
};

 

Edited by MrBill
Link to comment
Share on other sites

  • MrBill changed the title to What's the most performant way to work with textures in PIXI.js? [SOLVED]

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...
 Share

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...