I can't verify the leak. I wrote myself this little test case. It's based on your method but stripped, because i don't have all this font-stuff. I simply colored the Bitmap in a random color. To tweak memory usage a little bit, i release immutable_thatfont a little earlier (what's the point of that bitmap anyway?) and added Config.unloadImmediately=true at the beginning of onCreate(). However, none of these is actually needed to make it work in the 2.2 emulator.
While playing around with preWarm(), i've noticed one thing that i consider to be a VM flaw: If you add this call at the end of the test2PosTexture-method, it crashes immediately with an OOM-Exception. If you let the method return normally and add the call right after that (like in this example), it works just fine. Looks like as if some memory won't be released even if it could unless the method returns...might be a GC flaw or "optimization".
However, this test case runs as it should for me. What happens in your case and why it goes away if you omit the call to replaceTexture() has to be something else. I *think* that is has to do with the fact that you are running on the edge of memory anyway. You create two large bitmaps, each 4mb in size plus the actual texture using another 4mb plus the memory needed to upload it plus the memory on the gpu (another 4mb) plus the memory that your application itself needs etc. Combine that with the fact that Android/Dalvik starts to act weird when running close to its memory limits especially when working with Bitmaps and you might run into trouble. If you omit the call to replace, there's no new upload and maybe the texture is smaller (don't know, impossible to tell from that code snippet).
Isn't it possible to reduce the size of these monster bitmaps? Lets say to 512*512 or something like that? Why does it has to be that large?
Anyway, here's the test case:
package com.threed.jpct.example;
import java.lang.reflect.Field;
import javax.microedition.khronos.egl.EGL10;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.egl.EGLDisplay;
import javax.microedition.khronos.opengles.GL10;
import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.opengl.GLSurfaceView;
import android.os.Bundle;
import android.view.MotionEvent;
import com.threed.jpct.Camera;
import com.threed.jpct.Config;
import com.threed.jpct.FrameBuffer;
import com.threed.jpct.Logger;
import com.threed.jpct.Object3D;
import com.threed.jpct.Primitives;
import com.threed.jpct.RGBColor;
import com.threed.jpct.Texture;
import com.threed.jpct.TextureManager;
import com.threed.jpct.World;
import com.threed.jpct.util.BitmapHelper;
import com.threed.jpct.util.MemoryHelper;
/**
* @author EgonOlsen
*
*/
public class HelloWorld extends Activity {
private static HelloWorld master = null;
private GLSurfaceView mGLView;
private MyRenderer renderer = null;
private FrameBuffer fb = null;
private World world = null;
private RGBColor back = new RGBColor(50, 50, 100);
private float touchTurn = 0;
private float touchTurnUp = 0;
private float xpos = -1;
private float ypos = -1;
private Object3D cube0 = null;
private Object3D cube1 = null;
private Object3D cube2 = null;
private Object3D cube3 = null;
private Object3D dummy = null;
// private Texture renderTarget = null;
private TextureManager tm = TextureManager.getInstance();
private int fps = 0;
private int cnt = 0;
protected void onCreate(Bundle savedInstanceState) {
Config.unloadImmediately = true;
Logger.setLogLevel(Logger.LL_VERBOSE);
Logger.log("onCreate");
if (master != null) {
copy(master);
}
super.onCreate(savedInstanceState);
mGLView = new GLSurfaceView(getApplication());
mGLView.setEGLConfigChooser(new GLSurfaceView.EGLConfigChooser() {
public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display) {
int[] attributes = new int[] { EGL10.EGL_DEPTH_SIZE, 16, EGL10.EGL_NONE };
EGLConfig[] configs = new EGLConfig[1];
int[] result = new int[1];
egl.eglChooseConfig(display, attributes, configs, 1, result);
return configs[0];
}
});
renderer = new MyRenderer();
mGLView.setRenderer(renderer);
setContentView(mGLView);
}
@Override
protected void onPause() {
super.onPause();
mGLView.onPause();
}
@Override
protected void onResume() {
super.onResume();
mGLView.onResume();
}
protected void onStop() {
super.onStop();
}
private void copy(Object src) {
try {
Logger.log("Copying data from master Activity!");
Field[] fs = src.getClass().getDeclaredFields();
for (Field f : fs) {
f.setAccessible(true);
f.set(this, f.get(src));
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public boolean onTouchEvent(MotionEvent me) {
if (me.getAction() == MotionEvent.ACTION_DOWN) {
xpos = me.getX();
ypos = me.getY();
return true;
}
if (me.getAction() == MotionEvent.ACTION_UP) {
xpos = -1;
ypos = -1;
touchTurn = 0;
touchTurnUp = 0;
return true;
}
if (me.getAction() == MotionEvent.ACTION_MOVE) {
float xd = me.getX() - xpos;
float yd = me.getY() - ypos;
xpos = me.getX();
ypos = me.getY();
touchTurn = xd / -100f;
touchTurnUp = yd / -100f;
return true;
}
try {
Thread.sleep(15);
} catch (Exception e) {
// No need for this...
}
return super.onTouchEvent(me);
}
protected boolean isFullscreenOpaque() {
return true;
}
class MyRenderer implements GLSurfaceView.Renderer {
private long time = System.currentTimeMillis();
private boolean stop = false;
public MyRenderer() {
}
public void stop() {
stop = true;
}
public void onSurfaceChanged(GL10 gl, int w, int h) {
if (fb != null) {
fb.dispose();
}
fb = new FrameBuffer(gl, w, h);
if (master == null) {
world = new World();
world.setAmbientLight(255, 255, 255);
Texture texture = new Texture(BitmapHelper.rescale(BitmapHelper.convert(getResources().getDrawable(R.drawable.icon)), 64, 64));
TextureManager.getInstance().addTexture("texture", texture);
texture.setMipmap(false);
dummy = Object3D.createDummyObj();
cube0 = Primitives.getCube(10);
cube0.rotateY(-(float) Math.PI / 4f);
cube0.rotateMesh();
cube0.clearRotation();
cube0.calcTextureWrapSpherical();
cube0.setTexture("texture");
cube0.strip();
cube0.build();
cube1 = cube0.cloneObject();
cube2 = cube0.cloneObject();
cube3 = cube0.cloneObject();
world.addObject(cube0);
world.addObject(cube1);
world.addObject(cube2);
world.addObject(cube3);
cube0.translate(-20, -20, 0);
cube1.translate(20, -20, 0);
cube2.translate(-20, 20, 0);
cube3.translate(20, 20, 0);
cube0.addParent(dummy);
cube1.addParent(dummy);
cube2.addParent(dummy);
cube3.addParent(dummy);
Camera cam = world.getCamera();
cam.moveCamera(Camera.CAMERA_MOVEOUT, 100);
// renderTarget = new Texture(256, 256, RGBColor.RED);
MemoryHelper.compact();
if (master == null) {
Logger.log("Saving master Activity!");
master = HelloWorld.this;
}
// Logger.setLogLevel(Logger.DEBUG);
}
}
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
}
public void onDrawFrame(GL10 gl) {
try {
if (!stop) {
if (touchTurn != 0) {
dummy.rotateY(touchTurn);
touchTurn = 0;
}
if (touchTurnUp != 0) {
dummy.rotateX(touchTurnUp);
touchTurnUp = 0;
}
cnt++;
/*
* Config.autoMaintainAspectRatio=false;
* fb.setRenderTarget(renderTarget);
* fb.clear(RGBColor.BLUE);
* fb.blit(tm.getTexture("texture"), 0, 0, 0, 0, 64, 64,
* fb.getWidth(), fb.getHeight(), -1, false, null);
* fb.display(); fb.removeRenderTarget();
* Config.autoMaintainAspectRatio=true;
*/
text2PosTexture("texture");
// TextureManager.getInstance().preWarm(fb);
fb.clear(back);
world.renderScene(fb);
world.draw(fb);
fb.display();
if (System.currentTimeMillis() - time >= 1000) {
Logger.log(fps + "fps");
fps = 0;
time = System.currentTimeMillis();
}
fps++;
} else {
if (fb != null) {
fb.dispose();
fb = null;
}
}
} catch (Exception e) {
Logger.log(e, Logger.MESSAGE);
}
}
}
public void text2PosTexture(String texName) {
Bitmap immutable_thatfont = Bitmap.createBitmap(1024, 1024, Bitmap.Config.ARGB_4444);
Bitmap fontTarget = immutable_thatfont.copy(Bitmap.Config.ARGB_4444, true);
int red = (int) (255d * Math.random());
int green = (int) (255d * Math.random());
int blue = (int) (255d * Math.random());
fontTarget.eraseColor(Color.rgb(red, green, blue));
immutable_thatfont.recycle();
immutable_thatfont = null;
MemoryHelper.compact(); // Actually not needed
Logger.log("Creating new texture!");
Texture allfonts = new Texture(fontTarget, true);
fontTarget.recycle();
fontTarget = null;
Logger.log("Unloading old texture!");
TextureManager.getInstance().unloadTexture(fb, TextureManager.getInstance().getTexture(texName));
Logger.log("Replacing with new texture!");
TextureManager.getInstance().replaceTexture(texName, allfonts);
}
}