7

So I've set up my framework in a neat little system to wrap SDL, openGL and box2D all together for a 2D game.

Now how it works is that I create an object of "GameObject" class, specify a "source PNG", and then it automatically creates an openGL texture and a box2d body of the same dimensions.

Now I am worried about if I start needing to render many different textures on screen.

Is it possible to load in all my sprite sheets at run time, and then group them all together into one texture? If so, how? And what would be a good way to implement it (so that I wouldn't have to manually be specifying any parameters or anything).

The reason I want to do it at run time and not pre-done is so that I can easily load together all (or most) of the tiles, enemies etc.. of a certain level into this one texture, because every level won't have the same enemies. It'd also make the whole creating art process easier.

genpfault
  • 51,148
  • 11
  • 85
  • 139
Omar Shehata
  • 1,054
  • 14
  • 18
  • How many is _many_? I would worry more about reusing existing textures -- does the engine handle this? – Stefan Hanke Mar 28 '12 at 04:07
  • I think at maximum I'd have like 100 different textures at any one time, so that would be 100 binding operations. Also, when you say reusing existing textures, do you mean like, if I have a certain tile that's used 12 times, the engine should draw it 12 times while binding only once instead of rebinding right? That would be a good idea..any way to check which texture is currently bound so I don't rebind it? Or do I have to make a system to draw the same textures together so I reduce binding...but what if I want to draw them in a certain order for depth sorting? – Omar Shehata Mar 28 '12 at 04:35
  • Yeah, right -- that saves the bindings. The second comment was more targetted at having textures with the same data, forget that. – Stefan Hanke Mar 28 '12 at 05:17

1 Answers1

12

There are likely some libraries that already exist for creating texture atlases (optimal packing is a nontrivial problem) and converting old texture coordinates to the new ones.

However, if you want to do it yourself, you probably would do something like this:

  • Load all textures from disk (your "source PNG") and retrieve the raw pixel data buffer,
  • If necessary, convert all source textures into the same pixel format,
  • Create a new texture big enough to hold all the existing textures, along with a corresponding buffer to hold the pixel data
  • "Blit" the pixel data from the source images into the new buffer at a given offset (see below)
  • Create a texture as normal using the new buffer's data.
  • While doing this, determine the mapping from "old" texture coordinates into the "new" texture coordinates (should be a simple matter of recording the offsets for each element of the texture atlas and doing a quick transform). It would probably also be pretty easy to do it inside a pixel shader, but some profiling would be required to see if the overhead of passing the extra parameters is worth it.

Obviously you also want to check to make sure you are not doing something silly like loading the same texture into the atlas twice, but that's a concern that's outside this procedure.


To "blit" (copy) from the source image to the target image you'd do something like this (assuming you're copying a 128x128 texture into a 512x512 atlas texture, starting at (128, 0) on the target):

unsigned char* source = new unsigned char[ 128 * 128 * 4 ]; // in reality, comes from your texture loader
unsigned char* target = new unsigned char[ 512 * 512 * 4 ];

int targetX = 128;
int targetY = 0;

for(int sourceY = 0; sourceY < 128; ++sourceY) {
    for(int sourceX = 0; sourceX < 128; ++sourceX) {
        int from = (sourceY * 128 * 4) + (sourceX * 4); // 4 bytes per pixel (assuming RGBA)
        int to = ((targetY + sourceY) * 512 * 4) + ((targetX + sourceX) * 4); // same format as source

        for(int channel = 0; channel < 4; ++channel) {
            target[to + channel] = source[from + channel];
        }
    }
}

This is a very simple brute force implementation: there are much faster, more succinct and more clever ways to copy an array, but the idea is that you are basically copying the contents of the source texture into the target texture at a given X and Y offset. In the end, you will have created a new texture which contains the old textures in it.

If the indexing math doesn't make sense to you, think about how a 2D array is actually indexed inside a 1D space (such as computer memory).

Please forgive any bugs. This isn't production code but instead something I wrote without checking if it compiles or runs.


Since you're using SDL, I should mention that it has a nice function that might be able to help you: SDL_BlitSurface. You can create an SDL_Surface entirely within SDL and simply use SDL_BlitSurface to copy your source surfaces into it, then convert the atlas surface into a GL texture.

It will take care of all the math, and can also do a format conversion for you on the fly.

Community
  • 1
  • 1
ravuya
  • 8,586
  • 4
  • 30
  • 33
  • Thank you for the answer ravuya! I think I know how to do all of what you stated except: ""Blit" the pixel data from the source images into the new buffer at a given offset" Any links or direction on how I would do this code-wise would be appreciated, and again, thanks! – Omar Shehata Mar 28 '12 at 04:38
  • I added a quick explanation of what I mean by "blitting" - it's effectively copying one array into the other. – ravuya Mar 28 '12 at 05:06
  • Wait so basically, the textures in openGL contain their pixel data (called buffers?) and what we'd be doing is copying the pixel data from each individual texture and throwing them all in a big pixel buffer thing, then apply that to my big texture, effectively copying over all the little textures into the big texture? I think I can handle the math of copying, I'm just not sure what the functions I'd need to use in openGL to access the pixel data. Again, thanks for the help so far, I really appreciate it! – Omar Shehata Mar 28 '12 at 05:20
  • Well...that is brilliant! Hopefully it's as easy as it sounds! Thank you very very much ravuya! That should be all I need to get it working. – Omar Shehata Mar 28 '12 at 05:28
  • 1
    You can completely skip the intermediary blitting part, by using glTexSubImage2D, to upload the smaller images directly to their place in the destination texture. – datenwolf Mar 28 '12 at 10:02
  • That's right too (and is actually how I implemented it years ago), I had forgotten about those arguments to that function. Thanks. – ravuya Mar 28 '12 at 13:31
  • By the way: The word "blit" literally just means "block [memory] transfer", aka "blt". – Ancurio Jun 21 '13 at 12:15