Pārlūkot izejas kodu

* 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

sha..RD 12 gadi atpakaļ
vecāks
revīzija
d6fbd97482

+ 2 - 2
engine/src/android/com/jme3/audio/android/AndroidOpenALSoftAudioRenderer.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();

+ 5 - 11
engine/src/android/com/jme3/renderer/android/OGLESShaderRenderer.java

@@ -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) {

+ 25 - 1
engine/src/core/com/jme3/util/NativeObject.java

@@ -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);
+        }
+    }
 }

+ 113 - 34
engine/src/core/com/jme3/util/NativeObjectManager.java

@@ -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;
 
@@ -50,7 +52,14 @@ import java.util.logging.Logger;
 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);
         }
-        refList.clear();
+        assert refMap.size() == 0;
     }
 
     /**
-     * Resets all {@link NativeObject}s.
+     * 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);
+    }
+    
+    /**
+     * (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(){

+ 2 - 2
engine/src/jogl/com/jme3/audio/joal/JoalAudioRenderer.java

@@ -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();

+ 1 - 1
engine/src/jogl/com/jme3/renderer/jogl/JoglGL1Renderer.java

@@ -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();
         }

+ 4 - 4
engine/src/jogl/com/jme3/renderer/jogl/JoglRenderer.java

@@ -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();
 

+ 2 - 2
engine/src/lwjgl/com/jme3/audio/lwjgl/LwjglAudioRenderer.java

@@ -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();

+ 1 - 1
engine/src/lwjgl/com/jme3/renderer/lwjgl/LwjglGL1Renderer.java

@@ -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();
         }

+ 7 - 4
engine/src/lwjgl/com/jme3/renderer/lwjgl/LwjglRenderer.java

@@ -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();