Game Development Community

UnChaos : Texture packing tool

by Sven Bergström · in iTorque 2D · 11/20/2009 (6:10 pm) · 42 replies

One of the necessities of working with mobile and 2d games in general is using the best possible setup for images and imagemaps. Packing images into sprite sheets can be tedious, and if one change arises its a pain in the ass to redo everything, and readjust all image source rects / spaces again.

Enter UnChaos :

img25.imageshack.us/img25/2969/screenshot6ls.jpg
UnChaos takes images from a folder, and packs them using clever algorithms to pack the images as tight as it can get. Not just a configurable tool for saving a packed image - it also saves out a source rects text file and an alpha mask.

Usage

To use the tool, you give it a directory to process (move the images into a folder for conversion). You can see below that my images are all arbitrary sizes and there is no restriction with power of two or square for pvr. Only my end result needs to have a square power of two size. It also supports padding/cell/margining the images as well.

img35.imageshack.us/img35/4228/screenshot4dx.jpg
The prefix and suffix options allow easy conversion to code. The output in the text file can be directly variables.

img163.imageshack.us/img163/4633/screenshot10v.jpg
For reference, it is GOOD PRACTICE and an important thing to remember that changing repetitive things is annoying and slow. Use variables to store the values - requiring only one change for all uses of thse sprite!!! This is crucial too :)

The results


A kick ass easy to use, readily packed image for PVR, and for engine use on iPhone.

img163.imageshack.us/img163/5719/screenshot8s.jpg

Using the results in game


Currently in your version, t2dStaticSprite fully supports this system.


new t2dImageMapDatablock(allShipsImageMap) {
      imageName = "~/data/images/allShips";
      ...

  new t2dStaticSprite(pShip) {
      imageMap = "allShipsImageMap";
      frame = "0";
      mUseSourceRect = "1";
      sourceRect = "0 0 44 18";
      ...

   new t2dStaticSprite(BigBoss) {
      imageMap = "allShipsImageMap";
      frame = "0";
      mUseSourceRect = "1";
      sourceRect = "0 18 93 48";


You can see how there is now only ONE image map, but multiple references to it.
Some other important notes are that now you have control over 2 images , not 100. so unloading / loading levels and clearing memory is simple! As you can see there are massive benefits to working this way. It also means you can use the editor as normal, working on windows etc - and as soon as the level is done and ready for iPhone, its a small optimisation pass that involves copy paste from the output text file.

function levelCleanup()
{

    pShip.delete();  //MUST remove references first, or no memory is freed!
    BigBoss.delete();

    allShipsImageMap.delete();
}

You may also noticed that i had "hardcoded the values" into the sprites. Instead, using the text file as is :

mUseSourceRect = "1";
      sourceRect = $SR_BG_GROUND_04;

Some animation info
Animations are normally the same size frames, meaning you can just use the normal CELL image map type, and let TGB offer you frames to use.

Some other interesting uses is packing normal animation sequences into one image, the sizes can be the same to make normal CELL sheets from image sequences.


Download


Download

Some notes :

Windows only (atm, i am porting it to OSX).

Credit Goes to Diorgo Jonkers (Luma Arcade) and uses BGMLib.
http://members.cox.net/scottheiman/bmglib.htm

Disclaimer :

This tool is not an official tool. It is provided as convenience and is most likely not connected to any support relationship. post issues you have in this thread.
Page «Previous 1 2 3 Last »
#1
11/20/2009 (6:22 pm)
What a fantastic name for a tool!
#2
11/20/2009 (6:32 pm)
Cool! it sounds like all of our smaller objects will get unified in a single spritesheet. Very cool, Sven and Diorgo!
#3
11/20/2009 (6:36 pm)
Joe, for performance they MUST! Its imperative. Thats why this is here :)
#4
11/20/2009 (6:37 pm)
This seems really cool! Can you give an example of how it can be implemented? How does iTGB use arbitrary rects from a single image maps? I haven't seen this anywhere. The only options I can see are same-sized cells.
#5
11/20/2009 (6:38 pm)
David, i will edit the main post to demonstrate. Good idea. Ok, added a section for using it in the game.
#6
11/20/2009 (6:49 pm)
What a valuable resource.
Thanks for the goodies.
Already converted half my sprites.
I had to separate my images between scenegraph and gui.
#7
11/20/2009 (8:19 pm)
Thanks Sven! :)
#8
11/20/2009 (8:20 pm)
Cool that you're thinking about OSX as well... Im going to try this PC version right now.
#9
11/21/2009 (1:48 am)
Very nice tool :)

Quote:Joe, for performance they MUST! Its imperative. Thats why this is here :)

Actually no
I tried combining the various image used on the tilemaps and the consequence was a drop of performance not a raise of performance.
I assume thats highly related to the fact that T2Di does not batch anything, which means that switches of now significantly larger textures cause a much worse slowdown than micro texture switches would have.

Texture combination only makes sense when it comes to animations and alike.
#10
11/21/2009 (3:02 am)
Perhaps tilemaps do something slow, because from experience(and logic) the packing of images is the best way to get performance from the engine. Without packed maps we were losing quite a lot of mspf doing stuff that multiple images caused, switching states and binding ad unbinding states is ALWAYS slower on the graphics hardware.

Ill do some benchmarking sometime and post the results but if you look below, the FPS is at maximum power on a normal second gen device. It is from experience that we found the performance increase coming from packing images, more images = slower performance.

img688.imageshack.us/img688/5476/img0014c.png
Posted with permission!
#11
11/21/2009 (4:13 am)
The problem with the normal second gen device is that its cpu is significantly faster and T2Di is CPU capped not GPU.

But yes I agree and suspect the Tilemap and Particle Handling for the slowdown (they create new vertex arrays for each tile / particle to replicate GL_QUAD, which bogs down the cpu due to the workload for the driver)


For animated sprites or generally just sprites, I agree and its clear that single textures of a balanced size (I would potentially avoid 1024x1024 unless your whole scene fits on it) likely offers the best performance as it reduces the amount of texture switches
#12
11/21/2009 (6:58 am)
Ill post more info sometime in a performace discussion thread :) but for reference - on a 1st gen iPod touch and iPhone 3g the game runs at 45 - 50 FPS as well.
#13
11/22/2009 (5:33 pm)
I have modified GuiBitmapButtonCtrl to use this new functionality. Yay for source rects.

Here is the modified code for those interested:

in GuiBitmapButtonCtrl.h, add the following to the protected section:
// Texture optimization
bool mUseSourceRects;
RectI mSourceRectNormal;
RectI mSourceRectHilight;
RectI mSourceRectDepressed;
RectI mSourceRectInactive;

void renderButtonSR(TextureHandle &texture, Point2I& offset, const RectI& updateRect, const RectI& sourceRect);

In GuiBitmapButtonCtrl.cpp, add this to the initialization list:
GuiBitmapButtonCtrl::GuiBitmapButtonCtrl()
:   mUseSourceRects(false),
    mSourceRectNormal(0, 0, 0, 0),
    mSourceRectHilight(0, 0, 0, 0),
    mSourceRectDepressed(0, 0, 0, 0),
    mSourceRectInactive(0, 0, 0, 0)

Then add this to initPersistFields:
addField("useSourceRects", TypeBool, Offset(mUseSourceRects, GuiBitmapButtonCtrl));
addField("sourceRectNormal", TypeRectI, Offset(mSourceRectNormal, GuiBitmapButtonCtrl));
addField("sourceRectHilight", TypeRectI, Offset(mSourceRectHilight, GuiBitmapButtonCtrl));
addField("sourceRectDepressed", TypeRectI, Offset(mSourceRectDepressed, GuiBitmapButtonCtrl));
addField("sourceRectInactive", TypeRectI, Offset(mSourceRectInactive, GuiBitmapButtonCtrl));

Modify the following in setBitmap (this avoids searching for textures that don't exist):
mTextureNormal = TextureHandle(buffer, BitmapTexture, true);
if (!mTextureNormal)
{
   dStrcpy(p, "_n");
   mTextureNormal = TextureHandle(buffer, BitmapTexture, true);
}
if(!mUseSourceRects)
{
    dStrcpy(p, "_h");
    mTextureHilight = TextureHandle(buffer, BitmapTexture, true);
    if (!mTextureHilight)
        mTextureHilight = mTextureNormal;
    dStrcpy(p, "_d");
    mTextureDepressed = TextureHandle(buffer, BitmapTexture, true);
    if (!mTextureDepressed)
        mTextureDepressed = mTextureHilight;
    dStrcpy(p, "_i");
    mTextureInactive = TextureHandle(buffer, BitmapTexture, true);
    if (!mTextureInactive)
        mTextureInactive = mTextureNormal;
}

Then modify the onRender like so, replace the old switch statement with this one:
if(mUseSourceRects)
{
   switch (state)
   {
   case NORMAL:      
      renderButtonSR(mTextureNormal, offset, updateRect, mSourceRectNormal); 
      break;
   case HILIGHT:     
      renderButtonSR(mTextureNormal, offset, updateRect, mSourceRectHilight.isValidRect() ? mSourceRectHilight : mSourceRectNormal); 
      break;
   case DEPRESSED:   
      renderButtonSR(mTextureNormal, offset, updateRect, mSourceRectDepressed.isValidRect() ? mSourceRectDepressed : mSourceRectNormal); .
      break;
   case INACTIVE:    
      renderButtonSR(mTextureNormal, offset, updateRect, mSourceRectInactive.isValidRect() ? mSourceRectInactive : mSourceRectNormal); 
      break;
   }
}
else
{
    switch (state)
    {
    case NORMAL:      
       renderButton(mTextureNormal, offset, updateRect); 
       break;
    case HILIGHT:     
       renderButton(mTextureHilight ? mTextureHilight : mTextureNormal, offset, updateRect); 
       break;
    case DEPRESSED:   
       renderButton(mTextureDepressed, offset, updateRect); 
       break;
    case INACTIVE:    
       renderButton(mTextureInactive ? mTextureInactive : mTextureNormal, offset, updateRect); 
       break;
    }
}

Finally, add the new method, renderButtonSR:
void GuiBitmapButtonCtrl::renderButtonSR(TextureHandle &texture, Point2I& offset, const RectI& updateRect, const RectI& sourceRect)
{
if (texture)
{
    RectI rect(offset, mBounds.extent);
    dglClearBitmapModulation();
    dglDrawBitmapStretchSR(texture, rect, sourceRect, 0);
    renderChildControls( offset, updateRect);
}
else
    Parent::onRender(offset, updateRect);
}

To use this, simply follow similar steps as with the other rect stuff, but this time there is up to 4 different source rects.

Example:
new GuiBitmapButtonCtrl() {
      // Stuff...
      
      useSourceRects = "1";
      sourceRectNormal = $SR_BUTTON;
      sourceRectDepressed = $SR_BUTTON_D;
      sourceRectInactive = $SR_BUTTON_I;    
      // optional, not much point on iPhone
      //sourceRectHilight = $SR_BUTTON_H;   
      
      bitmap = "~/data/images/my_nice_big_texture";
   };
#14
11/22/2009 (5:41 pm)
David just got another rank in Awesome.
#15
11/22/2009 (6:35 pm)
Oooh, very nice Sven and David! There's just too darn much cool stuff to do sometimes!
#16
11/23/2009 (2:46 am)
Thanks David too ;) That might even make it in as a last minute change for next release! Gotta love a helpful community !

(take that as a challenge, the rest of you slackers :D)
#17
12/11/2009 (1:48 pm)
Could someone put a working code example because I can't get this to work. The t2dStaticSprite I create is just the ImageMap and sourceRect doesn't appear to do anything.
#18
12/11/2009 (4:59 pm)
I love the program, but using this method eliminates the ability to create objects using the level editor. Is there anyway the program could create Key Image spritesheets with a customizable key color (I see that you can put space between images)? Or would that not be as efficient?
#19
12/13/2009 (10:49 pm)
I tried to get to work but I don't think it is enabled (or at least not in the 1.3Beta build).

The code is in there but have the following defines:
_LUMA_STATICSPRITE_SOURCERECT

I thought recompiling it with those defines enabled in the project file would get things working but it doesn't as when torque starts up it is just crashing out on the onAdd of the any static sprite datablock.

BTW..the original post also has an error mUseSourceRect sprite is the C++ member variable name. The script name is "useSourceRect"
#20
12/15/2009 (5:31 pm)
Same Terence problem!
Page «Previous 1 2 3 Last »