* 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. 143
      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);
f.setId(id);
objManager.registerForCleanup(f);
objManager.registerObject(f);
}
if (f instanceof LowPassFilter) {
@ -1212,7 +1212,7 @@ public class AndroidOpenALSoftAudioRenderer implements AndroidAudioRenderer, Run
id = ib.get(0);
ab.setId(id);
objManager.registerForCleanup(ab);
objManager.registerObject(ab);
}
ab.getData().clear();

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

@ -45,6 +45,11 @@ public abstract class NativeObject implements Cloneable {
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.
* Typically returned from calls like glGenTextures, glGenBuffers, etc.
@ -82,9 +87,14 @@ public abstract class NativeObject implements Cloneable {
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.
*
* @param id The ID to set
*/
public void setId(int id){
@ -138,6 +148,7 @@ public abstract class NativeObject implements Cloneable {
try {
NativeObject obj = (NativeObject) super.clone();
obj.handleRef = new Object();
obj.objectManager = null;
obj.id = INVALID_ID;
obj.updateNeeded = true;
return obj;
@ -187,4 +198,17 @@ public abstract class NativeObject implements Cloneable {
* should be functional for this object.
*/
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;
import com.jme3.scene.VertexBuffer;
import com.jme3.renderer.Renderer;
import java.lang.ref.PhantomReference;
import java.lang.ref.ReferenceQueue;
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.Logger;
@ -51,6 +53,13 @@ public class NativeObjectManager {
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.
* 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;
/**
* The queue will receive notifications of {@link NativeObject}s which
* are no longer referenced.
* Reference queue for {@link NativeObjectRef native object references}.
*/
private ReferenceQueue<Object> refQueue = new ReferenceQueue<Object>();
/**
* List of currently active GLObjects.
*/
private HashSet<NativeObjectRef> refList
= new HashSet<NativeObjectRef>();
private IntMap<NativeObjectRef> refMap = new IntMap<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 WeakReference<NativeObject> realObj;
public NativeObjectRef(NativeObject obj){
public NativeObjectRef(ReferenceQueue<Object> refQueue, NativeObject obj){
super(obj.handleRef, refQueue);
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){
NativeObjectRef ref = new NativeObjectRef(obj);
refList.add(ref);
public void registerObject(NativeObject obj) {
if (obj.getId() <= 0) {
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)) {
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.
*
* @param rendererObject The renderer object.
@ -103,14 +165,20 @@ public class NativeObjectManager {
*/
public void deleteUnused(Object rendererObject){
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) {
// Remove objects reclaimed by GC.
NativeObjectRef ref = (NativeObjectRef) refQueue.poll();
if (ref == null) {
break;
}
refList.remove(ref);
ref.objClone.deleteObject(rendererObject);
deleteNativeObject(rendererObject, ref.objClone, ref, true, false);
removed++;
}
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){
deleteUnused(rendererObject);
for (NativeObjectRef ref : refList){
ref.objClone.deleteObject(rendererObject);
NativeObject realObj = ref.realObj.get();
if (realObj != null){
// Note: make sure to reset them as well
// They may get used in a new renderer in the future
realObj.resetObject();
for (IntMap.Entry<NativeObjectRef> entry : refMap) {
NativeObjectRef ref = entry.getValue();
deleteNativeObject(rendererObject, ref.objClone, ref, true, false);
}
assert refMap.size() == 0;
}
refList.clear();
/**
* 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(){
for (NativeObjectRef ref : refList){
// here we use the actual obj not the clone,
// otherwise its useless
NativeObject realObj = ref.realObj.get();
if (realObj == null)
for (IntMap.Entry<NativeObjectRef> entry : refMap) {
// Must use the real object here, for this to be effective.
NativeObject realObj = entry.getValue().realObj.get();
if (realObj == null) {
continue;
}
realObj.resetObject();
if (logger.isLoggable(Level.FINEST))
if (logger.isLoggable(Level.FINEST)) {
logger.log(Level.FINEST, "Reset: {0}", realObj);
}
refList.clear();
}
refMap.clear();
}
// public void printObjects(){

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

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

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

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

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

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

Loading…
Cancel
Save