Release image after texture is loaded

From my jme2 experience, texture is one of the most memory consuming part.

Because most textures are not changed after loaded into gfx memory,

I nullified Image’s bytebuffer data and saved pretty amount of memory.

Can it be applied to jme3 also?

Then how about adding a flag at the texture and dereferencing the data after uploading?

Well all done already



The textures are held by a softreference so if memory is low they will be deloaded.

Also it is not intelligient to deload the earlier, since for example I use around 500mb of textures but my graficcard only has 265mb luckily the levels do not use all textures at the same point so when you move slowly textures are deloaded and replaced with the ones needed in gfx ram, now imagine if the texture would be reloaded from disk every time, mipmaps generated ect. I think no fluent play would be possible then as I would have hard texture loading lags.

Sounds good! :slight_smile:

I think sometimes this kind of functionality may be useful.

What you said applies to general MMORPG.

But if the all scene is loaded but not released while game is played,

it consumes memory much and limits the max size of texture can be loaded.



And even in MMORPG, there are textures which will never be released.

Then developer can set the flag and save memory.

I think it seems good to have event listener about texture lifecycle.

There’s one part in jME3 that requires textures to remain in memory. If you use canvas, then under some cases you may have to remove/add it, at that point the context is destroyed and any GL resources you allocate like textures, meshes, shaders, etc get deleted and if you don’t have them in memory you will have to reload all of them from disk

It should not be released by default.

But I think there are always some resources which will never be released(like UI textures),

then it doesn’t need to be retained in memory afterward.

It seems good to have a resource lifecycle event handler, so user can do custom action.

Well as i said i think the softreference is doing a good job here, however if we find a good way to get a bit more controll, why not.

I suggest GLObject Event Handling,

then user can choose what to do.

I wish it would not be weird? :roll:



[patch]

Index: src/core/com/jme3/renderer/GLObjectListener.java

===================================================================

— src/core/com/jme3/renderer/GLObjectListener.java (revision 0)

+++ src/core/com/jme3/renderer/GLObjectListener.java (revision 0)

@@ -0,0 +1,31 @@

+package com.jme3.renderer;

+

+/**

    • GLObject lifecycle event listener
    • @author Lim, YongHoon
  • *
  • */

    +public interface GLObjectListener {
  • /**
  • * Called when the object is added to manager.<br />
    
  • * Object reference must not stored and used elsewhere<br />
    
  • * @param obj<br />
    
  • * @param data obj related data. In case of texture image, data is Texture instance for image<br />
    
  • */<br />
    
  • public void onRegister(GLObject obj, Object data);

    +
  • /**
  • * Called when the object is updated.<br />
    
  • * Object reference must not stored and used elsewhere<br />
    
  • * @param obj<br />
    
  • * @param data<br />
    
  • */<br />
    
  • public void onUpdate(GLObject obj, Object data);

    +
  • /**
  • * Called when the object is removed from manager.<br />
    
  • * Object reference must not stored and used elsewhere<br />
    
  • * @param id removed GLObject id<br />
    
  • */<br />
    
  • public void onDeregister(int id);

    +}

    Index: src/core/com/jme3/renderer/Renderer.java

    ===================================================================

    — src/core/com/jme3/renderer/Renderer.java (revision 6615)

    +++ src/core/com/jme3/renderer/Renderer.java (working copy)

    @@ -206,5 +206,7 @@
  • @param value

    */

    public void setAlphaToCoverage(boolean value);

    +
  • public void setGLObjectListener(GLObjectListener l);



    }

    Index: src/jogl/com/jme3/renderer/jogl/JoglRenderer.java

    ===================================================================

    — src/jogl/com/jme3/renderer/jogl/JoglRenderer.java (revision 6615)

    +++ src/jogl/com/jme3/renderer/jogl/JoglRenderer.java (working copy)

    @@ -39,6 +39,7 @@

    import com.jme3.math.ColorRGBA;

    import com.jme3.math.Matrix4f;

    import com.jme3.renderer.Caps;

    +import com.jme3.renderer.GLObjectListener;

    import com.jme3.renderer.GLObjectManager;

    import com.jme3.renderer.IDList;

    import com.jme3.renderer.RenderContext;

    @@ -534,14 +535,14 @@

    }

    }


  • public void updateTexImageData(Image image, Texture.Type type, boolean mips) {
  • public void updateTexImageData(Texture tex, Image image, Texture.Type type, boolean mips) {

    int texId = image.getId();

    if (texId == -1) {

    // create texture

    gl.glGenTextures(1, ib1);

    texId = ib1.get(0);

    image.setId(texId);
  •        objManager.registerForCleanup(image);<br />
    
  •        objManager.registerForCleanup(image, tex);<br />
    

statistics.onNewTexture();
}
@@ -570,6 +571,7 @@
}

TextureUtil.uploadTexture(gl, image, 0, generateMips, powerOf2);
+ objManager.update(image, tex);


image.clearUpdateNeeded();
@@ -588,7 +590,7 @@
public void setTexture(int unit, Texture tex) {
Image image = tex.getImage();
if (image.isUpdateNeeded()) {
- updateTexImageData(image, tex.getType(), tex.getMinFilter().usesMipMapLevels());
+ updateTexImageData(tex, image, tex.getType(), tex.getMinFilter().usesMipMapLevels());
}

int texId = image.getId();
@@ -664,7 +666,7 @@
gl.glGenBuffers(1, ib1);
bufId = ib1.get(0);
vb.setId(bufId);
- objManager.registerForCleanup(vb);
+ objManager.registerForCleanup(vb, null);
}

int target;
@@ -1111,4 +1113,9 @@
gl.glDisable(gl.GL_SAMPLE_ALPHA_TO_COVERAGE);
}
}
+
+ @Override
+ public void setGLObjectListener(GLObjectListener l) {
+ objManager.setListener(l);
+ }
}
Index: src/core/com/jme3/renderer/GLObjectManager.java
===================================================================
--- src/core/com/jme3/renderer/GLObjectManager.java (revision 6615)
+++ src/core/com/jme3/renderer/GLObjectManager.java (working copy)
@@ -56,6 +56,8 @@
* referenced.
*/
private ReferenceQueue<Object> refQueue = new ReferenceQueue<Object>();
+
+ private GLObjectListener listener;

/**
* List of currently active GLObjects.
@@ -79,14 +81,33 @@

/**
* Register a GLObject with the manager.
+ * @param obj
+ * @param data
*/
- public void registerForCleanup(GLObject obj){
+ public void registerForCleanup(GLObject obj, Object data){
GLObjectRef ref = new GLObjectRef(obj);
refList.add(ref);
if (logger.isLoggable(Level.FINEST))
logger.log(Level.FINEST, "Registered: {0}", new String[]{obj.toString()});
+ if (listener != null) {
+ listener.onRegister(obj, data);
+ }
}
-
+
+ public void update(GLObject obj, Object data) {
+ if (listener != null) {
+ listener.onUpdate(obj, data);
+ }
+ }
+
+ /**
+ * Set listener for GLObject's lifecycle event
+ * @param l
+ */
+ public void setListener(GLObjectListener l) {
+ this.listener = l;
+ }
+
/**
* Deletes unused GLObjects
*/
@@ -100,6 +121,9 @@
ref.objClone.deleteObject(r);
if (logger.isLoggable(Level.FINEST))
logger.log(Level.FINEST, "Deleted: {0}", ref.objClone);
+ if (listener != null) {
+ listener.onDeregister(ref.objClone.getId());
+ }
}
}

Index: src/lwjgl-ogl/com/jme3/renderer/lwjgl/LwjglRenderer.java
===================================================================
--- src/lwjgl-ogl/com/jme3/renderer/lwjgl/LwjglRenderer.java (revision 6615)
+++ src/lwjgl-ogl/com/jme3/renderer/lwjgl/LwjglRenderer.java (working copy)
@@ -39,6 +39,7 @@
import com.jme3.math.Vector2f;
import com.jme3.math.Vector3f;
import com.jme3.renderer.Caps;
+import com.jme3.renderer.GLObjectListener;
import com.jme3.renderer.GLObjectManager;
import com.jme3.renderer.IDList;
import com.jme3.renderer.Renderer;
@@ -887,7 +888,7 @@
glDeleteShader(id);
} else {
// register for cleanup since the ID is usable
- objManager.registerForCleanup(source);
+ objManager.registerForCleanup(source, null);
}
}

@@ -968,7 +969,7 @@
} else {
shader.setUsable(true);
if (needRegister) {
- objManager.registerForCleanup(shader);
+ objManager.registerForCleanup(shader, null);
statistics.onNewShader();
} else {
// OpenGL spec: uniform locations may change after re-link
@@ -1193,7 +1194,7 @@
Texture tex = rb.getTexture();
Image image = tex.getImage();
if (image.isUpdateNeeded()) {
- updateTexImageData(image, tex.getType(), tex.getMinFilter().usesMipMapLevels(), 0);
+ updateTexImageData(tex, 0);

// NOTE: For depth textures, sets nearest/no-mips mode
// Required to fix "framebuffer unsupported"
@@ -1233,7 +1234,7 @@
glGenFramebuffersEXT(intBuf1);
id = intBuf1.get(0);
fb.setId(id);
- objManager.registerForCleanup(fb);
+ objManager.registerForCleanup(fb, null);

statistics.onNewFrameBuffer();
}
@@ -1547,14 +1548,17 @@
}
}

- public void updateTexImageData(Image img, Texture.Type type, boolean mips, int unit) {
+ public void updateTexImageData(Texture tex, int unit) {
+ Image img = tex.getImage();
+ Texture.Type type = tex.getType();
+ boolean mips = tex.getMinFilter().usesMipMapLevels();
int texId = img.getId();
if (texId == -1) {
// create texture
glGenTextures(intBuf1);
texId = intBuf1.get(0);
img.setId(texId);
- objManager.registerForCleanup(img);
+ objManager.registerForCleanup(img, tex);

statistics.onNewTexture();
}
@@ -1626,14 +1630,15 @@
glGenerateMipmapEXT(target);
}
}
-
+
+ objManager.update(img, tex);
img.clearUpdateNeeded();
}

public void setTexture(int unit, Texture tex) {
Image image = tex.getImage();
if (image.isUpdateNeeded()) {
- updateTexImageData(image, tex.getType(), tex.getMinFilter().usesMipMapLevels(), unit);
+ updateTexImageData(tex, unit);
}

int texId = image.getId();
@@ -1744,7 +1749,7 @@
glGenBuffers(intBuf1);
bufId = intBuf1.get(0);
vb.setId(bufId);
- objManager.registerForCleanup(vb);
+ objManager.registerForCleanup(vb, null);

created = true;
}
@@ -2187,4 +2192,9 @@
glDisable(ARBMultisample.GL_SAMPLE_ALPHA_TO_COVERAGE_ARB);
}
}
+
+ @Override
+ public void setGLObjectListener(GLObjectListener l) {
+ objManager.setListener(l);
+ }
}
Index: src/core/com/jme3/system/NullRenderer.java
===================================================================
--- src/core/com/jme3/system/NullRenderer.java (revision 6615)
+++ src/core/com/jme3/system/NullRenderer.java (working copy)
@@ -37,6 +37,7 @@
import com.jme3.math.ColorRGBA;
import com.jme3.math.Matrix4f;
import com.jme3.renderer.Caps;
+import com.jme3.renderer.GLObjectListener;
import com.jme3.renderer.Renderer;
import com.jme3.renderer.Statistics;
import com.jme3.scene.Mesh;
@@ -140,4 +141,7 @@
public void setAlphaToCoverage(boolean value) {
}

+ public void setGLObjectListener(GLObjectListener l) {
+ }
+
}

[/patch]