* Add NativeObject.dispose() which deletes the object from GL driver, and if UNSAFE=true, also native buffers.

* Rename NativeObjectManager.registerForCleanup() -> registerObject() so that its not confused with enqueueUnusedObject()


git-svn-id: https://jmonkeyengine.googlecode.com/svn/trunk@10618 75d07b2b-3a1a-0410-a2c5-0572b91ccdca
3.0
sha..RD 12 years ago
parent bf2a663022
commit d6fbd97482
  1. 4
      engine/src/android/com/jme3/audio/android/AndroidOpenALSoftAudioRenderer.java
  2. 16
      engine/src/android/com/jme3/renderer/android/OGLESShaderRenderer.java
  3. 26
      engine/src/core/com/jme3/util/NativeObject.java
  4. 145
      engine/src/core/com/jme3/util/NativeObjectManager.java
  5. 4
      engine/src/jogl/com/jme3/audio/joal/JoalAudioRenderer.java
  6. 2
      engine/src/jogl/com/jme3/renderer/jogl/JoglGL1Renderer.java
  7. 8
      engine/src/jogl/com/jme3/renderer/jogl/JoglRenderer.java
  8. 4
      engine/src/lwjgl/com/jme3/audio/lwjgl/LwjglAudioRenderer.java
  9. 2
      engine/src/lwjgl/com/jme3/renderer/lwjgl/LwjglGL1Renderer.java
  10. 11
      engine/src/lwjgl/com/jme3/renderer/lwjgl/LwjglRenderer.java

@ -296,7 +296,7 @@ public class AndroidOpenALSoftAudioRenderer implements AndroidAudioRenderer, Run
id = ib.get(0); id = ib.get(0);
f.setId(id); f.setId(id);
objManager.registerForCleanup(f); objManager.registerObject(f);
} }
if (f instanceof LowPassFilter) { if (f instanceof LowPassFilter) {
@ -1212,7 +1212,7 @@ public class AndroidOpenALSoftAudioRenderer implements AndroidAudioRenderer, Run
id = ib.get(0); id = ib.get(0);
ab.setId(id); ab.setId(id);
objManager.registerForCleanup(ab); objManager.registerObject(ab);
} }
ab.getData().clear(); ab.getData().clear();

@ -942,7 +942,7 @@ public class OGLESShaderRenderer implements Renderer {
shader.clearUpdateNeeded(); shader.clearUpdateNeeded();
if (needRegister) { if (needRegister) {
// Register shader for clean up if it was created in this method. // Register shader for clean up if it was created in this method.
objManager.registerForCleanup(shader); objManager.registerObject(shader);
statistics.onNewShader(); statistics.onNewShader();
} else { } else {
// OpenGL spec: uniform locations may change after re-link // OpenGL spec: uniform locations may change after re-link
@ -1318,7 +1318,7 @@ public class OGLESShaderRenderer implements Renderer {
id = intBuf1.get(0); id = intBuf1.get(0);
fb.setId(id); fb.setId(id);
objManager.registerForCleanup(fb); objManager.registerObject(fb);
statistics.onNewFrameBuffer(); statistics.onNewFrameBuffer();
} }
@ -1673,7 +1673,7 @@ public class OGLESShaderRenderer implements Renderer {
texId = intBuf1.get(0); texId = intBuf1.get(0);
img.setId(texId); img.setId(texId);
objManager.registerForCleanup(img); objManager.registerObject(img);
statistics.onNewTexture(); statistics.onNewTexture();
} }
@ -1880,7 +1880,7 @@ public class OGLESShaderRenderer implements Renderer {
bufId = intBuf1.get(0); bufId = intBuf1.get(0);
vb.setId(bufId); vb.setId(bufId);
objManager.registerForCleanup(vb); objManager.registerObject(vb);
created = true; created = true;
} }
@ -2252,14 +2252,9 @@ public class OGLESShaderRenderer implements Renderer {
/** /**
* renderMeshVertexArray renders a mesh using vertex arrays * renderMeshVertexArray renders a mesh using vertex arrays
* @param mesh
* @param lod
* @param count
*/ */
private void renderMeshVertexArray(Mesh mesh, int lod, int count) { private void renderMeshVertexArray(Mesh mesh, int lod, int count) {
// IntMap<VertexBuffer> buffers = mesh.getBuffers(); for (VertexBuffer vb : mesh.getBufferList().getArray()) {
for (VertexBuffer vb : mesh.getBufferList().getArray()){
if (vb.getBufferType() == Type.InterleavedData if (vb.getBufferType() == Type.InterleavedData
|| vb.getUsage() == Usage.CpuOnly // ignore cpu-only buffers || vb.getUsage() == Usage.CpuOnly // ignore cpu-only buffers
|| vb.getBufferType() == Type.Index) { || vb.getBufferType() == Type.Index) {
@ -2306,7 +2301,6 @@ public class OGLESShaderRenderer implements Renderer {
indices = mesh.getBuffer(Type.Index);// buffers.get(Type.Index.ordinal()); indices = mesh.getBuffer(Type.Index);// buffers.get(Type.Index.ordinal());
} }
for (VertexBuffer vb : mesh.getBufferList().getArray()){ for (VertexBuffer vb : mesh.getBufferList().getArray()){
if (vb.getBufferType() == Type.InterleavedData if (vb.getBufferType() == Type.InterleavedData
|| vb.getUsage() == Usage.CpuOnly // ignore cpu-only buffers || vb.getUsage() == Usage.CpuOnly // ignore cpu-only buffers
|| vb.getBufferType() == Type.Index) { || vb.getBufferType() == Type.Index) {

@ -45,6 +45,11 @@ public abstract class NativeObject implements Cloneable {
public static final int INVALID_ID = -1; public static final int INVALID_ID = -1;
/**
* The object manager to which this NativeObject is registered to.
*/
protected NativeObjectManager objectManager = null;
/** /**
* The ID of the object, usually depends on its type. * The ID of the object, usually depends on its type.
* Typically returned from calls like glGenTextures, glGenBuffers, etc. * Typically returned from calls like glGenTextures, glGenBuffers, etc.
@ -82,9 +87,14 @@ public abstract class NativeObject implements Cloneable {
this.id = id; this.id = id;
} }
void setNativeObjectManager(NativeObjectManager objectManager) {
this.objectManager = objectManager;
}
/** /**
* Sets the ID of the GLObject. This method is used in Renderer and must * Sets the ID of the NativeObject. This method is used in Renderer and must
* not be called by the user. * not be called by the user.
*
* @param id The ID to set * @param id The ID to set
*/ */
public void setId(int id){ public void setId(int id){
@ -138,6 +148,7 @@ public abstract class NativeObject implements Cloneable {
try { try {
NativeObject obj = (NativeObject) super.clone(); NativeObject obj = (NativeObject) super.clone();
obj.handleRef = new Object(); obj.handleRef = new Object();
obj.objectManager = null;
obj.id = INVALID_ID; obj.id = INVALID_ID;
obj.updateNeeded = true; obj.updateNeeded = true;
return obj; return obj;
@ -187,4 +198,17 @@ public abstract class NativeObject implements Cloneable {
* should be functional for this object. * should be functional for this object.
*/ */
public abstract NativeObject createDestructableClone(); public abstract NativeObject createDestructableClone();
/**
* Reclaims native resources used by this NativeObject.
* It should be safe to call this method or even use the object
* after it has been reclaimed, unless {@link NativeObjectManager#UNSAFE} is
* set to true, in that case native buffers are also reclaimed which may
* introduce instability.
*/
public void dispose() {
if (objectManager != null) {
objectManager.markUnusedObject(this);
}
}
} }

@ -31,11 +31,13 @@
*/ */
package com.jme3.util; package com.jme3.util;
import com.jme3.scene.VertexBuffer; import com.jme3.renderer.Renderer;
import java.lang.ref.PhantomReference; import java.lang.ref.PhantomReference;
import java.lang.ref.ReferenceQueue; import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference; import java.lang.ref.WeakReference;
import java.util.HashSet; import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Queue;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
@ -51,6 +53,13 @@ public class NativeObjectManager {
private static final Logger logger = Logger.getLogger(NativeObjectManager.class.getName()); private static final Logger logger = Logger.getLogger(NativeObjectManager.class.getName());
/**
* Set to <code>true</code> to enable deletion of native buffers together with GL objects
* when requested. Note that usage of object after deletion could cause undefined results
* or native crashes, therefore by default this is set to <code>false</code>.
*/
public static boolean UNSAFE = false;
/** /**
* The maximum number of objects that should be removed per frame. * The maximum number of objects that should be removed per frame.
* If the limit is reached, no more objects will be removed for that frame. * If the limit is reached, no more objects will be removed for that frame.
@ -58,23 +67,26 @@ public class NativeObjectManager {
private static final int MAX_REMOVES_PER_FRAME = 100; private static final int MAX_REMOVES_PER_FRAME = 100;
/** /**
* The queue will receive notifications of {@link NativeObject}s which * Reference queue for {@link NativeObjectRef native object references}.
* are no longer referenced.
*/ */
private ReferenceQueue<Object> refQueue = new ReferenceQueue<Object>(); private ReferenceQueue<Object> refQueue = new ReferenceQueue<Object>();
/** /**
* List of currently active GLObjects. * List of currently active GLObjects.
*/ */
private HashSet<NativeObjectRef> refList private IntMap<NativeObjectRef> refMap = new IntMap<NativeObjectRef>();
= new HashSet<NativeObjectRef>();
/**
* List of real objects requested by user for deletion.
*/
private ArrayDeque<NativeObject> userDeletionQueue = new ArrayDeque<NativeObject>();
private class NativeObjectRef extends PhantomReference<Object>{ private static class NativeObjectRef extends PhantomReference<Object> {
private NativeObject objClone; private NativeObject objClone;
private WeakReference<NativeObject> realObj; private WeakReference<NativeObject> realObj;
public NativeObjectRef(NativeObject obj){ public NativeObjectRef(ReferenceQueue<Object> refQueue, NativeObject obj){
super(obj.handleRef, refQueue); super(obj.handleRef, refQueue);
assert obj.handleRef != null; assert obj.handleRef != null;
@ -84,18 +96,68 @@ public class NativeObjectManager {
} }
/** /**
* Register a GLObject with the manager. * (Internal use only) Register a <code>NativeObject</code> with the manager.
*/ */
public void registerForCleanup(NativeObject obj){ public void registerObject(NativeObject obj) {
NativeObjectRef ref = new NativeObjectRef(obj); if (obj.getId() <= 0) {
refList.add(ref); throw new IllegalArgumentException("object id must be greater than zero");
}
NativeObjectRef ref = new NativeObjectRef(refQueue, obj);
refMap.put(obj.getId(), ref);
obj.setNativeObjectManager(this);
if (logger.isLoggable(Level.FINEST)) { if (logger.isLoggable(Level.FINEST)) {
logger.log(Level.FINEST, "Registered: {0}", new String[]{obj.toString()}); logger.log(Level.FINEST, "Registered: {0}", new String[]{obj.toString()});
} }
} }
private void deleteNativeObject(Object rendererObject, NativeObject obj, NativeObjectRef ref,
boolean deleteGL, boolean deleteBufs) {
assert rendererObject != null;
// "obj" is considered the real object (with buffers and everything else)
// if "ref" is null.
NativeObject realObj = ref != null ?
ref.realObj.get() :
obj;
assert realObj == null || obj.getId() == realObj.getId();
if (deleteGL && obj.getId() > 0) {
// Unregister it from cleanup list.
NativeObjectRef ref2 = refMap.remove(obj.getId());
if (ref2 == null) {
throw new IllegalArgumentException("This NativeObject is not " +
"registered in this NativeObjectManager");
}
assert ref == null || ref == ref2;
// Delete object from the GL driver
obj.deleteObject(rendererObject);
assert obj.getId() == NativeObject.INVALID_ID;
if (logger.isLoggable(Level.FINEST)) {
logger.log(Level.FINEST, "Deleted: {0}", obj);
}
if (realObj != null){
// Note: make sure to reset them as well
// They may get used in a new renderer in the future
realObj.resetObject();
}
}
if (deleteBufs && UNSAFE && realObj != null) {
// Only the real object has native buffers.
// The destructable clone has nothing and cannot be used in this case.
realObj.deleteNativeBuffersInternal();
}
}
/** /**
* Deletes unused NativeObjects. * (Internal use only) Deletes unused NativeObjects.
* Will delete at most {@link #MAX_REMOVES_PER_FRAME} objects. * Will delete at most {@link #MAX_REMOVES_PER_FRAME} objects.
* *
* @param rendererObject The renderer object. * @param rendererObject The renderer object.
@ -103,14 +165,20 @@ public class NativeObjectManager {
*/ */
public void deleteUnused(Object rendererObject){ public void deleteUnused(Object rendererObject){
int removed = 0; int removed = 0;
while (removed < MAX_REMOVES_PER_FRAME && !userDeletionQueue.isEmpty()) {
// Remove user requested objects.
NativeObject obj = userDeletionQueue.pop();
deleteNativeObject(rendererObject, obj, null, true, true);
removed++;
}
while (removed < MAX_REMOVES_PER_FRAME) { while (removed < MAX_REMOVES_PER_FRAME) {
// Remove objects reclaimed by GC.
NativeObjectRef ref = (NativeObjectRef) refQueue.poll(); NativeObjectRef ref = (NativeObjectRef) refQueue.poll();
if (ref == null) { if (ref == null) {
break; break;
} }
refList.remove(ref); deleteNativeObject(rendererObject, ref.objClone, ref, true, false);
ref.objClone.deleteObject(rendererObject);
removed++; removed++;
} }
if (removed >= 1) { if (removed >= 1) {
@ -119,38 +187,49 @@ public class NativeObjectManager {
} }
/** /**
* Deletes all objects. Must only be called when display is destroyed. * (Internal use only) Deletes all objects.
* Must only be called when display is destroyed.
*/ */
public void deleteAllObjects(Object rendererObject){ public void deleteAllObjects(Object rendererObject){
deleteUnused(rendererObject); deleteUnused(rendererObject);
for (NativeObjectRef ref : refList){ for (IntMap.Entry<NativeObjectRef> entry : refMap) {
ref.objClone.deleteObject(rendererObject); NativeObjectRef ref = entry.getValue();
NativeObject realObj = ref.realObj.get(); deleteNativeObject(rendererObject, ref.objClone, ref, true, false);
if (realObj != null){
// Note: make sure to reset them as well
// They may get used in a new renderer in the future
realObj.resetObject();
}
} }
refList.clear(); assert refMap.size() == 0;
}
/**
* Marks the given <code>NativeObject</code> as unused,
* to be deleted on the next frame.
* Usage of this object after deletion will cause an exception.
* Note that native buffers are only reclaimed if
* {@link #UNSAFE} is set to <code>true</code>.
*
* @param obj The object to mark as unused.
*/
void enqueueUnusedObject(NativeObject obj) {
userDeletionQueue.push(obj);
} }
/** /**
* Resets all {@link NativeObject}s. * (Internal use only) Resets all {@link NativeObject}s.
* This is typically called when the context is restarted.
*/ */
public void resetObjects(){ public void resetObjects(){
for (NativeObjectRef ref : refList){ for (IntMap.Entry<NativeObjectRef> entry : refMap) {
// here we use the actual obj not the clone, // Must use the real object here, for this to be effective.
// otherwise its useless NativeObject realObj = entry.getValue().realObj.get();
NativeObject realObj = ref.realObj.get(); if (realObj == null) {
if (realObj == null)
continue; continue;
}
realObj.resetObject(); realObj.resetObject();
if (logger.isLoggable(Level.FINEST)) if (logger.isLoggable(Level.FINEST)) {
logger.log(Level.FINEST, "Reset: {0}", realObj); logger.log(Level.FINEST, "Reset: {0}", realObj);
}
} }
refList.clear(); refMap.clear();
} }
// public void printObjects(){ // public void printObjects(){

@ -294,7 +294,7 @@ public class JoalAudioRenderer implements AudioRenderer, Runnable {
id = ib.get(0); id = ib.get(0);
f.setId(id); f.setId(id);
objManager.registerForCleanup(f); objManager.registerObject(f);
} }
if (f instanceof LowPassFilter) { if (f instanceof LowPassFilter) {
@ -1033,7 +1033,7 @@ public class JoalAudioRenderer implements AudioRenderer, Runnable {
id = ib.get(0); id = ib.get(0);
ab.setId(id); ab.setId(id);
objManager.registerForCleanup(ab); objManager.registerObject(ab);
} }
ab.getData().clear(); ab.getData().clear();

@ -782,7 +782,7 @@ public class JoglGL1Renderer implements GL1Renderer {
gl.glGenTextures(1, ib1); gl.glGenTextures(1, ib1);
texId = ib1.get(0); texId = ib1.get(0);
img.setId(texId); img.setId(texId);
objManager.registerForCleanup(img); objManager.registerObject(img);
statistics.onNewTexture(); statistics.onNewTexture();
} }

@ -1143,7 +1143,7 @@ public class JoglRenderer implements Renderer {
shader.clearUpdateNeeded(); shader.clearUpdateNeeded();
if (needRegister) { if (needRegister) {
// Register shader for clean up if it was created in this method. // Register shader for clean up if it was created in this method.
objManager.registerForCleanup(shader); objManager.registerObject(shader);
statistics.onNewShader(); statistics.onNewShader();
} else { } else {
// OpenGL spec: uniform locations may change after re-link // OpenGL spec: uniform locations may change after re-link
@ -1506,7 +1506,7 @@ public class JoglRenderer implements Renderer {
gl.glGenFramebuffers(1, intBuf1); gl.glGenFramebuffers(1, intBuf1);
id = intBuf1.get(0); id = intBuf1.get(0);
fb.setId(id); fb.setId(id);
objManager.registerForCleanup(fb); objManager.registerObject(fb);
statistics.onNewFrameBuffer(); statistics.onNewFrameBuffer();
} }
@ -1899,7 +1899,7 @@ public class JoglRenderer implements Renderer {
gl.glGenTextures(1, intBuf1); gl.glGenTextures(1, intBuf1);
texId = intBuf1.get(0); texId = intBuf1.get(0);
img.setId(texId); img.setId(texId);
objManager.registerForCleanup(img); objManager.registerObject(img);
statistics.onNewTexture(); statistics.onNewTexture();
} }
@ -2141,7 +2141,7 @@ public class JoglRenderer implements Renderer {
gl.glGenBuffers(1, intBuf1); gl.glGenBuffers(1, intBuf1);
bufId = intBuf1.get(0); bufId = intBuf1.get(0);
vb.setId(bufId); vb.setId(bufId);
objManager.registerForCleanup(vb); objManager.registerObject(vb);
//statistics.onNewVertexBuffer(); //statistics.onNewVertexBuffer();

@ -266,7 +266,7 @@ public class LwjglAudioRenderer implements AudioRenderer, Runnable {
id = ib.get(0); id = ib.get(0);
f.setId(id); f.setId(id);
objManager.registerForCleanup(f); objManager.registerObject(f);
} }
if (f instanceof LowPassFilter) { if (f instanceof LowPassFilter) {
@ -1002,7 +1002,7 @@ public class LwjglAudioRenderer implements AudioRenderer, Runnable {
id = ib.get(0); id = ib.get(0);
ab.setId(id); ab.setId(id);
objManager.registerForCleanup(ab); objManager.registerObject(ab);
} }
ab.getData().clear(); ab.getData().clear();

@ -729,7 +729,7 @@ public class LwjglGL1Renderer implements GL1Renderer {
glGenTextures(ib1); glGenTextures(ib1);
texId = ib1.get(0); texId = ib1.get(0);
img.setId(texId); img.setId(texId);
objManager.registerForCleanup(img); objManager.registerObject(img);
statistics.onNewTexture(); statistics.onNewTexture();
} }

@ -55,6 +55,7 @@ import com.jme3.texture.Texture;
import com.jme3.texture.Texture.WrapAxis; import com.jme3.texture.Texture.WrapAxis;
import com.jme3.util.BufferUtils; import com.jme3.util.BufferUtils;
import com.jme3.util.ListMap; import com.jme3.util.ListMap;
import com.jme3.util.NativeObject;
import com.jme3.util.NativeObjectManager; import com.jme3.util.NativeObjectManager;
import com.jme3.util.SafeArrayList; import com.jme3.util.SafeArrayList;
import java.nio.*; import java.nio.*;
@ -129,10 +130,12 @@ public class LwjglRenderer implements Renderer {
nameBuf.rewind(); nameBuf.rewind();
} }
@Override
public Statistics getStatistics() { public Statistics getStatistics() {
return statistics; return statistics;
} }
@Override
public EnumSet<Caps> getCaps() { public EnumSet<Caps> getCaps() {
return caps; return caps;
} }
@ -1081,7 +1084,7 @@ public class LwjglRenderer implements Renderer {
shader.clearUpdateNeeded(); shader.clearUpdateNeeded();
if (needRegister) { if (needRegister) {
// Register shader for clean up if it was created in this method. // Register shader for clean up if it was created in this method.
objManager.registerForCleanup(shader); objManager.registerObject(shader);
statistics.onNewShader(); statistics.onNewShader();
} else { } else {
// OpenGL spec: uniform locations may change after re-link // OpenGL spec: uniform locations may change after re-link
@ -1431,7 +1434,7 @@ public class LwjglRenderer implements Renderer {
glGenFramebuffersEXT(intBuf1); glGenFramebuffersEXT(intBuf1);
id = intBuf1.get(0); id = intBuf1.get(0);
fb.setId(id); fb.setId(id);
objManager.registerForCleanup(fb); objManager.registerObject(fb);
statistics.onNewFrameBuffer(); statistics.onNewFrameBuffer();
} }
@ -1802,7 +1805,7 @@ public class LwjglRenderer implements Renderer {
glGenTextures(intBuf1); glGenTextures(intBuf1);
texId = intBuf1.get(0); texId = intBuf1.get(0);
img.setId(texId); img.setId(texId);
objManager.registerForCleanup(img); objManager.registerObject(img);
statistics.onNewTexture(); statistics.onNewTexture();
} }
@ -2041,7 +2044,7 @@ public class LwjglRenderer implements Renderer {
glGenBuffers(intBuf1); glGenBuffers(intBuf1);
bufId = intBuf1.get(0); bufId = intBuf1.get(0);
vb.setId(bufId); vb.setId(bufId);
objManager.registerForCleanup(vb); objManager.registerObject(vb);
//statistics.onNewVertexBuffer(); //statistics.onNewVertexBuffer();

Loading…
Cancel
Save