ZhouWeikuan / cocos2d

cocos2d for android, based on cocos2d-android-0.82, and now ported from cocos2d-iphone 0.99.4. The googlecode address is here: http://code.google.com/p/cocos2d-android-1/ . There are several demos to watch.
610 stars 291 forks source link

Textures not being released #22

Open sourceBreaker opened 13 years ago

sourceBreaker commented 13 years ago

Hi, I am trying to remove all textures from CCTextureCache to free up memory from native Heap . I am using CCTextureCahce.purgeSharedTextureCache() but its not cleared as expected. For example if size of my native heap is 10MB before clearing than after calling purgeSharedTextureCache it is again 10MB. :(

Actually in my app at a point i want to release all textures from memory and than reload new textures from scratch. please help.

dustinanon commented 13 years ago

Have you profiled your application yet? (there's a profiler included with ADT)

You might find that you're holding a hard reference somewhere outside of the TextureCache that you're not breaking. Double check that and if you're still having issues, let us know.

sourceBreaker commented 13 years ago

We have run yourkit profiler on our application and CCtextures seems to be cleared okay. But the application is still crashing with OOM error.

I have created a test program where I am allocating 14 bitmaps and releasing them right away in a loop, and the application crashed while loading the images for the second time with OOM exception.

I can see that init of CCtexture2D is using Buffer.allocateDirect, which does not seem to be releasing the memory as expected. I ran across an article which seems to suggest that nio.buffer.allocateDirect is prone to OOM in case of allocating and allocating buffer on run time. please see the link below. http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4857305

HELPPPPP

dustinanon commented 13 years ago

I came across someone else with this issue on StackOverflow. I certainly don't think this it the best solution, but maybe we can get some ideas going in the right direction.

http://stackoverflow.com/questions/5060307/bytebuffer-not-releasing-memory

You might try seeing what happens when ByteBuffer tries to release the memory and see if it gives you an idea on how to fix it.

sourceBreaker commented 13 years ago

After meging latest code of cocos2d the textures in my code are disappearing and causing White Textures to my animations. Is this due to the in CCTextureCache. ? please help

dustinanon commented 13 years ago

I've had this happen a few times before the patch. I think somebody already posted another issue about this. You should always create an individual thread for each symptom you encounter.

Have you had any luck with the ByteBuffer issue?

sourceBreaker commented 13 years ago

Hi, Byte Buffer issue is not resolved but the real issue in my game is of texture Releasing . Actually let me brief you my scenerio In my Game i have different Levels on Level 1 : I load multiple sprites in my GameLayer and my native heap goes around 15 MB now i want to load Level 2: I reinitialize my GameLayer and i clear all textures in CCtextureCache now theoritically my native heap should come down to 2~3MB as i have remove all sprites from my GameLayer using GameLayer.removeAllChildren(true); but this is not happened on level 2 my Native Heap again rises upto 30MB as the previos textures do`nt get released and more textures are loaded in memory.

Now if i load my Level 2 on a new Activity and finish my Old Activity Native Heap comes down but it is not the best solution i need some good solution.

I think GLResourceHelper is holding my textures and not being released even after clearing Texture Cache.

Waiting for a kind reply

sourceBreaker commented 13 years ago

Or might be GL library is not releasing my textures from memory :(

starburst997 commented 13 years ago

I am having the exact same problem, I think. I can create new CCSprite, then call CCTextureCache.sharedTextureCache().removeAllTextures(), create new CCSprite again with the same textures, repeat, and the memory will go up until it crash... If I don't call removeAllTextures(), it will stay the same since it use the cached CCTexture2D, but I need to call it since the textures changes every level, I will try sourceBreaker's new activity trick but it seems like there is some memory leak with textures.

starburst997 commented 13 years ago

Here is an example, I can't figure why this is leaking

private float time = 1.0f;
public void render( float delta )
{
    time -= delta;

    if ( time < 0 )
    {
        time = 0.01f;

        removeAllChildren(true);
        CCTextureCache.sharedTextureCache().removeAllTextures();

        CCSprite sprite = CCSprite.sprite("a.png");

        sprite.setPosition( (float)Math.random() * 100, (float)Math.random() * 100 );

        addChild( sprite );
    }
}
dustinanon commented 13 years ago

Are you doing this while the scene is running? It might be that the GL thread is holding a reference somewhere... I'm not 100% sure on best practices, but I think you should only removeAllTextures() after you've released the Scene from the Director.

d3alek commented 13 years ago

I had a similar problem but I think I solved it, inspired by the suggestion here: http://stackoverflow.com/questions/4928677/properly-delete-texture

The changes I made:

GLResourceHelper.java

public void perform(GLResorceTask res) {
    /* perform task only if in the GLThread, else add to taskQueue */
    if (Thread.currentThread().getName().startsWith("GLThread")) {
        res.perform(CCDirector.gl);
    }
    else taskQueue.add(res);
}

CCTexture2D.java

public void releaseTexture (GL10 gl) {
    if (_name != 0) {
        GLResourceHelper.sharedHelper().perform(new GLResourceHelper.GLResorceTask() {
            @Override
            public void perform(GL10 gl) {
                IntBuffer intBuffer = IntBuffer.allocate(1);
                intBuffer.put(0, _name);
                gl.glDeleteTextures(1, intBuffer);
            }
        });
    }
}

Basically I ensure that releaseTexture is only called in the GLThread, making the assumption that it somehow fails otherwise.

Now it releases the textures fine and I have no memory leaks, but I suppose this solution is not optimal. Still it works for me :)

(tested only on HTC Desire)

starburst997 commented 13 years ago

Thanks a lot for the replies! I edited GLResourceHelper.java and CCTexture2D.java with your changes, but I can't seem to be able to create a texture and then delete it.

I tried a simple test of creating a scene that call "replaceScene()" after a few milliseconds and inside of this scene there is just a sprite. I call "removeAllTextures()" in the constructor to makes sure it doesn't cache the texture and recreate it each few milliseconds as the scene changes to test the memory leak. But it doesn't works, the memory goes up...

Could you show me an example where you change scene and completely remove it's content from memory? The same logic that I used in the iOS version of cocos2D doesn't seemed to work on android.

d3alek commented 13 years ago

Here's the class I made following your instructions.

MemoryTestLayer.java

public class MemoryTestLayer extends CCLayer {
    public MemoryTestLayer() {
        GLResourceHelper.sharedHelper().perform(new GLResourceHelper.GLResorceTask() {
            @Override
            public void perform(GL10 gl) {
                CCTextureCache.sharedTextureCache().removeAllTextures();
                CCSprite sprite = CCSprite.sprite("a.png");
                CGSize winSize = CCDirector.sharedDirector().winSize();
                sprite.setPosition(winSize.width/2.f, winSize.height/2.f);
                addChild(sprite);
            }
        });
        schedule("newScene");
    }
    public void newScene(float dt) {
        CCScene scene = CCScene.node();
        scene.addChild(new MemoryTestLayer());
        CCDirector.sharedDirector().replaceScene(scene);
    }
}

It is working fine and not leaking memory with me even if I don't wrap the removeAllTextures() and the sprite creation in a GLResourceTask, but this might be why you are having issues.

starburst997 commented 13 years ago

Thanks! Using your code it is still leaking, after a few minutes the app crash. I tried with your changes to GLResourceHelper/CCTexture2D and with a fresh copy from the repository, but still leaking. Maybe it is my phone? I have the HTC Desire HD.

starburst997 commented 13 years ago

I also updated my android sdk, still no luck, just to be sure, here is my activity.java:

package test;

import javax.microedition.khronos.opengles.GL10;

import org.cocos2d.layers.CCLayer;
import org.cocos2d.layers.CCScene;
import org.cocos2d.nodes.CCDirector;
import org.cocos2d.nodes.CCSprite;
import org.cocos2d.nodes.CCTextureCache;
import org.cocos2d.opengl.CCGLSurfaceView;
import org.cocos2d.opengl.GLResourceHelper;
import org.cocos2d.types.CGSize;

import android.app.Activity;
import android.os.Bundle;
import android.view.Window;
import android.view.WindowManager;

public class TestActivity extends Activity {

    private CCGLSurfaceView mGLSurfaceView;

    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
                WindowManager.LayoutParams.FLAG_FULLSCREEN);
        getWindow().setFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON, 
                WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);

        mGLSurfaceView = new CCGLSurfaceView(this);
        CCDirector director = CCDirector.sharedDirector();
        director.attachInView(mGLSurfaceView);
        director.setDeviceOrientation(CCDirector.kCCDeviceOrientationLandscapeLeft);
        setContentView(mGLSurfaceView);

        // show FPS
        CCDirector.sharedDirector().setDisplayFPS(true);

        // frames per second
        CCDirector.sharedDirector().setAnimationInterval(1.0f / 30);

        CCScene scene = CCScene.node();
        scene.addChild(new MemoryTestLayer());

        // Make the Scene active
        CCDirector.sharedDirector().runWithScene(scene);
    }

    @Override
    public void onStart() {
        super.onStart();
    }

    @Override
    public void onPause() {
        super.onPause();

        CCDirector.sharedDirector().onPause();
    }

    @Override
    public void onResume() {
        super.onResume();

        CCDirector.sharedDirector().onResume();
    }

    @Override
    public void onDestroy() {
        super.onDestroy();

        CCDirector.sharedDirector().end();
    }

    public class MemoryTestLayer extends CCLayer {
        public MemoryTestLayer() {
            GLResourceHelper.sharedHelper().perform(new GLResourceHelper.GLResorceTask() {
                @Override
                public void perform(GL10 gl) {
                    CCTextureCache.sharedTextureCache().removeAllTextures();
                    CCSprite sprite = CCSprite.sprite("a.png");
                    CGSize winSize = CCDirector.sharedDirector().winSize();
                    sprite.setPosition(winSize.width/2.f + (float)Math.random() * 100, winSize.height/2.f + (float)Math.random() * 100);
                    addChild(sprite);
                }
            });
            schedule("newScene");
        }
        public void newScene(float dt) {
            CCScene scene = CCScene.node();
            scene.addChild(new MemoryTestLayer());
            CCDirector.sharedDirector().replaceScene(scene);
        }
    }
}
dustinanon commented 13 years ago

This isn't a solution in the slightest, but what I've noticed is that if you don't removeAllTextures() and just break your reference to the Scene (change the scene, null the old one out, etc.) then the GC will release the textures on it's own due to the WeakReference... of course the texture is still held by OpenGL, but it prolongs the OOM crash...

Maybe this will let you do what you'd like until a proper fix is found..

MyungjinChoi commented 13 years ago

I'v changed CCTextureCache.java

and maybe solved the problem.

In the original CCTextureCache.java "load(Resource res)" is called twice whenever new texture is created.

So i put a gatekeeper(if (((CCTexture2D)res).name() == 0) to check if the texture2d class is already initiated.

Here is my createTextureFromFilePath function in CCTextureCache.java

private static CCTexture2D createTextureFromFilePath(final String path) {

    CCTexture2D tex = new CCTexture2D();

    tex.setLoader(new GLResourceHelper.GLResourceLoader() {

        @Override
        public void load(Resource res) {
            try {

                if (((CCTexture2D)res).name() == 0)
                {
                    InputStream is = CCDirector.sharedDirector().getActivity().getAssets().open(path);

                    BitmapFactory.Options opts = new BitmapFactory.Options();
                    opts.inPreferredConfig = ((CCTexture2D)res).pixelFormat();
                    Bitmap bmp = BitmapFactory.decodeStream(is, null, opts);

                    is.close();

                    ((CCTexture2D)res).initWithImage(bmp);
                }

            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    });

    return tex;
}

and tested blow code.. and no crash.

private float time = 1.0f; public void render( float delta ) { time -= delta;

if ( time < 0 )
{
    time = 0.01f;

    removeAllChildren(true);
    CCTextureCache.sharedTextureCache().removeAllTextures();

    CCSprite sprite = CCSprite.sprite("a.png");

    sprite.setPosition( (float)Math.random() * 100, (float)Math.random() * 100 );

    addChild( sprite );
}

}

sourceBreaker commented 13 years ago

Hi MyungjinChoi i am using an older revision of code please see code below and let me know how do i integrate your changes in my code.. please do tell me how do i get _name of texture in load() function ... as i cannot move to latest version because of some dependencies of my game.

private static CCTexture2D createTextureFromFilePathExternal(final String path) {

    final CCTexture2D tex = new CCTexture2D();
    tex.setLoader(new GLResourceHelper.GLResourceLoader() {

        @Override
        public void load() {
            InputStream is = null;
            try {
                File file = new File(path);
                if(file.exists())
                {
                    is = new FileInputStream(path);
                    BitmapFactory.Options maskOpts = new BitmapFactory.Options();
                    maskOpts.inPreferredConfig = Bitmap.Config.ARGB_4444;
                    maskOpts.inSampleSize = 8 ;
                    Bitmap bmp = BitmapFactory.decodeStream(is,null,maskOpts);
                    tex.initWithImage(bmp);
                }
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                try {
                    if(is != null)
                    {
                        is.close();
                        is = null;
                    }
                } catch (IOException ex) {
                    ex.printStackTrace();
                }
            }
        }
    });

    return tex;
}
MyungjinChoi commented 13 years ago

maybe your version of CCTexture2D.java have private variable like this

private int _name = 0;

so just put getter like this in the CCTexture2D.java

public int name()
{
    return _name;
}

and use that function in the load() function

if (tex.name() == 0) { }

i don't know your version of CCTexture2D.java well.. so if above code dosn't solve your problem plz send your version of CCTexture2D.java

then i will look into detail.

On Thu, Sep 15, 2011 at 5:07 PM, sourceBreaker < reply@reply.github.com>wrote:

Hi MyungjinChoi i am using an older revision of code please see code below and let me know how do i integrate your changes in my code.. please do tell me how do i get _name of texture in load() function ... as i cannot move to latest version because of some dependencies of my game.

private static CCTexture2D createTextureFromFilePathExternal(final String path) {

   final CCTexture2D tex = new CCTexture2D();
   tex.setLoader(new GLResourceHelper.GLResourceLoader() {

                   @Override
                   public void load() {
                           InputStream is = null;
                           try {
                                   File file = new File(path);
                                   if(file.exists())
                                   {
                                           is = new

FileInputStream(path); BitmapFactory.Options maskOpts = new BitmapFactory.Options(); maskOpts.inPreferredConfig = Bitmap.Config.ARGB_4444; maskOpts.inSampleSize = 8 ; Bitmap bmp = BitmapFactory.decodeStream(is,null,maskOpts); // if(!Util.isSufficientMemory(bmp)){ // // Toast toast = Toast.makeText(CCDirector.sharedDirector().theApp.getApplicationContext(), "Your Device is running in Low Memory , please close the background applications or Restart the application.S", Toast.LENGTH_LONG); // // toast.show(); // // return; // // System.gc(); // } tex.initWithImage(bmp); //bmp.recycle(); //bmp = null; } } catch (IOException e) { e.printStackTrace(); } finally { try { if(is != null) { is.close(); is = null; } } catch (IOException ex) { ex.printStackTrace(); } } } });

   return tex;

}

Reply to this email directly or view it on GitHub: https://github.com/ZhouWeikuan/cocos2d/issues/22#issuecomment-2102093


Myungjin Choi Vice President & Head of Research Institute

12 Fl. Peyto Bldg., 5, Nonhyeon-dong, Gangnam-gu, Seoul, Korea Tel: 82.2.569.2431 | Fax: 82.2.6008.2461 | Cell: 82.10.3598.4908

E-mail: myungjin.choi@crevisse.com | Website: http://www.crevisse.com

sourceBreaker commented 13 years ago

Yes thanks i got it now i will tell you if your solution works for me ... I Hope it will....

sourceBreaker commented 13 years ago

Hi i have done your changes in my code but now my Textures are getting white after i call CCDirector.resume() and CCDirector.Pause()

???

MyungjinChoi commented 13 years ago

Sorry.. my code was just a temporary solution for the problem.

forget about the code(rollback the modification) and use this version of CCTexture2D.java (changed setLoader function)

public void setLoader(GLResourceHelper.GLResourceLoader loader) {
 if(loader != null) {
  loader.load(this);

  GLResourceHelper.sharedHelper().addLoader(this, loader,

false); // use false instead of true } }

it will work and above code shoud be applied to cocos2d-android-1 library. :)

On Thu, Sep 15, 2011 at 8:11 PM, sourceBreaker < reply@reply.github.com>wrote:

Hi i have done your changes in my code but now my Textures are getting white after i call CCDirector.resume() and CCDirector.Pause()

???

Reply to this email directly or view it on GitHub: https://github.com/ZhouWeikuan/cocos2d/issues/22#issuecomment-2103285


Myungjin Choi Vice President & Head of Research Institute

12 Fl. Peyto Bldg., 5, Nonhyeon-dong, Gangnam-gu, Seoul, Korea Tel: 82.2.569.2431 | Fax: 82.2.6008.2461 | Cell: 82.10.3598.4908

E-mail: myungjin.choi@crevisse.com | Website: http://www.crevisse.com

dustinanon commented 13 years ago

MyungjinChoi, you should fork this project and commit your code changes into your own fork. Then you can request that your pulls be merged in. Btw, I made this change already, committed, and is in my pull request which hasn't been addressed for nearly two weeks.

That being said, I still get the crash and heap dump. I thought the problem was solved, but apparently not -sigh-