13
\$\begingroup\$

I am developing a 2D game, and I have a lot of sprites. I used 3D animations and models to render into 2D, to give them that "Fallout" or "Diablo" look to them. It is also easier than drawing by hand, lol.

I have already had to cut the framerate down to 15fps, which was the lowest I could lower without making them have a choppy look to them. However, it was sad due to how incredibly smooth 24 frames looked.

There are two reasons I did this:

1) Cut down on HDD space. The fewer the images, the smaller my total game will be.

2) Cut down on RAM consumption. The fewer images to load, the more likely I am to avoid issues bloating my RAM limitation.

However, if there was a way to compress the images in both HDD space and RAM, I would do so. I have tested it before, and most do not receive any change in quality when giving from RGBA8888 to RGBA5555 and only a little hit when converting to RGBA4444 in my TexturePacker program. I do not do this currently, because SFML seems to use the same amount of memory regardless of which type of .PNG image it is. I looked into researching how to load it differently, but failed to find anything on the subject.

I have read a lot about how to handle 2D video games. The consensus is overwhelming: Pack your Sprites into a Bigger Texture for great performance! So I pack my tiny sprites into a much larger spritesheet using TexturePacker.

However, I plan to have 10-15 animations per character, 5 directions to move, and 15-40 frames per animation (probably an average of 24). With 15 animations, 5 directions, and an average of 24 frames per animation; That is 1800 individual frames per character. If packed in a sprite sheet, that is only 75 images instead. (One sprite sheet per Animation, per Direction. 15 * 5)

For the one huge boss character in the game, I cannot use a spritesheet and have to program a way to simply load in one image at a time. I do not know if I can do this for performance yet.

For the characters, I already pack them in a spritesheet. For a single character walking about, this seems to work most of the time, although sometimes it stalls. However, I attribute that to my ill conceived code that swaps out textures instead of preloading all textures for that character.

If I were to preload the textures, it makes sense for sprite sheets. I would only imagine it's a bad idea to preload 1800 tiny images for each character.

However, I imagine streaming them into and out of memory one at a time would be extremely fast, so I would only need to have a single image in memory at one time. Wouldn't this mean that at any given moment I would only have each character consume a few KB instead of 45+MB?

I imagine this would kill my performance, as streaming would need to be incredibly fast (15 images going into and out of memory and rendering, per second) and although the images would be very small- it might be a better idea to load character spritesheets into memory instead. But I will have to code a single-image stream-like render system for my larger boss character anyway.

I have been experimenting, but it is not a simple process. Especially given the fact I am working on other parts of the game engine that do not deal with graphics right now.

\$\endgroup\$
5
  • \$\begingroup\$ 1. You did not specified your RAM or HDD constraints. How many characters need to be in fast access? 2. There are several questions along the text, maybe you could focus them with bold or even split the questions in parts? \$\endgroup\$
    – Kromster
    Commented Sep 13, 2013 at 10:05
  • \$\begingroup\$ Oh, I'm sorry. Not many. I would imagine that the maximum number of individual characters on screen at any one time would be about 40. If people made a point to try to crash their clients, then... 130 is the absolute max. Typically, there would only need to be 10 for the typical max, and the absolute max would be no more than <40. Anything above 40 would be an extreme, extreme rarity, with users purposefully trying to cram in characters for no reason other than a screenshot or for the fun of cramming in characters. Anything above 10 is rare, and anything about 40 is extremely, extremely rare. \$\endgroup\$
    – Carter81
    Commented Sep 13, 2013 at 10:11
  • \$\begingroup\$ The game is a PC-only (no mobile) 2D rpg, however I would not like to rule out a mobile platform unless it's simply not feasible. I imagine that the RAM space I have is limited to whatever RAM is on the user's PC, and the user's VRAM. I severely doubt it will be very large HDD wise. It's just that it's always true for HDD consumption that the smaller it is, the better. \$\endgroup\$
    – Carter81
    Commented Sep 13, 2013 at 10:12
  • \$\begingroup\$ How many UNIQUE characters you need to have on screen? \$\endgroup\$
    – Kromster
    Commented Sep 13, 2013 at 10:17
  • \$\begingroup\$ No more than 20. Anything above that would be next to impossible unless they cheated. Typically 5-10. \$\endgroup\$
    – Carter81
    Commented Sep 13, 2013 at 10:18

2 Answers 2

16
\$\begingroup\$

We have a similar case with our RTS Remake. All units and houses are sprites. We have 18 000 sprites for units and houses and terrain, plus another ~6 000 for team colors (applied as masks). Long-stretched we also have some ~30 000 characters used in fonts.

So the main reason behind atlases are:

  • less wasted RAM (in older days when you upload NPOT to GPU it stretched/padded it to POT, I read it's still the same with iOS and some frameworks. You better check on range of hardware you target)
  • less texture switches
  • faster loading of everything in fewer bigger chunks

What did not worked for us:

  • paletted textures. The feature existed only in OpenGL 1.x 2.x and even then was mostly dropped by GPU makers. However if you aim at OpenGL+Shaders you can do that in shaders code yourself just fine!
  • NPOT textures, we had issues with wrong borders and blurred sprites, which is unacceptable in pixel art. RAM usage was much higher too.

Now we have everything packed in several dozens of 1024x1024 atlases (modern GPUs support even bigger dimensions) and that works just well eating only ~300mb of memory, which is quite fine for a PC game. Some optimizations we had:

  • add user option to use RGB5_A1 instead of RGBA8 (checkerboard shadows)
  • avoid 8bit Alpha when possible and use RGB5_A1 format
  • tightly pack sprites into atlases (see Bin Packing algorithms)
  • store and load everything in one chunk from HDD (resource files should be generated offline)
  • you might also try hardware compression formats (DXT, S3TC, etc.)

When you seriously consider moving to mobile devices you will worry about constraints. For now just get the game working and attract players! ;)

\$\endgroup\$
1
  • \$\begingroup\$ This was the best solution by far! My sprites don't even look any different in RGB5_A1 or RGBA4444, but it saves on memory. An your suggestion in chat to preload all of my assets in RAM and VRAM is perfect. Even further, you suggested to have optional graphic levels, such as high definition client for those who have the RAM, or an option to reduce framerate by half, etc. Great suggestions all around, and exactly what I needed! \$\endgroup\$
    – Carter81
    Commented Sep 13, 2013 at 13:01
5
\$\begingroup\$

I have a tangentially related answer in here, but the general idea is that, if you're loading and drawing textures at different times (you're not loading additional textures while you're rendering), then there are two places where what you do will affect your performance:

Loading time:

This is the moment where you upload your textures into memory. The entire amount of data you are sending to VRAM is what will mostly define how long your loading time will be. Making your textures have smaller formats, like RGBA4444, will make this faster. However, unless you're uploading textures in the high hundreds of megabytes to VRAM, you will probably not be having a bottleneck in here. If you do, a nice loading screen can ease the wait.

Joining your textures into atlases will have little effect, as the entire amount of information you're sending into VRAM will be the same. In fact, if you're atlasing your textures, and you have to leave empty spaces in your atlases, then you will be actually sending more data into VRAM, and therefore this part will be slower!

Rendering performance:

Once all your textures are in VRAM, the amount of textures you have will not affect the rendering performance. There are four elements that do affect your rendering performance:

  1. Render state changes: Each time you change the image you want to render from, will seriously increase the time required to render it. In general, you want to minimize the amount of state changes, and you can reduce the amount of state changes by grouping several images that you will consecutively draw, into a texture atlas.

    Just atlasing is not enough. You have to atlas in a way in which state changes are reduced, in order to get performance gains. For example, one may think that having your main character in a sprite sheet will get you a performance gain, but if you're only drawing one sprite from that sprite sheet per frame, you won't be getting any performance gains compared to having each sprite in a separate file.

    Proper atlasing is not trivial, but in general you can safely group sprites from the same layer. For example, having all GUI items in one sprite sheet is a very promising idea, while grouping monsters alphabetically may not.

  2. Draw calls: In general, you may want to keep your draw calls to a minimum. A good rule of thumb is that if there are no render state changes between two draw calls, you can join them into a single draw call. For more advanced performance gains, you can use, say, 8 texture samplers, and group draw calls for every 8 textures, so you only have to change textures every 8 textures.

  3. Triangle count: In fact, the more triangles you draw, the longer it will take to draw them. However, in modern computers, and for most 2D games, you will be very far away from maxing this out. You can safely draw hundreds of thousands of sprites per frame and still get insanely good framerates. You will probably be more CPU-bound if you're drawing extreme amounts of sprites before you get problems with your GPU.

  4. API settings: If you're doing everything right, and you're still getting strangely low framerates, check the settings with which you're drawing your sprites. I don't know SFML, but for example, in Direct3D 9, creating a vertex buffer with D3DUSAGE_DYNAMIC, or in D3DPOOL_MANAGED can easily increase your rendering times tenfold. Of course, using vSync will cap your framerate at the refresh rate of your monitor. Also, using non-aligned FVFs may decrease performance in some GPUs. This too, is for Direct3D 9.

    In your case, check the documentation for the API that you're using.

If you only have a low to moderate amount of textures (less than 1GB), and you're drawing low amounts of sprites (less than a million per frame), then the first thing I would look is at changing the API settings, and then reducing the amount of render states and draw calls.

\$\endgroup\$
10
  • \$\begingroup\$ If I don't care about load times, am I to assume that unless I run out of RAM or VRAM, I should just load everything into memory beforehand? \$\endgroup\$
    – Carter81
    Commented Sep 13, 2013 at 12:15
  • \$\begingroup\$ I am only concerned with everything BECAUSE I am afraid of running low on RAM/VRAM. I don't know why, but it petrifies me that users playing my game will crash whenever they try to load into an area that has too many unique sprites, or will crash whenever too many characters walk onto their screen. If I am not mistaken and each individual sprite consumes 96KB, then if each unique character has 15 animations, 5 directions, and average 24 frames per animation- each individual character fully loaded is 173MB. There may be 10, maybe even more, unique characters on screen at once. \$\endgroup\$
    – Carter81
    Commented Sep 13, 2013 at 12:17
  • \$\begingroup\$ @KromStern: If you atlas, and you have to leave empty spaces (padding), then the data will be larger, and therefore loading times will be longer. It is clear that the cause of longer loading times is the empty space, and that total loading time is related to the total amount of data, and not the amount of textures. I don't see anything misleading in there, and I think that a person with enough knowledge to understand the original question, will be able to join the dots and make his own conclusions for all cases when the textures and atlases are PoT and nPoT. \$\endgroup\$ Commented Sep 13, 2013 at 12:19
  • 1
    \$\begingroup\$ @Carter81: You need to pick your target hardware configuration, like "i5, 1gb RAM, NVidia GT260, 400mb hdd" and work from that. There's always going to be PCs that are weaker and have less RAM. \$\endgroup\$
    – Kromster
    Commented Sep 13, 2013 at 12:19
  • \$\begingroup\$ Obviously, they don't need all 15 animations at any given time. However, what if all 10 unique characters go into "Combat Mode" at the same time, and thus require 5 sets of animations (walk, run, idle, non-combat etc.) to be swapped out with 5 more (combat walk, combat idle, combat etc.)? I am scared swapping textures because when I tried doing that with SFML, it created a noticeable freeze or pause of the client when switching texture atlases. I don't know what my bottleneck would be with the various strategies to handle so many sprites. \$\endgroup\$
    – Carter81
    Commented Sep 13, 2013 at 12:20

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .