I mentioned this in the contribution depot, but I figured it's serious enough that it could do with its own topic.
The problem is: If you create a new empty Image, and then use getGraphics, two OpenGL Textures will be created, but Image.getTexture will only point to one of them (the FBO/pbuffer texture). Therefore, even if you are properly calling destroy() on your Image, a game that creates many offscreen images will eventually run out of memory and crash.
The reason FBO/Pbuffer does not release the old texture is because the user may still want to use it. For example, to add 'decals' to a texture, you might do something like:
Code:
tile = new Image("res/tex/tile.png"); //original tile
tile2 = new Image(tile.getTexture()); //tile with 'decal'
Graphics g = tile2.getGraphics();
g.setColor(Color.black);
g.fillOval(5, 5, 10, 10); //draw 'decal'
g.flush();
In this situation, it wouldn't make sense for getGraphics to release the old texture. But often getGraphics is used for the sake of double buffering or rendering something to an empty texture (e.g. a complex tile map).
Workarounds:The simplest workaround is to grab the old texture before getGraphics is called, and then release() the old texture after getGraphics. Two textures are still generated, however.
A cleaner workaround is to use something like a NullTexture, so that you are only creating one texture in the end. You would use the class like so:
Code:
tile = new Image(new NullTexture(250, 250));
Graphics g = tile.getGraphics();
//... render to texture ...
g.flush();
NullTexture.java:
Code:
import org.newdawn.slick.opengl.InternalTextureLoader;
import org.newdawn.slick.opengl.Texture;
/**
* A "null" texture which holds no image data and does not
* create or bind any OpenGL textures. This is useful
* for creating off-screen images where there is no need
* to keep hold of the original image (i.e. if the off-screen
* image you are creating is going to be initially empty).
*
* @author davedes
*/
public class NullTexture implements Texture {
private int width, height;
private int texWidth, texHeight;
public NullTexture(int width, int height) {
this.width = width;
this.height = height;
this.texWidth = InternalTextureLoader.get2Fold(width);
this.texHeight = InternalTextureLoader.get2Fold(height);
}
public boolean hasAlpha() {
return true;
}
public String getTextureRef() {
return null;
}
public void bind() {
}
public int getImageHeight() {
return width;
}
public int getImageWidth() {
return height;
}
public float getHeight() {
return width/(float)texWidth;
}
public float getWidth() {
return height/(float)texHeight;
}
public int getTextureHeight() {
return texWidth;
}
public int getTextureWidth() {
return texHeight;
}
public void release() {
}
public int getTextureID() {
return 0;
}
public byte[] getTextureData() {
return null;
}
public void setTextureFilter(int textureFilter) {
}
}
In the long run, I think we should be revisiting the purpose/naming of EmptyImageData (and likewise the Image(width, height) constructor). These are most often used for offscreen rendering, but they result in excessive texture creation. Instead, at least in my opinion, an empty texture should truly be 'empty' in that it stores no RGBA/texture data. If the user
really wanted to create a blank texture (without FBO/pbuffer), only then would EmptyImageData be employed.