Take the 2-minute tour ×
Game Development Stack Exchange is a question and answer site for professional and independent game developers. It's 100% free, no registration required.

I'm very new to Android and OpenGL coding (I have previously used ogre3d). I am trying to find an efficient way to load PNG textures. It is currently taking around 8 secs to load 3 512x512 textures on a fairly fast phone (Motorola Defy)

There seems to be a problem with the texture being upside-down (I used a Matrix to flip it below). Secondly, the order of the colour channels are incompatible, therefore each pixel needs changed. This (I believe) is the cause of the slowness, is there a way to make the BitmapFactory load it in a channel order that is compatible with opengl? Is there a better approach here?

Here is the code: -

// Will load a texture out of a drawable resource file, and return an OpenGL texture ID:
private int loadTexture(GL10 gl, Context context, int resource, boolean loadMipMaps) {
    // We need to flip the textures vertically:
    Matrix flip = new Matrix();
    flip.postScale(1f, -1f);

    // This will tell the BitmapFactory to not scale based on the device's pixel density:
    BitmapFactory.Options opts = new BitmapFactory.Options();
    opts.inScaled = false;

    // Load up, and flip the texture:
    Bitmap temp = BitmapFactory.decodeResource(context.getResources(), resource, opts);
    Bitmap bmp = Bitmap.createBitmap(temp, 0, 0, temp.getWidth(), temp.getHeight(), flip, true);
    temp.recycle();

    int id = loadTextureFromBmp(gl,bmp, true);

    bmp.recycle();

    return id;
}

protected static int loadTextureFromBmp(GL10 gl, Bitmap bmp, boolean reverseRGB) {
    ByteBuffer bb = ByteBuffer.allocateDirect(bmp.getHeight()*bmp.getWidth()*4);
    bb.order(ByteOrder.nativeOrder());
    IntBuffer ib = bb.asIntBuffer();


    for (int y=bmp.getHeight()-1;y>-1;y--)
            for (int x=0;x<bmp.getWidth();x++) {
                    if (reverseRGB) {
                            int px = bmp.getPixel(x,y);
                            int alpha = (px & 0xFF000000) >> 24;
                            int red = (px & 0xFF0000)>>16;
                            int green = (px & 0xFF00)>>8;
                            int blue = (px & 0xFF);
                            ib.put((alpha << 24) | (blue << 16) | (green<<8) | (red));
                            //ib.put(Integer.reverseBytes(bmp.getPixel(x, y)));
                    }
                    else {
                            ib.put(bmp.getPixel(x,y));
                    }
            }
    ib.position(0);
    bb.position(0);

    int[] tmp_tex = new int[1];

    gl.glGenTextures(1, tmp_tex, 0);
    int tex = tmp_tex[0];
    gl.glBindTexture(GL10.GL_TEXTURE_2D, tex);
    gl.glTexImage2D(GL10.GL_TEXTURE_2D, 0, GL10.GL_RGBA, bmp.getWidth(), bmp.getHeight(), 0, GL10.GL_RGBA, GL10.GL_UNSIGNED_BYTE, bb);
    gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_LINEAR);
    gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR);

    return tex;

}

share|improve this question
add comment

1 Answer 1

up vote 2 down vote accepted

Here's my code to load from a bitmap, which doesn't suffer from the same problems you describe, and looks like it will accomplish the same thing:

/**
 * Helper method to load a GL texture from a bitmap
 *
 * Note that the caller should "recycle" the bitmap
 *
 * @return the ID of the texture returned from glGenTextures()
 */
public static int loadGLTextureFromBitmap( Bitmap bitmap, GL10 gl ) {

    // Generate one texture pointer
    int[] textureIds = new int[1];
    gl.glGenTextures( 1, textureIds, 0 );

    // bind this texture
    gl.glBindTexture( GL10.GL_TEXTURE_2D, textureIds[0] );

    // Create Nearest Filtered Texture
    gl.glTexParameterf( GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_LINEAR );
    gl.glTexParameterf( GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR );

    gl.glTexParameterf( GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_S, GL10.GL_REPEAT );
    gl.glTexParameterf( GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_T, GL10.GL_REPEAT );

    // Use the Android GLUtils to specify a two-dimensional texture image from our bitmap
    GLUtils.texImage2D( GL10.GL_TEXTURE_2D, 0, bitmap, 0 );

    return textureIds[0];
}

and to load from a resource, I call it via this method (i have another similar method that will create a bitmap from a file):

/**
 * Create a texture from a given resource
 * 
 * @param resourceID the ID of the resource to be loaded
 * @param scaleToPO2 determines whether the image should be scaled up to the next highest power
 * of two, or whether it should be "inset" into such an image. Having textures that are
 * dimensions of some power-of-two is critical for performance in opengl.
 *
 * @return the ID of the texture returned from glGenTextures()
 */
public static int loadGLTextureFromResource( int resourceID, Context context, boolean scaleToPO2 ) {

    // pull in the resource
    Bitmap bitmap = null;
    Resources resources = context.getResources();

    Drawable image = resources.getDrawable( resourceID );
    float density = resources.getDisplayMetrics().density;

    int originalWidth = (int)(image.getIntrinsicWidth() / density);
    int originalHeight = (int)(image.getIntrinsicHeight() / density);

    int powWidth = getNextHighestPO2( originalWidth );
    int powHeight = getNextHighestPO2( originalHeight );

    if ( scaleToPO2 ) {
        image.setBounds( 0, 0, powWidth, powHeight );
    } else {
        image.setBounds( 0, 0, originalWidth, originalHeight );
    }

    // Create an empty, mutable bitmap
    bitmap = Bitmap.createBitmap( powWidth, powHeight, Bitmap.Config.ARGB_4444 );
    // get a canvas to paint over the bitmap
    Canvas canvas = new Canvas( bitmap );
    bitmap.eraseColor(0);

    image.draw( canvas ); // draw the image onto our bitmap

    int textureId = loadGLTextureFromBitmap( bitmap );

    bitmap.recycle();

    return textureId;
}

and lastly, here's a utility to get the next-highest-power-of-two:

/**
 * Calculates the next highest power of two for a given integer.
 *
 * @param n the number
 * @return a power of two equal to or higher than n
 */
public static int getNextHighestPO2( int n ) {
    n -= 1;
    n = n | (n >> 1);
    n = n | (n >> 2);
    n = n | (n >> 4);
    n = n | (n >> 8);
    n = n | (n >> 16);
    n = n | (n >> 32);
    return n + 1;
}
share|improve this answer
    
Thanks user6592, I will give it a go. Although I notice you use GLUtils.texImage2D. I was using it previously and it worked on the emulator, but wouldn't work on the device. Hopefully it was a different problem. Thanks! Ash –  Ash McConnell Apr 8 '11 at 15:32
    
Odd. This has been working on my test phone for quite some time now... Hope this helps! –  stephelton Apr 8 '11 at 15:44
    
Yep it works. I needed to use scaleToPO2, otherwise it was very small. Odd as it's already PO2 (512x512). Thanks a lot! –  Ash McConnell Apr 8 '11 at 15:48
    
int is a 32-bit type. The n = n | (n >> 32); line is therefore useless. –  Sam Hocevar Apr 9 '11 at 11:30
    
Excuse me.. I pretty new to android & opengl... your call to the loadGLTextureFromBitmap in following line of code, dont you need to pass in the gl object as 2nd parameter.. it should prompt an error right? int textureId = loadGLTextureFromBitmap( bitmap ); –  user16316 May 16 '12 at 12:06
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.