Wednesday, December 28, 2011

(The hard way) OpenGL, Bypass the power of two (POT) restriction

Assuming you have decided to use OpenGL ES for your game and your designer drew all the backgrounds as 324×226 images like the one on the picture 1.





picture 1


During the development you load these backgrounds as textures in the OpenGL and you are happy watching nice graphic results on the emulator.

At some point of the timeline of your project you transfer the application on a real device and suddenly you realize than instead of a nice artwork you see a white square 324×226 (scaled at whatever scale-factor you have calculated according to the physical screen resolution)

What happened actually????

OpenGL ES binds only images where the width and the height are both power of 2 (POT).

This means, only 32×16, 128×64, 8×256 etc images are supported. It does not sound flexible, huh?. It is a certain restriction for your designer (I guess it is you!).

Newer GPUs that support OpenGL version 2.0 or above can load any arbitrary dimension but why to take such a huge risk?

In this tutorial I am showing to you a solution to this issue so to leave your talent free and wake up the hidden Van Gong who lives inside you and forget about any such kind of restrictions.

The main idea is very simple: You feed the engine with images of whatever dimension you want and the procedure that I am going to describe right now will split this image to the minimum amount of images with all their dimensions to be of POT.

In the example above, a background will be split in 12 images:

4 × 2
4 × 32
4 × 64
4 × 128
64 × 2
64 × 32
64 × 64
64 × 128
256 × 2
256 × 32
256 × 64
256 × 128

Like you can see on the picture 2




picture 2

Seeing this picture it is clear what steps has to be executed by an automated procedure:

  1. Express the width and the height of the image as a sum of numbers of only powers of two.

  2. Combine these numbers that sums the width with the numbers that sums the height and we have the dimensions of the new images.

  3. Crop the initial image using these dimensions and get all the part-images as also the coordinates of them in relation of the (0,0) corner of the initial image.

Expressing any natural number k∈N as a sum of powers of two is the same as to represent this number into the binary numeral system. In fact this is the definition of the binary numeral system.

Apply this step in our example:

324 = (101000100)2
= 1×28 + 0×27 + 1×26 + 0×25 +0×24 +0×23 + 1×22 + 0×21 +0×20
= 256 + 64 + 4
= 4 + 64 + 256

similar for the height
226 = (11100010)2
= 27 + 26 + 25 + 21
= 128 + 64 + 32 + 2
= 2 + 32 + 64 + 128

It would be handy for the second step if we had an array for the width and another for the height to store these constants. For example it could be:

for 324, int w[] = {4, 64, 256 }
for 226, int h[] = {2, 32, 64, 128 }

And since the speech is about arrays it raises automatically the need to allocate memory for them before making any operation on them. What we exactly seek is the appearances of ‘1’s in the binary representation of the number. Let’s introduce our first function

int bitCount ( int number)
{
     int ones = 0;
     for ( int i = 0; i < 16; i++ )
     {
          if ( (number&1) == 1 )
               ones++;
      number >>= 1;
     }
     return ones;
}


Nothing special has been used than just a few boring bitwise operations.
(...or you can simply use the Integer.bitCount(int i) static method of Java!!!!)

Now we can allocate memory for our arrays and play safely with them. According to your IDE environment you can use malloc (Ansi C) , NSMutableArray (iPhone) or the “new” operator (Android/Java/C++)

Using the same pattern we can fill the array with values.

void getPowersOfTwo ( int number, int[] array )
{
     int pos = 0;
     for ( int i = 0; i < 16; i++ )
     {
          if ( (number&1) == 1)
          {
               array[pos++] = (int) Math.pow((double) 2, (double) i);
          }
          number >>= 1;
     }
}


Be very careful about the precedence of the operators since pos++ here doesn't have the same effect as ++pos. (Why?)

After we have called this function on both w[] and h[] arrays we loop through their elements to build all pairs we need for the cropping.

There are (w.length)×(h.length) combinations. That means that for our example there will be 3×4=12 images.

For this purpose we introduce two nested “for” loops :

int fromX = 0;
int fromY = 0;
for ( int i = 0; i < w.length; i++ )
{
     for ( int j = 0; j < h.length; j++ )
     {
          System.out.printf("Crop %d x %d from (%d,%d\n", w[i],h[j],fromX,fromY);
          fromY += h[j];
     }
     fromY = 0;
     fromX += w[i];
} 


Inside the body of the second “for” it is where you have to call a special function of the standard graphics library (not the OpenGL) and get the part of the image that you can bind right after as an OpenGL-texture. The printf has everything we need: Where to crop from (and where in the OpenGL mesh to put) and what size to crop.

Of course you have to save also the values of fromX and fromY to a data structure and don't blast them into oblivion after this print-out.

Here are these functions for the main platforms that rule the galaxy nowadays:

Android:
Bitmap android.graphics.Bitmap.createBitmap(Bitmap source, int x, int y, int width, int height) 


iPhone:
CGImageRef image = CGImageCreateWithImageInRect(CGImage source, rect);
UIImage*   uiimage = [UIImage imageWithCGImage:image];


And here is the output of this function on the console.

Crop 4 × 2 from (0,0)
Crop 4 × 32 from (0,2)
Crop 4 × 64 from (0,34)
Crop 4 × 128 from (0,98)
Crop 64 × 2 from (4,0)
Crop 64 × 32 from (4,2)
Crop 64 × 64 from (4,34)
Crop 64 × 128 from (4,98)
Crop 256 × 2 from (68,0)
Crop 256 × 32 from (68,2)
Crop 256 × 64 from (68,34)
Crop 256 × 128 from (68,98)

Is this all what you need? No, but it is the 90%.

I leave the remaining ...90% as an excercise.

Good luck

No comments:

Post a Comment