Jump to content

Spritesheet limits


patrickfabrizius
 Share

Recommended Posts

I've had some issues (PIXI 3.0.7) with large spritesheets lately and recently learned a lot about it by searching around this forum and the PIXI github comments. There are still a few things that puzzles me, however: 

 

* Does the MAX_TEXTURE_SIZE limit apply only for WebGL renderer, or canvas renderer as well? (During a test I did it seemed like using the canvas renderer let me use bigger spritesheets than WebGL).

* Are there any "best practices" as of spritesheet size limits; I understand it all depends on the device, but what choices have YOU made, and why? Is it safe to say that 4096x4096 spritesheets are OK for reasonable backward compatibility, or do you use 2048x2048, or less? 

 

* I've tested splitting up my spritesheets (using the multipack feature in Texturepacker), but wonder how PIXI handles several spritesheets under the hood. Are there any disadvantage of having say, 10 sprites with 10 different assets from 10 spritesheets over using just one spritesheet? (Other than loading time, of course). And what about movieclips with frames loaded from different spritesheets? 

 

* In general, what would you guys say is the best practice for dealing with large assets (full screen retina backgrounds, full screen movieclips, etc)? Let the loader load it all, or do I need to carefully load / unload them from memory as I go?

 

* I've seen that some of you repeatedly advice to use: 

new PIXI.Sprite(PIXI.loader.resources[myFrame]) 

over:

new PIXI.Sprite(PIXI.utils.TextureCache[myFrame])ornew PIXI.Sprite.fromFrame(myFrame)

.. when using the PIXI loader, that you might end up having duplicates in memory. It seems I get slightly worse performance taking that advice, though (a couple 100ms longer render time and slightly more memory usage)? Is this something that has changed with PIXI v3 or am I doing something wrong? 

 

Thanks in advance, and thanks for all your great work with PIXI! :)

Link to comment
Share on other sites

Does the MAX_TEXTURE_SIZE limit apply only for WebGL renderer, or canvas renderer as well? (During a test I did it seemed like using the canvas renderer let me use bigger spritesheets than WebGL).

 

Only WebGL.

 

Are there any "best practices" as of spritesheet size limits; I understand it all depends on the device, but what choices have YOU made, and why? Is it safe to say that 4096x4096 spritesheets are OK for reasonable backward compatibility, or do you use 2048x2048, or less? 

 

Up to you, depending on your app and who you are targeting. Use http://webglstats.com/ to see what devices have what feature penetration.

 

I've tested splitting up my spritesheets (using the multipack feature in Texturepacker), but wonder how PIXI handles several spritesheets under the hood. Are there any disadvantage of having say, 10 sprites with 10 different assets from 10 spritesheets over using just one spritesheet? (Other than loading time, of course). And what about movieclips with frames loaded from different spritesheets? 

 

10 different sprites with 10 different underlying texture images will result in 10 draw calls. 10 different sprites sharing a single underlying texture image will result in one single batch draw. We only need to draw when the texture we are drawing with changes. If everything uses the same texture we can batch them all together into a single draw call.

 

In general, what would you guys say is the best practice for dealing with large assets (full screen retina backgrounds, full screen movieclips, etc)? Let the loader load it all, or do I need to carefully load / unload them from memory as I go?

 

Depends on your app. If you don't need it anymore unload it. Response time and other requirements vary app-to-app. Do what wworks best for your case.

 

I get slightly worse performance taking that advice, though (a couple 100ms longer render time and slightly more memory usage)? Is this something that has changed with PIXI v3 or am I doing something wrong? 

 

No you don't. Reading from the resource object or reading from the cache object has 0 speed difference. The reason I recommend that advice is because reading from the TextureCache is undefined. It is an internal construct. It is used *only* to ensure that multiple .fromImage() calls do not result in multiple network requests.

 

If you are using the loader, use only the loader. There is no performance difference.

Link to comment
Share on other sites

Only WebGL.

 

Are there any limits for canvas then (other than system memory)?

 

Up to you, depending on your app and who you are targeting. Use http://webglstats.com/ to see what devices have what feature penetration.

 

Thanks, I've looked at those stats and 99.4% support for 4096 seems like a good baseline, I was more thinking in terms of best practice here, if (in your experience) one should settle for 2048 (or even less)?

 

10 different sprites with 10 different underlying texture images will result in 10 draw calls. 10 different sprites sharing a single underlying texture image will result in one single batch draw. We only need to draw when the texture we are drawing with changes. If everything uses the same texture we can batch them all together into a single draw call.

 

I meant if there's a difference using 10 different textures from 10 different sources (10 images or 10 spritesheets), compared to 10 different textures from a single spritesheet? Does PIXI just crop out the asset into RAM, or does it reference the whole spritesheet when it needs access to it?

 

Depends on your app. If you don't need it anymore unload it. Response time and other requirements vary app-to-app. Do what wworks best for your case.

 

Ok, thanks! 

 

No you don't. Reading from the resource object or reading from the cache object has 0 speed difference. The reason I recommend that advice is because reading from the TextureCache is undefined. It is an internal construct. It is used *only* to ensure that multiple .fromImage() calls do not result in multiple network requests.

 

If you are using the loader, use only the loader. There is no performance difference.

Sorry, I dont understand. "There is no performance difference" yet "If you are using the loader, use only the loader"? 

Thanks for your time :) 

Link to comment
Share on other sites

Are there any limits for canvas then (other than system memory)?

I honestly don't know.

Thanks, I've looked at those stats and 99.4% support for 4096 seems like a good baseline, I was more thinking in terms of best practice here, if (in your experience) one should settle for 2048 (or even less)?

Do what works best for your app. I'm not aware of a "best practice" size other than the one that fits the most users of your app.

I meant if there's a difference using 10 different textures from 10 different sources (10 images or 10 spritesheets), compared to 10 different textures from a single spritesheet? Does PIXI just crop out the asset into RAM, or does it reference the whole spritesheet when it needs access to it?

Pixi doesn't change images. If you have 10,000 Textures used by 10,000 different sprites, but they all share the same BaseTexture then you will only have a single draw call. Seperate images are separate draw calls.

Sorry, I dont understand. "There is no performance difference" yet "If you are using the loader, use only the loader"?

You are trying to combine 3 separate APIs that were made for 3 separate purposes. The fromX() methods are there for quickly and easily loading things into objects without having to deal with aset loading. The loader is for loading and parsing assets for use with pixi objects, for example loading an image and automatically parsing it into textures for you. Finally, the TextureCache is an internal mechanism that is private, at any point in time I could rename it to _chadsSneakyTextureCache and that would not be a major version change, because it is a private internal API. Don't use it.

When I say "if you are using the loader, only use the loader" it has nothing to do with performance. It has to do with literally how it works. If you load an asset with the loader, why would you then turn around and expect and entirely different system to somehow magically then know about what you just did? We try to do a bit of internal magic for optimization reasons but the only way you could possibly combine those three APIs effectively is if you are fimiliar with all of pixi's texture handling code. Instead you could just use the public APIs. Either use the fromX() methods OR use the loader. Pick one and run with it, don't try to use both. And never touch TextureCache.

Link to comment
Share on other sites

I honestly don't know.

Ok!

Do what works best for your app. I'm not aware of a "best practice" size other than the one that fits the most users of your app.

Ok.

Pixi doesn't change images. If you have 10,000 Textures used by 10,000 different sprites, but they all share the same BaseTexture then you will only have a single draw call. Seperate images are separate draw calls.

Ok, just so I get this right - baseTexture would be the whole spritesheet? So, less spritesheets the better? The reason I'm asking is that I have several views and in that case it would make more sense to gather all the assets for each view in a single spritesheet, right?

You are trying to combine 3 separate APIs that were made for 3 separate purposes. The fromX() methods are there for quickly and easily loading things into objects without having to deal with aset loading. The loader is for loading and parsing assets for use with pixi objects, for example loading an image and automatically parsing it into textures for you. Finally, the TextureCache is an internal mechanism that is private, at any point in time I could rename it to _chadsSneakyTextureCache and that would not be a major version change, because it is a private internal API. Don't use it.

When I say "if you are using the loader, only use the loader" it has nothing to do with performance. It has to do with literally how it works. If you load an asset with the loader, why would you then turn around and expect and entirely different system to somehow magically then know about what you just did? We try to do a bit of internal magic for optimization reasons but the only way you could possibly combine those three APIs effectively is if you are fimiliar with all of pixi's texture handling code. Instead you could just use the public APIs. Either use the fromX() methods OR use the loader. Pick one and run with it, don't try to use both. And never touch TextureCache.

Ok! Thanks for your elaborate answer! :)

Maybe I misunderstood the docs, I am using the loader to pre-fetch everything so that the game runs smoothly, but I don't create all sprites or textures in the loader. Instead I create sprites as I go, and wouldn't want the game to perform a network operation to fetch an image in the middle of the action.

Link to comment
Share on other sites

Ok, just so I get this right - baseTexture would be the whole spritesheet? So, less spritesheets the better? The reason I'm asking is that I have several views and in that case it would make more sense to gather all the assets for each view in a single spritesheet, right?

Yes, less images is better. A BaseTexture is just a pixi wrapper around a source image; Texture is just a frame of a BaseTexture.

Maybe I misunderstood the docs, I am using the loader to pre-fetch everything so that the game runs smoothly, but I don't create all sprites or textures in the loader.

You may not know it, but the loader is doing this for you. After the images you put in the loader have loaded the resources object has the image data, and precreated textures and base textures for you to use. There is no need to create textures yourself, the loader does this when parsing a load for you, look at the resources object there are a lot of fun things there. Sprites, you still need to create yourself.

Link to comment
Share on other sites

Yes, less images is better. A BaseTexture is just a pixi wrapper around a source image; Texture is just a frame of a BaseTexture.

Ok, thanks!

 

You may not know it, but the loader is doing this for you. After the images you put in the loader have loaded the resources object has the image data, and precreated textures and base textures for you to use. There is no need to create textures yourself, the loader does this when parsing a load for you, look at the resources object there are a lot of fun things there. Sprites, you still need to create yourself.

 

Hm ... But what about when sprites are drawn the first time?

I have a 1024 x 1024 view with a full view background image, and about 5 movieclips that are ~300 x 300 with 20 - 40 frames each, loaded from a spritesheet manually. When running at resolution = 2 (thus consequently loading high-res assets instead with twice the width / height) I get a significant delay. When leaving a view I destroy all sprites. Even so, next time I draw the same view it is almost instant. 

I can live with a delay but would rather have a longer load time than having delays during the game. Any ideas?

Link to comment
Share on other sites

Ok .. I did use the convenience methods at first, then tried directly from the loader resources, but as I said before there was actually a noticeable penalty (which, given what you said, would indicate I must have done something else wrong :) )

 

Here is an excerpt from the code, I had to rewrite it a bit in order to not flood this post .. It should be all the relevant parts, I hope. 

// Game.js (main)module.exports = function() {  var exports = {};   var state;   var loader = require("./loader.js");       exports.init = function() {    state = "loading";     // Usual PIXI init stuff here - create renderer etc .. (left the most obvious stuff out)    resolution = PIXI.RESOLUTION = window.devicePixelRatio;    renderer = PIXI.autoDetectRenderer(1025,1024,{resolution:resolution,backgroundColor:bgColor,antialias:antialias});    var stage = new PIXI.Container();     renderer.addChild(stage);         loader.init(exports.startgame);   }    exports.startgame = function() {    var bg = new MyObject({spritesheet:"backgrounds",texture:"bg.png",stage:stage,x:512,y:512}); // bg.png is a frame in spritesheet backgrounds.json    var someAsset = new MyObject({spritesheet:"misc",texture:"button.png",stage:stage,x:512,y:512}); // button.png is a frame in spritesheet misc.json    var movieclip = new MyAnimation({frame:"animation",stage:stage,x:512,y:512});   }  }();// Loader.jsmodule.exports = function() {  var exports = {};   exports.init = function(callback) {      if (PIXI.RESOLUTION > 1) {      var suffix = "@2x.json";    } else {      var suffix = ".json";    }    var assets = [      {name:"backgrounds",url:"assets/gfx/backgrounds"+suffix},      {name:"misc",url:"assets/gfx/misc"+suffix},      {name:"animations",url:"assets/gfx/animations"+suffix}    ];    PIXI.loader      .add(assets)      .on('progress', function(e) {        console.log("loading: "+Math.round(e.progress)+"%");      })      .once('complete',function() {        callback();       })      .load();    }  }  return exports; }();// An static object (controlling a sprite)function MyObject(options) {  this.sprite = new PIXI.Sprite(PIXI.loader.resources[options.spritesheet].textures[options.texture]);    this.sprite.position.set(options.x,options.y);  this.stage = options.stage;   this.stage.addChild(this.sprite); }// An animated objectfunction MyAnimation(options) {  this.frameId = options.frame;  this.loop = options.hasOwnProperty("loop") ? options.loop : 1;  this.animationSpeed = options.hasOwnProperty("animationSpeed") ? options.animationSpeed : 0.2;  // Try and load animation starting frame  var i = 0;   var texture = PIXI.loader.resources.animations.textures[this.frameId+"_"+i+".png"]; // animation_0.png through animation_X.png is located in spritesheet animation.json  if (!texture) {    throw new Error("frame "+this.frameId+"_0.png not found");  }  this.frameList = [];  do {    this.frameList.push(texture);    i++;    texture = PIXI.loader.resources.animations.textures[this.frameId+"_"+i+".png"];  } while (texture);  this.object = new PIXI.extras.MovieClip(this.frameList);  this.object.position.set(options.x,options.y);  this.object.loop = this.loop;  this.object.animationSpeed = this.animationSpeed;   if (this.loop && (options.hasOwnProperty("loopPause") || options.hasOwnProperty("loopRandPause"))) {    this.loopPause = options.hasOwnProperty("loopPause") ? options.loopPause : 1;    this.loopRandPause = options.hasOwnProperty("loopRandPause") ? options.loopRandPause : 0;    this.object.loop = false;     var that = this;     this.object.onComplete = function() {      that.object.gotoAndStop(0);        that.timerObj = setTimeout(function() { that.play() }, (that.loopPause+Math.random()*that.loopRandPause)*1000);      }  }  if (options.play) { this.object.play(); }  this.stage.addChild(this.object);}
Link to comment
Share on other sites

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...