Spritesheets vs Individual Sprites

Started by
8 comments, last by streamer 11 years, 9 months ago
Hello, I am aware that there is a topic that was very similar to this, but it is really old, so I thought I would start a new one to get new ideas/opinions.
The question that I am asking is what is better to do, use spritesheets or individual sprites, or what is a perfect balance between the two?

I am currently working on a game, and I want to make sure that it is effecient. I actually split up all the characters/npcs/etc, and then halfway through I realized how costly it is to do that. I am using DirectX9, and I believe even with OpenGL, using SetTexture is very costly. The pros of having individual tiles in their own image file is that you won't use as much memory because you only have loaded what you needed. It is very ineffecient because SetTexture is costly and it loses a lot of performance.

Using one big tileset is also a bad idea because some graphics cards have a texture maximum size and if you're tileset is big enough(which is very possible) then you're application will not be compatible with certain people's computers. It also uses a lot of memory to do this because you have all the tiles in memory but you are probably only using 5-10% of the actual loaded textures. A pro of this is that it will be effecient performance-wise because you only have to call SetTexture once and you just clip what you need.

I believe that to be the most effecient, I will need to create a perfect balance of the two. I am thinking that I want to make each texture a 512x512 size and once it gets to that size, then I will create a second texture to continue on. Most graphics cards will be able to support a 512x512 texture, it gives you a performance boost because the spritesheets will be split up by category, so I will be calling SetTexture once, render category, and move on. It is a bit of a memory waste, but not as much as the one big tileset would be. I would much prefer to have my application render faster than worry about memory as much. Most computers can handle the memory, and as long as it isn't ridiculous, then it will be fine.

What are your guys' thoughts on this? This is meant to be a conceptual topic, but it seems like it is leaning towards my problem. I just want to hear what you guys think and maybe that will help influence me to reach a conclusion about my problem. And so you guys know, tilesheet = spritesheet, tiles = sprites. I use them interchangeably for some reason.
Advertisement
I'm working on a 3D space game that, for the enemy spaceships, uses sprites (pre-rendered from every angle) instead of models. All spaceships have 544 angles (32 angles of yaw, 17 of pitch), each individual sprite is 256x256. We used to just load in 544 single 256x256 texture files in the beginning - and on decent hardware that would take at the very least 30 seconds to load. When you have 10+ different spaceship classes in a mission, that made for absolutely unacceptable loading times. I then wrote a tool that would read in the 544 sprites and output 17 spritesheets for each ship (ie. one sheet for each pitch rotation), each spritesheet containing 32 256x256 sprites (all the yaws in the pitch of the spritesheet) - so 2048x1024. This reduced loading times down to 2-3 seconds per ship.

In a nutshell: how effective spritesheets are over individual sprites depends on your game, my game is probably one of the strongest cases you'll find, if you're talking about 16 8x8 sprites, however, it probably won't make that much sense for example. How large you can make your spritesheets depends on the hardware you're targeting, you mention 512x512, that's extremely conservative. If you're not aiming at supporting really old GPUs you can certainly go up to 4096x4096, I'd be willing to bet that all GPUs that came out like 6 years or earlier maybe(?) will support this texture size. And then there's even more to it, if you look at memory consumption, you need to consider using DXTn texture compression. This will also speed up loading times in case you're converting each loaded texture from their PNG or JPEG format to RGB(A) at the moment. Last but not least, make sure to use a vertex shader for selecting the portion of the spritesheet you actually want. Doing this on the CPU is a really bad idea these days, for obvious reasons.

Here's an example of a vertex shader that does this (HLSL):
[source lang="cpp"]
float4x4 mat_worldviewproj;
float4 rect;

struct vs_input
{
float4 position : POSITION;
float2 uv : TEXCOORD0;
};

struct vs_output
{
float4 position : POSITION;
float2 uv : TEXCOORD0;
};

vs_output MyVertexShader ( vs_input input )
{
vs_output output;

// transform vertex into screen space for rendering
output.position = mul ( float4 ( input.position.xyz, 1.0f ), mat_worldviewproj );

// uv atlas (u/v/w/h of sprite in sheet, all range 0..1)
output.uv.x = ( input.uv.x * rect.z ) + rect.x;
output.uv.y = ( input.uv.y * rect.w ) + rect.y;

return output;
}
[/source]

Just feed it the vector4 "rect" with the top left corner coordinates as the first two elements and then the width and height of the portion you want from the spritesheet (all in range 0..1 - this way the size of your texture is irrelevant to the shader).

Gone on a bit of a tangent there, hope this helps you in some way!
There is one "extended" version of using sprite sheets, and that is using of 3D textures. On programming side you have one texture call, with defining one more texture coordinate beside u and v.

Probably this will be the fastes way.
The bigger gain you're going to get from using a spritesheet (you'll also find them referred to as a texture atlas) is not so much from reduced SetTexture calls but more from reduced Draw(Indexed)Primitive calls - if state doesn't need to be changed then you're able to get much more efficient draw call batching which D3D9 really needs.

That doesn't mean that reducing the number of SetTexture calls is something you can ignore - it still has benefit - but just that the primary advantage is going to be elsewhere.

If you're using ID3DXSprite you'll find that it automatically does the required draw call batching for you; if not you'll need to do a little extra work yourself (but it's not that difficult).

Regarding texture size, with SM2.0 hardware you can generally rely on at least 2048x2048 textures being available, although SM2.0 doesn't specify a texture size (I've seen it go down to 1024x1024 but never lower). If you're coding to SM3.0 you're guaranteed at least 4096x4096 - source: http://msdn.microsoft.com/en-us/library/bb219845%28v=VS.85%29.aspx

Direct3D has need of instancing, but we do not. We have plenty of glVertexAttrib calls.

Thanks d k h, that story is really comforting to know. It tells me that spritesheets are the way to go for sure. I am wanting to support older graphic cards, so I am trying to pick a relatively low texture size that a lot of graphic cards will support. From what mhagain says, I should be safe with 1024x1024, which sounds good.

Even if I were to use 4096x4096 or whatever texture size, I am wanting to answer the question "What if I exceed that size with my textures?" I am trying to think on a bigger scale(which in the game I'm making, I will certainly exceed 1024x1024 a few times). I could make another textures to hold this data but whenever I am in the rendering layer, I would most likely end up switching textures from the first page to the second page. As I slowly add on more and more "pages" of textures, the more I have to switch textures between them.

Any ideas?

Thanks d k h, that story is really comforting to know. It tells me that spritesheets are the way to go for sure. I am wanting to support older graphic cards, so I am trying to pick a relatively low texture size that a lot of graphic cards will support. From what mhagain says, I should be safe with 1024x1024, which sounds good.

Even if I were to use 4096x4096 or whatever texture size, I am wanting to answer the question "What if I exceed that size with my textures?" I am trying to think on a bigger scale(which in the game I'm making, I will certainly exceed 1024x1024 a few times). I could make another textures to hold this data but whenever I am in the rendering layer, I would most likely end up switching textures from the first page to the second page. As I slowly add on more and more "pages" of textures, the more I have to switch textures between them.

Any ideas?


As I stated before 3D textures are way to go, if you are concerned about switching textures.

If you feel any problem with 3D textures (yes they may not work on really old hardware) then your only way to go is to determine maximum texture size available on computer through caps, then dynamically create bigger texture sheet.

Say you have 4 textures with 1024x1024 size and maximum available texture size on computer is 4096x4096, then you during loading process putt all 4 textures on one bigger. Of course you will need to have special class for handling UV of dynamically created textures but that is another story.

But as mhagain stated probably you will not have too much problems with texture switching, biggest bottleneck you will probably have is a number of draw calls.

Just try to make 4000 BeginSprite/DrawSprite/EndSprite calls then make same with 1 BeginSprite, 4000 DrawSprite and 1 EndSprite call,l and you will understand the problem.

[quote name='addy914' timestamp='1341939612' post='4957660']
Thanks d k h, that story is really comforting to know. It tells me that spritesheets are the way to go for sure. I am wanting to support older graphic cards, so I am trying to pick a relatively low texture size that a lot of graphic cards will support. From what mhagain says, I should be safe with 1024x1024, which sounds good.

Even if I were to use 4096x4096 or whatever texture size, I am wanting to answer the question "What if I exceed that size with my textures?" I am trying to think on a bigger scale(which in the game I'm making, I will certainly exceed 1024x1024 a few times). I could make another textures to hold this data but whenever I am in the rendering layer, I would most likely end up switching textures from the first page to the second page. As I slowly add on more and more "pages" of textures, the more I have to switch textures between them.

Any ideas?


As I stated before 3D textures are way to go, if you are concerned about switching textures.

If you feel any problem with 3D textures (yes they may not work on really old hardware) then your only way to go is to determine maximum texture size available on computer through caps, then dynamically create bigger texture sheet.

Say you have 4 textures with 1024x1024 size and maximum available texture size on computer is 4096x4096, then you during loading process putt all 4 textures on one bigger. Of course you will need to have special class for handling UV of dynamically created textures but that is another story.

But as mhagain stated probably you will not have too much problems with texture switching, biggest bottleneck you will probably have is a number of draw calls.

Just try to make 4000 BeginSprite/DrawSprite/EndSprite calls then make same with 1 BeginSprite, 4000 DrawSprite and 1 EndSprite call,l and you will understand the problem.
[/quote]

I believe I am already using 3D textures? I am doing this in C# using SharpDX, and I only use the Direct3D9 class, so I assume it's a 3D texture. I also have a function to call BeginScene and one to call EndScene. So I call BeginScene, draw everything that is needed on the screen, and then call EndScene & Present to display it. The number of draw calls is inevitable because it doesn't matter if I use a spritesheet or not, I will have to call the draw function the same amount of times.(approximately, not enough to notice)

I think I will make another test program and get some data seeing how slow SetTexture is.

"I could make another textures to hold this data but whenever I am in the rendering layer, I would most likely end up switching textures from the first page to the second page. As I slowly add on more and more "pages" of textures, the more I have to switch textures between them."
I am still not sure what to do about this issue.

I actually didn't think of making a bunch of 1024x1024 spritesheets, and then combining them to the graphic cards max texture size. That will make it more efficient. I can also spit out an error message if they don't have at least a 1024x1024 max texture size.
I am not familiar with SharpDX, but you are probably not using 3D textures out of box. 3D textures are made like bunch of 2d texture "slices", so if you have for example 10 pcs of 1024x1024 textures, you can make 1 3D textures with 10 layers of 1024x1024 textures. Then you make 1 SetTexture call and use 3 coordinates for mapping, instead of u and v coordinate for "ordinary" 2D textures.
Oh okay, that makes sense, then no I do not have that. I will have to learn how to do that. Is there a limit of how big the 3D texture can be? The width/height maximum texture size will be the same as a 2D texture, but what about depth-wise?
I don't think there is any restriction about it (not 100% sure), probably the memory is the limit.

However if I remember well, I have read somewhere that 3th dimension on texture also should be power of 2. So it is wise to have 1,2,4,8,16 etc layers.

This topic is closed to new replies.

Advertisement