From d6fbd974828ed9dd91b8936a508bf65208e4e020 Mon Sep 17 00:00:00 2001 From: "sha..RD" Date: Thu, 16 May 2013 00:53:58 +0000 Subject: [PATCH] * 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 --- .../AndroidOpenALSoftAudioRenderer.java | 4 +- .../renderer/android/OGLESShaderRenderer.java | 16 +- .../src/core/com/jme3/util/NativeObject.java | 26 +++- .../com/jme3/util/NativeObjectManager.java | 147 ++++++++++++++---- .../jme3/audio/joal/JoalAudioRenderer.java | 4 +- .../jme3/renderer/jogl/JoglGL1Renderer.java | 2 +- .../com/jme3/renderer/jogl/JoglRenderer.java | 8 +- .../jme3/audio/lwjgl/LwjglAudioRenderer.java | 4 +- .../jme3/renderer/lwjgl/LwjglGL1Renderer.java | 2 +- .../jme3/renderer/lwjgl/LwjglRenderer.java | 11 +- 10 files changed, 162 insertions(+), 62 deletions(-) diff --git a/engine/src/android/com/jme3/audio/android/AndroidOpenALSoftAudioRenderer.java b/engine/src/android/com/jme3/audio/android/AndroidOpenALSoftAudioRenderer.java index 1a0672033..1c00969d6 100644 --- a/engine/src/android/com/jme3/audio/android/AndroidOpenALSoftAudioRenderer.java +++ b/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(); diff --git a/engine/src/android/com/jme3/renderer/android/OGLESShaderRenderer.java b/engine/src/android/com/jme3/renderer/android/OGLESShaderRenderer.java index f2b7ae1b9..378b2527d 100644 --- a/engine/src/android/com/jme3/renderer/android/OGLESShaderRenderer.java +++ b/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 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) { diff --git a/engine/src/core/com/jme3/util/NativeObject.java b/engine/src/core/com/jme3/util/NativeObject.java index e9f637ee7..4827265ef 100644 --- a/engine/src/core/com/jme3/util/NativeObject.java +++ b/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); + } + } } diff --git a/engine/src/core/com/jme3/util/NativeObjectManager.java b/engine/src/core/com/jme3/util/NativeObjectManager.java index 8dc7d8757..f112cf991 100644 --- a/engine/src/core/com/jme3/util/NativeObjectManager.java +++ b/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 true 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 false. + */ + 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 refQueue = new ReferenceQueue(); /** * List of currently active GLObjects. */ - private HashSet refList - = new HashSet(); + private IntMap refMap = new IntMap(); + + /** + * List of real objects requested by user for deletion. + */ + private ArrayDeque userDeletionQueue = new ArrayDeque(); - private class NativeObjectRef extends PhantomReference{ + private static class NativeObjectRef extends PhantomReference { private NativeObject objClone; private WeakReference realObj; - public NativeObjectRef(NativeObject obj){ + public NativeObjectRef(ReferenceQueue 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 NativeObject 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 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 NativeObject 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 true. + * + * @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 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(){ diff --git a/engine/src/jogl/com/jme3/audio/joal/JoalAudioRenderer.java b/engine/src/jogl/com/jme3/audio/joal/JoalAudioRenderer.java index 6f28a1866..9f99ba458 100644 --- a/engine/src/jogl/com/jme3/audio/joal/JoalAudioRenderer.java +++ b/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(); diff --git a/engine/src/jogl/com/jme3/renderer/jogl/JoglGL1Renderer.java b/engine/src/jogl/com/jme3/renderer/jogl/JoglGL1Renderer.java index e8ed65b71..8cb23dbfa 100644 --- a/engine/src/jogl/com/jme3/renderer/jogl/JoglGL1Renderer.java +++ b/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(); } diff --git a/engine/src/jogl/com/jme3/renderer/jogl/JoglRenderer.java b/engine/src/jogl/com/jme3/renderer/jogl/JoglRenderer.java index 544c28f1a..95ec79a59 100644 --- a/engine/src/jogl/com/jme3/renderer/jogl/JoglRenderer.java +++ b/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(); diff --git a/engine/src/lwjgl/com/jme3/audio/lwjgl/LwjglAudioRenderer.java b/engine/src/lwjgl/com/jme3/audio/lwjgl/LwjglAudioRenderer.java index 4d80b3b8f..dc8628975 100644 --- a/engine/src/lwjgl/com/jme3/audio/lwjgl/LwjglAudioRenderer.java +++ b/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(); diff --git a/engine/src/lwjgl/com/jme3/renderer/lwjgl/LwjglGL1Renderer.java b/engine/src/lwjgl/com/jme3/renderer/lwjgl/LwjglGL1Renderer.java index f1a1a3207..c0bb17daa 100644 --- a/engine/src/lwjgl/com/jme3/renderer/lwjgl/LwjglGL1Renderer.java +++ b/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(); } diff --git a/engine/src/lwjgl/com/jme3/renderer/lwjgl/LwjglRenderer.java b/engine/src/lwjgl/com/jme3/renderer/lwjgl/LwjglRenderer.java index 044d2e1a4..0b2cd1507 100644 --- a/engine/src/lwjgl/com/jme3/renderer/lwjgl/LwjglRenderer.java +++ b/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 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();