Best way to reuse a texture

Hi,

Im in the process of porting my Falling Rocks game to android and I am having some FPS issues, I have discovered that this is due to the way my objects are created.

My FPS on desktop is 60 solid but FPS on a Galaxy S4 and Tab 3 are around 35-40, which is low considering the power of these devices and the simplicity of the game.

My game creates 300 new objects every minute on normal and does 600/minute on hard, this means that my game does a minimum of 300 File I/O operations a minute.

This seems to be the problem, now I thought “heh, i’ll just create an assets class with a bunch of static fields and reuse the sprites”, well that did not work out well because my objects have a random size every spawn, also after 5 seconds they start to blink to let you know they are about to expire. Which is obviously not working because when 1 sprite flashes they all flash.

However when I did this my FPS was 60 solid on both devices.

What can I do to reduce these File I/O operations? I thought object pooling but that means I would have to load in like , 30 of every texture into memory and store them, then borrow and put back constantly. Might end up hogging all the memory or making the GC go crazy?

Solution

Baring in mine I am using the LibGDX library but that should not make a difference.

Basically create a static reference to the texture/sprite somewhere like so:


public class AssetLoader{

public static final Sprite EMERALD = spriteLoader("data/img/gems/emerald.png");

}

Then when creating a new object, you can use the following constructor in the Sprite class:

/** Creates a sprite that is a copy in every way of the specified sprite. */
	public Sprite (Sprite sprite) {
		set(sprite);
	}

So I done this:

setSprite(AssetLoader.EMERALD);

The set sprite method declares a new sprite, rather than taking the object reference itself, it copes it. So don’t use

this.sprite = sprite;

Instead use

this.sprite = new Sprite(sprite);

Load the texture in statically, but don’t create the actual rock statically. If you don’t have those activities separated, then you need to do so. Make a rock class that holds a reference to a texture. Then, when you create that rock object, copy the texture data, but don’t actually load it back in from the file system. This should be fairly easy to do.

Edit:
Longarmx explained it much better than I did.

Load 1 spritesheet texture at the beginning and then pull TextureRegions from that texture to draw.

So:



Sprite sprite = new Sprite(AssetLoader.ROCK);


This will create a new sprite using that data?

I personally haven’t ever used the AssetLoader in LibGDX, I just create TextureRegions from a large spritesheet. For example:


public Mob(World world, int x, int y, int tx, int ty){
		texture = new TextureRegion(new Texture("data/tiles.png"), tx, ty, 16, 16);
		sprite = new Sprite(texture);

Where tx and ty indicate the position of the texture in the spritesheet, and 16 is the width and height of said TextureRegion. Now, this actually isn’t good code because I create the spritesheet every time I load a new Mob (new Texture(“data/tiles.png”)), but it would be easy enough to fix that.

I’ll take a look into the AssetLoader, sounds interesting.

Actually the asset loader is my own, it’s nothing special, pretty standard and horrible.

However doing what you recommended works fine, it copies the data rather than use the actual data.

Here is my asset thingie:

/**
 * Basic assets loader, used to load in textures or polygon information for
 * box2d
 * 
 * @author Stephen Gibson
 */
public class AssetLoader {

	/* Gems */
	public static final Sprite EMERALD = spriteLoader("data/img/gems/emerald.png");
	public static final Sprite RUBY = spriteLoader("data/img/gems/ruby.png");
	public static final Sprite SHAPPIRE = spriteLoader("data/img/gems/sapphire.png");
	public static final Sprite DIAMOND = spriteLoader("data/img/gems/diamond.png");

	/* Rocks */
	public static final Sprite ROCK_1 = spriteLoader("data/img/rocks/rock1.png");
	public static final Sprite ROCK_2 = spriteLoader("data/img/rocks/rock2.png");
	public static final Sprite ROCK_3 = spriteLoader("data/img/rocks/rock3.png");
	public static final Sprite ROCK_4 = spriteLoader("data/img/rocks/rock4.png");
	public static final Sprite ROCK_5 = spriteLoader("data/img/rocks/rock5.png");

	/* Health pickups */
	public static final Sprite FIRST_AID_KIT = spriteLoader("data/img/health/firstaidkit.png");
	public static final Sprite MEDI_KIT = spriteLoader("data/img/health/medikit.png");

	/* Fonts */
	public static final BitmapFont INDICATOR_FONT = fontLoader("data/font/PriceDown38-White.fnt");

	/* Poly data */
	public static final BodyEditorLoader GEMS = bodyLoader("data/img/gems/gems");
	public static final BodyEditorLoader ROCKS = bodyLoader("data/img/rocks/rocks");
	public static final BodyEditorLoader HEALTH = bodyLoader("data/img/health/health");

	public AssetLoader() {
		
	}

	/**
	 * Load a sprite from a file
	 * 
	 * @param file
	 *            the file location in storage
	 * @return
	 */
	private static Sprite spriteLoader(String file) {
		return new Sprite(new Texture(Gdx.files.internal(file)));

	}

	/**
	 * Load a font from a file
	 * 
	 * @param file
	 *            the file location in storage
	 * @return
	 */
	private static BitmapFont fontLoader(String file) {
		return new BitmapFont(Gdx.files.internal(file));
	}

	/**
	 * Load body information from a file
	 * 
	 * @param file
	 *            the file location in storage
	 * @return
	 */
	private static BodyEditorLoader bodyLoader(String file) {
		return new BodyEditorLoader(Gdx.files.internal(file));
	}

Like I said it doesn’t do much and was just done dirty and quick, it gets the job done and increases performance so +1 lol.

Oops, I was thinking of the AssetManager :wink: Got me there! so your problem is fixed?

Running tests now, will let you know if the FPS has improved.

60 FPS on S4 and Tab 3, 40 or so on Experia S and Desire HD, give or take.

Thanks, will update the OP in a mo for solution.

EDIT: Runs fking terrible on HTC Hero lol

Don’t create a new texture for everything. Generating and binding textures is an expensive operation. Instead use TexturePacker to pack textures and load each sprite with a texture region.

You can run TexturePacker every time you run your application while in development.


TexturePacker.process("../Project-android/assets/textures", "../Project-android/assets/textures/packed",
		"sprites"); // I find it easiest to use a json file for the settings

Ignore the atlas file that’s generated; it’s only useful for Scene2D skins now. Instead, load the generated spritesheet as a texture and load each sprite with a texture region, like so:


private static Texture sprites = new Texture(Gdx.files.internal("textures/packed/sprites.png"));

private static Sprite spriteLoader(int x, int y, int width, int height) {
      return new Sprite(new TextureRegion(sprites, x, y, width, height));
}

Once set up, all you have to do is put a new sprite in the assets folder and it’ll automatically pack it into the spritesheet.

Is the way I am doing it much more expensive?

Am I not just creating the texture once and then reusing the texture?

[quote]Am I not just creating the texture once and then reusing the texture?
[/quote]
No, you are using a separate Texture for each sprite, as shown by your AssetLoader code:


<snip>

private static Sprite spriteLoader(String file) {
      return new Sprite(new Texture(Gdx.files.internal(file))); //<--- Texture() being called! Bad!
}

<snip>

Troubleshoots is replacing that new Texture() call with a new TextureRegion() call which is referencing a section in a spritesheet, which is the best way to do it, as there is only one texture sent the GPU (the spritesheet).

TextureRegions are simply lightweight references to a piece of texture, and can be used as a substitute for individual Textures in most places.

No, you are using a separate Texture for each sprite, as shown by your AssetLoader code:


<snip>

private static Sprite spriteLoader(String file) {
      return new Sprite(new Texture(Gdx.files.internal(file))); //<--- Texture() being called! Bad!
}

<snip>

Troubleshoots is replacing that new Texture() call with a new TextureRegion() call which is referencing a section in a spritesheet, which is the best way to do it, as there is only one texture sent the GPU (the spritesheet).

TextureRegions are simply lightweight references to a piece of texture, and can be used as a substitute for individual Textures in most places.
[/quote]
Hm, I only call that Texture() constructor once per sprite at run time so for assets of this size (like 10 sprites) this is ok right?

I do not want to pack all these things into a spritesheet if not entirely needed, I don’t want to sound like I am being ignorant but just clearing up the facts, only way to learn.

Here is the constructor for the LibGDX sprite class that I am calling in order to set the sprite using the static reference:

/** Creates a sprite that is a copy in every way of the specified sprite. */
	public Sprite (Sprite sprite) {
		set(sprite);
	}

It does not seem to be creating a texture, appreciate the advice and have used sprite sheets before but didn’t want to use it this time because I was expecting to use < 10 sprites for the entire game.