1000 images in a frame, it was a bit exaggerating but the game do have lots of non-static images. And I tried to put them in sprite sheets but found out it isn't efficient as there will be like 1k+ different items/tiles/monsters scattered around the map and each has different image, some of the images are big too. So even if I made lots of sprite sheets, there will be a big chance next image will be loaded from different sprite sheet. I'm still looking for a solution.
You should definitely be using texture atlases as much as you can. Three texture atlases is going to load and perform a lot faster than 300 separate images.
If you absolutely can't organize your images in batches, then some lower level stuff like shaders and multi-texturing may help here. This is not within the scope of slick (yet), so you'll have to do some digging yourself.
Also, using your SpriteBatch class, I did some test showing 2000 same small image(32x32) in a frame. I got 800FPS using slick's image.draw(), and 2400FPS o.o using SpriteBatch.
Nice! Here are my results rendering 17,000 32x32 sprites to the screen.SpriteBatch.drawImage:
60 FPS (7 render calls, SpriteBatch size of 18,000)Image.drawEmbedded:
50 FPS (using startUse/endUse)Image.draw:
Controls: 1-9 change ball count, 0 subtract 100 balls, + add 100 balls, SPACE toggle SpriteBatch, R toggle renderInUse (if not using spriteBatch), V toggle vsync, W/Q change batch size, B apply new batch size.
But when I test again by showing 2000 different images(ranging from 32x32 to 512x512), I got 50 FPS with slick.image() and 65 FPS with SpriteBatch. Is it expected?
Yes. SpriteBatch is best suited for rendering a series of images that point to the same texture. You won't see very much of a benefit from rendering many different textures. At that point it's basically doing what Image.draw is doing: bind new texture, send quad to GPU, repeat.
The idea of sprite batching is as follows, for anyone wondering:
Whenever a user tries to draw an image, store the vertex data (position, texcoords, colors) in a single large array. We continue adding to the single array until:
- The user calls flush(), forcing us to render all the vertex data we have so far accumulated
- We have reached the end of our array
- The user is rendering a different texture than the last one, meaning we need to flush() and bind the new texture before continuing
Flushing the batch sends the vertex data to the GPU. A larger sized SpriteBatch will be able to hold more vertex data before flushing, thus reducing the amount of render calls needed to draw all objects in the batch. If we are constantly switching textures, or if the batch size is very small, there will be a lot more flushing. You can find out how many flushes are being used per frame with the public renderCalls variable (set it to zero at the start of the frame, and after you call the final flush check its value).