today I sat down and implemented the SpriteBatch class.
But what is a SpriteBatch...
Imagine you have a big bunch of Sprites that share many properties, i.e. they originate from the same Texture or they might even use the same TextureRegion. Up to now, you attached all those sprites to the Scene or another Entity, basically ending up with lots of children. While this is perfectly fine, it can be done faster, because these Sprites might share many properties, which redundantly get applied to OpenGL, which is quite costly. This is where the SpriteBatch class comes in. The SpriteBatch class omits many of such redundant calls and most importantly combines all the geometry (triangle-vertices) and texturecoordinates into one big Buffer instead of many many small ones, greatly improving performance.
So let's have a look at the code of the SpriteBenchmark. There are three ways to achieve the following result of 1000 Sprites being drawn on the screen:
The first approach is the ordinary, easiest but slowest way:
Using java Syntax Highlighting
- private void drawUsingSprites(final Scene scene) {
- for(int i = 0; i < SPRITE_COUNT; i++) {
- final Sprite face = new Sprite(this.mRandom.nextFloat() * (CAMERA_WIDTH - 32), this.mRandom.nextFloat() * (CAMERA_HEIGHT - 32), this.mFaceTextureRegion);
- face.setBlendFunction(GL10.GL_ONE, GL10.GL_ONE_MINUS_SRC_ALPHA);
- face.setIgnoreUpdate(true);
- scene.attachChild(face);
- }
- }
Parsed in 0.031 seconds, using GeSHi 1.0.8.4
The second approach is a bit smarter, as it makes use of the fact that all Sprites have the same size, so that it can reuse one RectangleVertexBuffer for all the Sprites!
Using java Syntax Highlighting
- private void drawUsingSprites(final Scene scene) {
- /* As we are creating quite a lot of the same Sprites, we can let them share a VertexBuffer to significantly increase performance. */
- final RectangleVertexBuffer sharedVertexBuffer = new RectangleVertexBuffer(GL11.GL_STATIC_DRAW);
- sharedVertexBuffer.update(this.mFaceTextureRegion.getWidth(), this.mFaceTextureRegion.getHeight());
- for(int i = 0; i < SPRITE_COUNT; i++) {
- final Sprite face = new Sprite(this.mRandom.nextFloat() * (CAMERA_WIDTH - 32), this.mRandom.nextFloat() * (CAMERA_HEIGHT - 32), this.mFaceTextureRegion, sharedVertexBuffer);
- face.setBlendFunction(GL10.GL_ONE, GL10.GL_ONE_MINUS_SRC_ALPHA);
- face.setIgnoreUpdate(true);
- scene.attachChild(face);
- }
- }
Parsed in 0.032 seconds, using GeSHi 1.0.8.4
The third and last approach is even better, as it uses the new SpriteBatch class, which omits many redundant OpenGL calls and internally uses one big Buffer for all Sprites:
Using java Syntax Highlighting
- private void drawUsingSpriteBatch(final Scene scene) {
- final int width = this.mFaceTextureRegion.getWidth();
- final int height = this.mFaceTextureRegion.getHeight();
- final SpriteBatch spriteBatch = new SpriteBatch(this.mTexture, SPRITE_COUNT);
- spriteBatch.setBlendFunction(GL10.GL_ONE, GL10.GL_ONE_MINUS_SRC_ALPHA);
- for(int i = 0; i < SPRITE_COUNT; i++) {
- final float x = this.mRandom.nextFloat() * (CAMERA_WIDTH - 32);
- final float y = this.mRandom.nextFloat() * (CAMERA_HEIGHT - 32);
- spriteBatch.draw(this.mFaceTextureRegion, x, y, width, height);
- }
- spriteBatch.submit();
- scene.attachChild(spriteBatch);
- }
Parsed in 0.034 seconds, using GeSHi 1.0.8.4
And now the most interesting part... why go the extra mile of code? (Note: some of the devices are actually hitting the 60 FPS cap easily:!:
A very similar result can be achieved for the EntityModifierBenchmark, where the sprites are changing every single frame:
Depending on your game you might receive a >2-3x performance improvement almost for free
Some things I should know about using a SpriteBatch
- SpriteBatch can help you greatly improve performance.
- SpriteBatch is not a hack, but individual Sprites are nicer from the object-oriented design side.
- You will usually have few (often just one) SpriteBatch in your game.
- Your SpriteBatch will usually contain many small, very similar Sprites , i.e. something like a tiled grid (background)
- The less you change the SpriteBatch the better it performs. While it is best to never change it, it even increases performance for completely dynamic SpriteBatches (See: EntityModifierBenchmark)!
- Your SpriteBatch could be anything else you come up with. Just give it a try!
- Consider SpriteBatch a way of optimization, but it might pay off to keep it in mind from the beginning.
- Profit :!:
There are a few restrictions though.
- All Sprites in the SpriteBatch have to use the same Texture
- All Sprites in the SpriteBatch share the same color/transparency (Indivial color/transparency from the sprites is ignored)
- All Sprites in the SpriteBatch share the same BlendFunction (Indivial BlendFunction from the sprites is ignored)
Resources:
Best Regards,
Nicolas
