3
\$\begingroup\$

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;

}

\$\endgroup\$

1 Answer 1

3
\$\begingroup\$

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;
}
\$\endgroup\$
5
  • \$\begingroup\$ 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 \$\endgroup\$ Apr 8, 2011 at 15:32
  • \$\begingroup\$ Odd. This has been working on my test phone for quite some time now... Hope this helps! \$\endgroup\$
    – notlesh
    Apr 8, 2011 at 15:44
  • \$\begingroup\$ Yep it works. I needed to use scaleToPO2, otherwise it was very small. Odd as it's already PO2 (512x512). Thanks a lot! \$\endgroup\$ Apr 8, 2011 at 15:48
  • 1
    \$\begingroup\$ int is a 32-bit type. The n = n | (n >> 32); line is therefore useless. \$\endgroup\$ Apr 9, 2011 at 11:30
  • \$\begingroup\$ 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 ); \$\endgroup\$
    – user16316
    May 16, 2012 at 12:06

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .