StatCollector and Canvas issue

Hey all,



So I have been refactoring all the 1.0-2.0 changes (and fixing issues that pop-up) the last couple of days and have come across something that makes me go 'huh?!?..'



I have built my own Debugging class (to share between canvas and norm implementantions) and put the StatCollector stuff in there.  For non-canvas stuff it works great, but when rendered on a canvas none of the state data is updated…



I have verified this by going through ALL the data that is collected each update and only the actualTime is update, no enteries are added to the Historical data (MultiStatSample list).  This is done RIGHT after I call StatCollector.update(); so I know its getting called…



I have also gone through ALL of the 'levels' of the simpleGame (down to abstractGame) and cannot find anything that is not getting called in the canvas implementation, furthermore when I remove all extra code and use the 'skeleton' abstractGame the problem never occurs; the StatCollector just happily collects the stats.



My next step is to develop a 'skeleton' canvas and test it out, but I thought I would post here first…

did you set System.setProperty("jme.stats", "set"); before staring the game?

YES!



In fact if this is NOT set it throws a EXT_FrameBuffer_Exception when you call StatCollector.update(); maybe not the best tatic for an API, better to just log the error and continue…

Okay, so I finally got a test wiped up; its rather long so please be patient. 



There are basically 5 classes:

StatCollectorTest - Top Class, has main method and launches choosen test

CanvasTest - AWT canvas implementation test

NonCanvasTest - ‘normal’ implementation test

GfxCanvas - Actual AWT canvas implentation

SceneDebug - Handles all the statCollector stuff for both Canvas and NonCanvas implementations.



The default test is CanvasTest, to switch simply switch the boolean testCanvas to false (or supply “non-canvas” as an argument at the cmd line).  To turn off the CollectorHistory printouts simple switch debugCollectorHistory to false,  although this does show the differences in the stats collected for Canvas vs. NonCanvas…



I had REALLY hoped that making this test would help clarify the issue, but alas it has not (which makes me think it must be something ‘under-the-hood’).



Thanx for your time…



NOTE: I had to host the test on a different site since it exceeded 20,000 chars (maybe that is a little restrictive…)

http://freetexthost.com/wucjzksthy

HaHa @ myself.  Core-Dump you were right!



I WAS calling

System.setProperty( "jme.stats", "set" );

, but NOT before the DisplaySystem initialized I guess…



That might be a source of problems for others also, maybe there should be a log entry supplied EVERY time the StatCollector is updated to inform the user that the stats are not actually being maintained.  (If a user is calling the StatCollector.update() method then it is reasonable to expect they mean to use the StatCollector…)



So heres what I have encountered:

If the  "jme.stats" property is never set, when the StatCollector.update() method is called an UNSUPPORTED_EXT exception is thrown.

If the  "jme.stats" property is set after the DisplaySystem initializes then the stats are NOT maintained and the user is never informed.



Also, I do know that that property is declared final (or something) to help the compiler/optimizer unwrap, however is the performance loss due to collecting the stats so insignificant that it can be ignored?  If the performance loss is negligible then GREAT!  If there is a measurable performance loss though, it would make more sense to me to allow the user to choose when they want to sacrifice some performance for logging…



EDIT: nvm about the perfomance… I guess you could just pause the StatCollector and do the same thing.

glad you figured it out :slight_smile:

hmmm, well I guess I spoke too soon.   



In all my tests now the StatCollector updates and renders fine, however in the actual application this is thrown when the update method is called:


Dec 9, 2008 4:08:21 PM class com.jme.renderer.lwjgl.LWJGLTextureRenderer render(Spatial, Texture, boolean)
SEVERE: Exception
java.lang.RuntimeException: FrameBuffer: 1, has caused a GL_FRAMEBUFFER_UNSUPPORTED_EXT exception
        at com.jme.renderer.lwjgl.LWJGLTextureRenderer.checkFBOComplete(LWJGLTextureRenderer.java:754)
        at com.jme.renderer.lwjgl.LWJGLTextureRenderer.setupForSingleTexDraw(LWJGLTextureRenderer.java:682)
        at com.jme.renderer.lwjgl.LWJGLTextureRenderer.render(LWJGLTextureRenderer.java:531)
        at com.jme.renderer.lwjgl.LWJGLTextureRenderer.render(LWJGLTextureRenderer.java:507)
        at com.jme.util.stat.graph.LineGrapher.statsUpdated(LineGrapher.java:227)
        at com.jme.util.stat.StatCollector.fireActionEvent(StatCollector.java:328)
        at com.jme.util.stat.StatCollector.update(StatCollector.java:255)


and I cant figure out why :(.  I have constructed my test so that is uses the exact same setup as the application does; which is like this: canvas is added to (custom) tabbedPane and that in turn is added to a split pane.  In the test this functions great; but like I said; in the real deal it throws the error.

I had a hunch that it might be thread stuff, but in both the test the Update method is called from the AWT-Thread and in the non-canvas test it is called from the main thread itself. So that's not it...

I also tried making sure all the creation stuff happened in the rendering thread but to no avail :(...

getting REALLY frustrated...

If ANYONE has any pointers, please feel free to suggest...

At this point the problem must lie in the 1000s of lines in the actual application (although I have narrowed it done to one section that knows NOTHING of jME :(), so posting any of it would not help a whole lot.  The test code posted above is still VERY similar, except that I now add the canvas to the tabbedPane then to the split pane...

Alright, I have FINALLY figured out what the problem is.



http://www.jmonkeyengine.com/jmeforum/index.php?topic=8902.msg75204#msg75204



read that post for background first (it IS the issue…)



I must say that the canvas gfx context being destroyed whenever it is removed is just kinda wrong…

It DOES make sense to have it like that, but there really should be a way to override that behavior.

I have spent at (wasted) least 20 hours of my life on this stupid 'issue' when a simple boolean flag to AUTO-Destroy the context is all that is needed.



In fact this could be accomplished in less than 5 mins!  Just add a setters and getters to the JmeCanvas interface and have both the LwjglCanvas and JoglCanvas check that boolean when removeNotify is fired. Then add a flag to determine if the GFX context has already been initialized.

Okay, got some diff patches done…



These patches simply add the functionality to not have the GL context destroyed when removed from the parent container; the default behavior of 'auto-closing' the GL context is still enforced so there should be NO impact on any user…



JmeCanvas Diff:


# This patch file was generated by NetBeans IDE
# Following Index: paths are relative to: /Volumes/Storage/Documents/Programs/_jME2.0/jME_2.0_JAR/src/com/jme/system/canvas
# This patch can be applied using context Tools: Patch action on respective folder.
# It uses platform neutral UTF-8 encoding and n newlines.
# Above lines and this line are ignored by the patching process.
Index: JMECanvas.java
--- JMECanvas.java Base (BASE)
+++ JMECanvas.java Locally Modified (Based On LOCAL)
@@ -63,7 +63,27 @@
      */
     void setUpdateInput(boolean doUpdate);
 
+   
     /**
+     * @param shouldAutoKillGfxContext
+     *          true(default) if the GFX Context should be destroyed
+     *          as soon as the canvas is removed from it's parent container
+     */
+    void setAutoKillGfxContext( boolean shouldAutoKillGfxContext );
+   
+    /**
+     * @return
+     *      true(default) if the GFX Context should be destroyed
+     *      as soon as the canvas is removed from it's parent container
+     */
+    boolean shouldAutoKillGfxContext();
+   
+    /**
+     *  Destroy GFX context
+     */
+    void killGfxContext();
+
+    /**
      * Set the desired update/redraw frequency of this canvas. If
      * setDrawWhenDirty was called with true, this frequency is just a cap to
      * possible redraw rate.



LWJGLCanvas:


# This patch file was generated by NetBeans IDE
# Following Index: paths are relative to: /Volumes/Storage/Documents/Programs/_jME2.0/jME_2.0_JAR/src/com/jmex/awt/lwjgl
# This patch can be applied using context Tools: Patch action on respective folder.
# It uses platform neutral UTF-8 encoding and n newlines.
# Above lines and this line are ignored by the patching process.
Index: LWJGLCanvas.java
--- LWJGLCanvas.java Base (BASE)
+++ LWJGLCanvas.java Locally Modified (Based On LOCAL)
@@ -75,6 +75,8 @@
 
     private long lastRender = 0;
 
+    private boolean shouldAutoKillContext = true;
+    private boolean glInitialized = false;
     private boolean drawWhenDirty = false;
     private boolean dirty = true;
 
@@ -93,6 +95,12 @@
 
     @Override
     protected void initGL() {
+       
+        if( glInitialized ){
+            return;
+        }
+        glInitialized = true;
+               
         try {
             LWJGLDisplaySystem display = (LWJGLDisplaySystem) DisplaySystem
                     .getDisplaySystem();
@@ -192,6 +200,7 @@
         return syncRate;
     }
 
+
     public void setDrawWhenDirty(boolean whenDirty) {
         this.drawWhenDirty = whenDirty;
     }
@@ -203,4 +212,25 @@
     public void makeDirty() {
         dirty = true;
     }
+   
+    @Override
+    public void removeNotify() {
+        if ( shouldAutoKillContext ) {
+            glInitialized = false;
+            super.removeNotify();
 }
+    }
+
+    public void setAutoKillGfxContext( boolean shouldAutoKillGfxContext ) {
+        this.shouldAutoKillContext = shouldAutoKillGfxContext;
+    }
+
+    public boolean shouldAutoKillGfxContext() {
+        return shouldAutoKillContext;
+    }
+
+    public void killGfxContext() {
+        glInitialized = false;
+        super.removeNotify();
+    }
+}



JOGLCanvas:


# This patch file was generated by NetBeans IDE
# Following Index: paths are relative to: /Volumes/Storage/Documents/Programs/_jME2.0/jME_2.0_JAR/src/com/jmex/awt/jogl
# This patch can be applied using context Tools: Patch action on respective folder.
# It uses platform neutral UTF-8 encoding and n newlines.
# Above lines and this line are ignored by the patching process.
Index: JOGLAWTCanvas.java
--- JOGLAWTCanvas.java Base (BASE)
+++ JOGLAWTCanvas.java Locally Modified (Based On LOCAL)
@@ -80,6 +80,8 @@
 
     private long lastRender = 0;
 
+    private boolean shouldAutoKillContext = true;
+    private boolean glInitialized = false;
     private boolean drawWhenDirty = false;
     private boolean dirty = true;
     private boolean updateInput = false;
@@ -145,6 +147,12 @@
     /* GLEventListener Methods


*/
 
     public void init(final GLAutoDrawable drawable) {
+       
+        if( glInitialized ){
+            return;
+        }
+        glInitialized = true;
+       
         logger.info("init " + drawable);
 
         // Switching the context is not necessary, since this is handled by the
@@ -243,4 +251,25 @@
     public void makeDirty() {
         dirty = true;
     }
+   
+    @Override
+    public void removeNotify() {
+        if ( shouldAutoKillContext ) {
+            glInitialized = false;
+            super.removeNotify();
 }
+    }
+
+    public void setAutoKillGfxContext( boolean shouldAutoKillGfxContext ) {
+        this.shouldAutoKillContext = shouldAutoKillGfxContext;
+    }
+
+    public boolean shouldAutoKillGfxContext() {
+        return shouldAutoKillContext;
+    }
+
+    public void killGfxContext() {
+        glInitialized = false;
+        super.removeNotify();
+    }
+}



LWJGLSWTCanvas:


# This patch file was generated by NetBeans IDE
# Following Index: paths are relative to: /Volumes/Storage/Documents/Programs/_jME2.0/jME_2.0_JAR/src/com/jmex/swt/lwjgl
# This patch can be applied using context Tools: Patch action on respective folder.
# It uses platform neutral UTF-8 encoding and n newlines.
# Above lines and this line are ignored by the patching process.
Index: LWJGLSWTCanvas.java
--- LWJGLSWTCanvas.java Base (BASE)
+++ LWJGLSWTCanvas.java Locally Modified (Based On LOCAL)
@@ -78,6 +78,8 @@
 
     private long lastRender = 0;
 
+    private boolean shouldAutoKillContext = true;
+    private boolean glInitialized = false;
     private boolean drawWhenDirty = false;
     private boolean dirty = true;
     private boolean doUpdateOnly = false;
@@ -112,6 +114,13 @@
     }
 
     public void init() {
+       
+        if( glInitialized ){
+            return;
+        }
+        glInitialized = true;
+       
+       
         try {
             LWJGLDisplaySystem display = (LWJGLDisplaySystem) DisplaySystem
                     .getDisplaySystem();
@@ -220,4 +229,25 @@
     public boolean isDirty() {
         return dirty;
     }
+   
+    @Override
+    public void dispose() {
+                if ( shouldAutoKillContext ) {
+            glInitialized = false;
+            super.dispose();
 }
+    }
+
+    public void setAutoKillGfxContext( boolean shouldAutoKillGfxContext ) {
+        this.shouldAutoKillContext = shouldAutoKillGfxContext;
+    }
+
+    public boolean shouldAutoKillGfxContext() {
+        return shouldAutoKillContext;
+    }
+   
+    public void killGfxContext() {
+        glInitialized = false;
+        super.dispose();
+    }
+}



In fact this could be accomplished in less than 5 mins!

EDIT: HOLY CRAP 4:59 secs! (and I even had to track down the darn eclipse canvas stuff ).

and yup, as SOON as I started using these changes my problems went away :D

EDIT: Added JavaDoc comments to JMECanvas implementation...

I do know that jME is going through some 'management' changes, so maybe I should just take the initiative and commit these myself…



If anyone has any objections let me know, I'll wait 24 hours before committing…

Nice. These canvas behaviors have been a real pain in the a–. I suggest posting your patches in the contrib depot.

Congratulations. Very nice job. Nice patches for all DisplaySystems.



Thanks.

Thanx :slight_smile:



but at least half the credit goes to nymon, he is the one that actually figured it out; I just made the patches…

The @Override above "public void dispose()" is not Java 1.5 compliant, could someone remove that from SVN?

Also, here, super.dispose() isn't a valid method. (in dispose() and killGfxContext()) Any suggestions? Right now I just comment them out and that seems to work fine. I have a suspision it is due to the OSX SWT_fake lib… (?)



TIA,

Alex

Umm, they are valid (I dunno about with the fake_swt.jar file though, which is only intended for Java Cocoa and OSX).


public class LWJGLSWTCanvas extends GLCanvas implements JMECanvas {

public class GLCanvas extends Canvas implements GLAutoDrawable {



(And Canvas.dispose() is a valid method...)

You're right about the override being valid, I thought it was the "@Override for interface implementations" thing…

This is mine from swt_fake: (I don't have the source)

// Compiled from GLCanvas.java (version 1.5 : 49.0, super bit)
public class org.eclipse.swt.opengl.GLCanvas extends org.eclipse.swt.widgets.Composite

Yeah, must be an issue with the swt_fake.jar file.



org.eclipse.swt.widgets.Composite is derived from org.eclipse.swt.widgets.Widget; which does have a dispose() method…