2

I'm trying to load my textures in order to use them in my game. for now, my game needs 84 image frames in order to work as I expect, but when I load this much of images, it gives me OutOfMemmory Exception.

Is there any better way to load these images?

Obs each one of them have 150x150px on HDPI devices.

the device i'm running it is an Galaxy SII witch I believe has 50mb of heap memory.

Here's where the exception occurs:

    public void loadTextures(GL10 gl) {
    InputStream is[] = new InputStream[textureCount];
    Bitmap bitmap[] = new Bitmap[textureCount];

    is[0] = context.getResources().openRawResource(R.drawable.bg_01);
    is[1] = context.getResources().openRawResource(R.drawable.bg_02);
    is[2] = context.getResources().openRawResource(R.drawable.bg_03);

    is[3] = context.getResources().openRawResource(R.drawable.win_hud);
    is[4] = context.getResources().openRawResource(R.drawable.heart);

    is[5] = context.getResources().openRawResource(R.drawable.skyblue);
    is[6] = context.getResources().openRawResource(R.drawable.heart);
    is[7] = context.getResources().openRawResource(R.drawable.heart);
    is[8] = context.getResources().openRawResource(R.drawable.heart);
    is[9] = context.getResources().openRawResource(R.drawable.heart);
    is[10] = context.getResources().openRawResource(R.drawable.heart);

             //reserving numbers for more textures from 6 to 10.


    is[11] = context.getResources().openRawResource(R.drawable.walking01);
    is[12] = context.getResources().openRawResource(R.drawable.walking02);
    is[13] = context.getResources().openRawResource(R.drawable.walking03);
    is[14] = context.getResources().openRawResource(R.drawable.walking04);
    is[15] = context.getResources().openRawResource(R.drawable.walking05);

    is[16] = context.getResources().openRawResource(R.drawable.flying01);
    is[17] = context.getResources().openRawResource(R.drawable.flying02);

    is[18] = context.getResources().openRawResource(R.drawable.falling_01);
    is[19] = context.getResources().openRawResource(R.drawable.falling_02);
    is[20] = context.getResources().openRawResource(R.drawable.fallen);

    is[21] = context.getResources().openRawResource(
            R.drawable.standingup_01);
    is[22] = context.getResources().openRawResource(
            R.drawable.standingup_02);

    is[23] = context.getResources().openRawResource(R.drawable.death_01);
    is[24] = context.getResources().openRawResource(R.drawable.death_02);
    is[25] = context.getResources().openRawResource(R.drawable.death_03);
    is[26] = context.getResources().openRawResource(R.drawable.death_04);
    is[27] = context.getResources().openRawResource(R.drawable.death_05);
    is[28] = context.getResources().openRawResource(R.drawable.death_06);


    is[29] = context.getResources().openRawResource(R.drawable.p_walk_1);
    is[30] = context.getResources().openRawResource(R.drawable.p_walk_2);
    is[31] = context.getResources().openRawResource(R.drawable.p_walk_3);
    is[32] = context.getResources().openRawResource(R.drawable.p_walk_4);
    is[33] = context.getResources().openRawResource(R.drawable.p_walk_5);
    is[34] = context.getResources().openRawResource(R.drawable.p_walk_6);

    is[35] = context.getResources().openRawResource(R.drawable.p_holding01);
    is[36] = context.getResources().openRawResource(R.drawable.p_holding03);

    is[37] = context.getResources().openRawResource(R.drawable.p_opening01);
    is[38] = context.getResources().openRawResource(R.drawable.p_opening02);


    is[39] = context.getResources().openRawResource(R.drawable.p_parachute01);
    is[40] = context.getResources().openRawResource(R.drawable.p_parachute02);
    is[41] = context.getResources().openRawResource(R.drawable.p_parachute03);
    is[42] = context.getResources().openRawResource(R.drawable.p_parachute04);
    is[43] = context.getResources().openRawResource(R.drawable.p_parachute05);

    is[44] = context.getResources().openRawResource(R.drawable.p_closing01);
    is[45] = context.getResources().openRawResource(R.drawable.p_closing02);

    is[46] = context.getResources().openRawResource(R.drawable.p_cracking01);
    is[47] = context.getResources().openRawResource(R.drawable.p_cracking02);

    is[48] = context.getResources().openRawResource(R.drawable.p_fallen01);

    is[49] = context.getResources().openRawResource(R.drawable.p_standing01);
    is[50] = context.getResources().openRawResource(R.drawable.p_standing02);
    is[51] = context.getResources().openRawResource(R.drawable.p_standing3);

    is[52] = context.getResources().openRawResource(R.drawable.p_dead01);
    is[53] = context.getResources().openRawResource(R.drawable.p_dead02);
    is[54] = context.getResources().openRawResource(R.drawable.p_dead03);
    is[55] = context.getResources().openRawResource(R.drawable.p_dead04);
    is[56] = context.getResources().openRawResource(R.drawable.p_dead05);
    is[57] = context.getResources().openRawResource(R.drawable.p_dead06);
    is[58] = context.getResources().openRawResource(R.drawable.p_dead07);
    is[59] = context.getResources().openRawResource(R.drawable.p_dead08);
    is[60] = context.getResources().openRawResource(R.drawable.p_dead09);

    is[61] = context.getResources().openRawResource(R.drawable.r_walking01);
    is[62] = context.getResources().openRawResource(R.drawable.r_walking02);
    is[63] = context.getResources().openRawResource(R.drawable.r_walking03);
    is[64] = context.getResources().openRawResource(R.drawable.r_walking04);
    is[65] = context.getResources().openRawResource(R.drawable.r_walking05);
    is[66] = context.getResources().openRawResource(R.drawable.r_walking06);
    is[67] = context.getResources().openRawResource(R.drawable.r_walking07);
    is[68] = context.getResources().openRawResource(R.drawable.r_walking08);
    is[69] = context.getResources().openRawResource(R.drawable.r_falling01);
    is[70] = context.getResources().openRawResource(R.drawable.r_falling02);

    is[71] = context.getResources().openRawResource(R.drawable.r_fallen);

    is[72] = context.getResources().openRawResource(R.drawable.r_standingup01);
    is[73] = context.getResources().openRawResource(R.drawable.r_standingup02);

    is[74] = context.getResources().openRawResource(R.drawable.r_dyeing01);
    is[75] = context.getResources().openRawResource(R.drawable.r_dyeing02);
    is[76] = context.getResources().openRawResource(R.drawable.r_dyeing03);
    is[77] = context.getResources().openRawResource(R.drawable.r_dyeing04);
    is[78] = context.getResources().openRawResource(R.drawable.r_dyeing05);
    is[79] = context.getResources().openRawResource(R.drawable.r_dyeing06);
    is[80] = context.getResources().openRawResource(R.drawable.r_dyeing07);
    is[81] = context.getResources().openRawResource(R.drawable.r_dyeing08);
    is[82] = context.getResources().openRawResource(R.drawable.r_dyeing09);
    is[83] = context.getResources().openRawResource(R.drawable.r_dyeing10);




    for (int loop = 0; loop < textureCount; loop++) {

        bitmap[loop] = BitmapFactory.decodeStream(is[loop]);
        try {
            is[loop].close();
            is[loop] = null;
        } catch (IOException e) {
        }
    }
    gl.glGenTextures(textureCount, textures, 0);
    for (int loop = 0; loop < textureCount; loop++) {
        gl.glBindTexture(GL10.GL_TEXTURE_2D, textures[loop]);
        gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER,
                GL10.GL_LINEAR);
        gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER,
                GL10.GL_LINEAR);
        gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT, // OpenGL docs.
                GL10.GL_NICEST);
        gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_S,
                GL10.GL_CLAMP_TO_EDGE);
        gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_T,
                GL10.GL_CLAMP_TO_EDGE);

        GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, bitmap[loop], 0);
        bitmap[loop].recycle();
    }
}
trincot
  • 317,000
  • 35
  • 244
  • 286
Moondustt
  • 864
  • 1
  • 11
  • 30
  • Do you require the use of all 84 images at the same time, meaning, are all 84 on screen simultaneously? The resolution kind of gives it away but just asking so we are on the same page! – Eric Tobias Jun 27 '13 at 13:53
  • Yes, the game is like plants vs zombies, and depending on the level you are, all kind of zombies can appear on the screen. – Moondustt Jun 27 '13 at 13:54
  • I see. Unfortunately there isn't much in terms of optimisation that can be done. I'll write an answer with some references for you to read. They should help! – Eric Tobias Jun 27 '13 at 13:57
  • Great, for a zombie of the size of plants vs zombies zombie, do you think 150px is too big or is it ok? – Moondustt Jun 27 '13 at 13:59
  • As you are getting OOM exceptions, my first guess would be that it is too big. Are you using different images for high, low, mid, and very high definitions? You should not let Android take care of scaling. Be sure to let Android handle the `res/drawable` to load from! – Eric Tobias Jun 27 '13 at 14:05
  • Unfortunately the only thing I can really think of is creating a JUnit test for your game and use an AssertGC. You would need a weakreference to do the assertGC against. That would help the garbage collector kind of kick along. – yams Jul 02 '13 at 20:19
  • see this. i think it's maybe help you. http://stackoverflow.com/questions/9148795/android-opengl-texture-compression – jeevamuthu Jul 04 '13 at 12:45

3 Answers3

1

As you require all of those on screen simultaneously, optimisation is quite impossible. I would recommend you take the Displaying Bitmaps Efficiently tutorial to heart as it offers some tips on how to avoid the dreaded java.lang.OutofMemoryError: bitmap size exceeds VM budget error.

There are a few other things that come to mind. First, I would recommend not loading all InputStreams before and then looping. Why not load the stream in the loop? Maybe write the names of the images to load in a file and iterate over them.

Also, in my experience, BitmapFactory.decodeFileDescriptor() causes less trouble. While I am not sure it will solve the problem, it can't hurt at this point!

Eric Tobias
  • 3,225
  • 4
  • 32
  • 50
  • I'll change the loop, and i'll see what happens. – Moondustt Jun 27 '13 at 14:27
  • I tried for (int loop = 0; loop < textureCount; loop++) { InputStream stream = context.getResources().openRawResource(is[loop]); bitmap[loop] = BitmapFactory.decodeStream(stream); } but it's using the same amount, I think there's no way, i'll need to use smaller images. – Moondustt Jul 01 '13 at 13:18
  • No. What you need to use is all sizes of images. You should read this developer guide on supporting multiple screen sizes (http://developer.android.com/guide/practices/screens_support.html#support) which also shows how to manage different image sizes. – Eric Tobias Jul 02 '13 at 07:45
1

Change you code to something like that:

public void loadTextures(GL10 gl) {
    int[] ids = new int[texturesCount];
    ids[0] = R.drawable.bg_01;
    // rest of id's

    gl.glGenTextures(textureCount, textures, 0);

    for (int loop = 0; loop < textureCount; loop++) {
        Bitmap bm = BitmapFactory.decodeResource(context.getResources(), ids[loop]);
        if (null == bm) {
            // handle error
        }
        gl.glBindTexture(GL10.GL_TEXTURE_2D, textures[loop]);
        gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER,
                GL10.GL_LINEAR);
        gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER,
                GL10.GL_LINEAR);
        gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT, // OpenGL docs.
                GL10.GL_NICEST);
        gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_S,
                GL10.GL_CLAMP_TO_EDGE);
        gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_T,
                GL10.GL_CLAMP_TO_EDGE);

        GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, bm, 0);
        bm.recycle();
    }
}

That way you won't have simultaneous 84 opened streams, 84 loaded bitmaps and so on...

arcone1
  • 224
  • 2
  • 8
  • Does it worked, or you still have OOM? How did you measure memory consumption? I have no idea how could you allocate 42MB since all your textures are about 8MB in total (84 * 150 * 150 * 4) – arcone1 Jul 01 '13 at 16:42
  • I think bm.recycle(); will free the memory some time in the future and not instantly, I think even triggering a manual gc collection wont help but it is not that important, the system will free the memory when it needs to with this approach so I would say its better then the original one! Its also imporant to convert it to a opengl compatible format, then it will use only half the space in the native ram – Simon Jul 02 '13 at 11:39
  • @Sponge, recycle will free memory if bitmap is not shared (basically it frees SkPixelRef that is hold in SkBitmap object), there's nothing like "opengl compatible format", you are probably talking about reducing pixel size (to 16bit RGB565) or texture compression, but texture compression is tricky as there is no standard format that supports alpha, for fully opaque textures one can use ETC1 – arcone1 Jul 02 '13 at 11:52
  • @Sponge One more note about recycle, it's indeed not needed on 3.0+ since pixelrefs are allocated on VM heap, but on older version they are allocated on native heap but counted against heap limit as "external allocation". Although recycle is invoked from finalizer it's asynchronous operation and may lead to OOMs when dealing with large amount of bitmaps, so it's safer to call recycle manually. – arcone1 Jul 02 '13 at 12:03
0

As far as I know there is a way to bind texturebuffer so that it will be loaded to GPU RAM, which will reduce your heap usage. I was reading about this, but not sure how exactly to do it. Maybe glBindTexture() is doing exactly the same, dunno.

Also it is good if you join all your texture files in one single file and load it just once in onSurfaceCreated();

Take a look at this question too: How to load textures in OpenGL ES efficiently

Community
  • 1
  • 1
Kamen Stoykov
  • 1,715
  • 3
  • 17
  • 31