Weird problems when recreating the jme window

We are using JME 1.0 in our SWT application and we are now seeing weird problems if the JME window is closed and then recreated. Our 3D view's colors become dark when the view is recreated so I think that OpenGL state becomes messed up. Could someone give me some hints where I should start my search for the bug?

You could try invalidating jME's view of states on recreation:


DisplaySystem.getDisplaySystem().getCurrentContext().invalidateStates();

Ok, now it looks otherwise better but I have some text written in ORTHO mode and those appear as white boxes in the view.

Sounds like the textures were lost when the display was recreated?

Try releasing the texture cache.


TextureManager.clearCache();



For completeness, you might want to call

TextureManager.doTextureCleanup();

before closing the old display to release your old textures from the card nicely.

So iow:

TextureManager.doTextureCleanup();
TextureManager.clearCache();
Reset the display.
DisplaySystem.getDisplaySystem().getCurrentContext().invalidateStates();


Didn't help. Texts are still white boxes in the view. Here's how I create them:



/**
    *
    */
   protected void initText(String msg) {   
      Renderer renderer = DisplaySystem.getDisplaySystem().getRenderer();
      TextureState font = initFont(renderer);
      AlphaState as1 = initTextAlphaState(renderer);
      
      text = new Text("Text", msg);      
      text.setTextureCombineMode(TextureState.REPLACE);
      //text.setForceView(true);               
      text.setLightCombineMode(LightState.OFF);            
      
      text.setRenderState(font);
      text.setRenderState(as1);
      text.updateRenderState();
      this.setRenderQueueMode(Renderer.QUEUE_ORTHO);
      
      this.attachChild(text);
   }
   
      
   /**
    *
    */
   protected TextureState initFont(Renderer renderer) {
      TextureState font = renderer.createTextureState();               
      font.setTexture(TextureManager.loadTexture(SWTGame.class
            .getClassLoader().getResource(FONT_LOCATION), Texture.MM_LINEAR,
            Texture.FM_LINEAR));
      
      font.setEnabled(true);
      return font;
   }
   
   
   /**
    *
    */
   protected AlphaState initTextAlphaState(Renderer renderer) {
      AlphaState as1 = renderer.createAlphaState();
      as1.setBlendEnabled(true);
      as1.setSrcFunction(AlphaState.SB_SRC_ALPHA);
      as1.setDstFunction(AlphaState.DB_ONE);
      as1.setTestEnabled(true);
      as1.setTestFunction(AlphaState.TF_GREATER);
      as1.setEnabled(true);                  
      return as1;
   }

Were the textures fine in the recreated window before invalidating the states?

No, texts are shown as white boxes even if I don't invalidate the states.

Try doing an updateRenderStates() on the rootNode after you have created the new window if you haven't done that already :slight_smile:

Just wanna confirm that I have the same problem that textures just don't work after doing dispose() followed by setVisual(true) on a JFrame. Can't really see any difference in the scenegraph I have also tried all the suggestions in this thread without any change.



Trying to find a solution…

In our implementation, this line helped:


Text.resetFontTexture();

renanse said:

TextureManager.doTextureCleanup();




Following the debuger while trying to understand how things works I noticed that my application generated a silent exception.
Yeah I just noticed that doTextureCleanup() must be called from the gl thread and that the wiki says that after looking around.

But still if you didn't know that and use it you will not notice that you are doing something wrong

    public static void doTextureCleanup() {
        if (DisplaySystem.getDisplaySystem() == null || DisplaySystem.getDisplaySystem().getRenderer() == null)
            return;
        TextureState ts = DisplaySystem.getDisplaySystem().getRenderer().createTextureState();
        for (Integer i : cleanupStore) {
            if (i != null) {
                try {
                    ts.deleteTextureId(i.intValue());
                } catch (Exception e) {} // ignore.
            }
        }
    }



since the exception deleteTextureId throws is ignored. Maby add a javadoc note to all methods that must be called from the gl thread if the exception is ignored :)

Yeah, it's ignored because it generally is being called on close.  But yeah, good idea.

TextureManager.doTextureCleanup();

TextureManager.clearCache();



Did the trick when you call it from the right thread btw, so thanx :slight_smile:

Lots of things to learn but im getting there  :smiley:



oh wait I also had to do a (from the GL thread)



m_textureState.load();



after the dispose for all my TextureStates

Ok there are still problems with Texts. I have located this problem



TextureManager.doTextureCleanup() does what is it supposed to but in LWJGLRenderer we have a LWJGLFont font object. After a TextureCleanup I think that the associated Texture is no longer valid so this font object needs to be recreated. I still a beginner when it comes to GL but isn’t the LWJGLFont.buildDisplayList() that is called when creating a new LWJGLFont linked to a specific texture or is it just a list to point to positions in a generic texture?



I found a piece of code that does the GL font setup that is a bit more readable then the jme implementation. The question is: If the fontTextureID of the texture change must createdFont2D be called again?



http://users.xith.org/JavaCoolDude/JWS/Lwjgl/CEBumpMap/source/GeomUtils/Text2D.java



There is no API interface to clear this font so to test I just implemented



public void clearFont()

{

    font=null;

}



in LWJGLRenderer.



By clearing this and doing what I wrote in previous post works, but only when I use my own font texture. Still didn’t get it to work with the defaultfont.



Feels like I’m in uncharted water when disposing JFrames and not exiting the application :X

The display lists are just simple uvs and verts.  uvs don't tie to a particular texture, they're just data that is used with any currently set texture.  Actually, it looks like the problem is indeed that you are in uncharted waters here.  Cleanup is meant to cleanup the card, not really prepare it for reuse.  The Texture objects still have the old texture id on them, even though they've been cleaned up.  To remedy this, we'd have to reset the texture ids on the Texture objects to 0 so on apply they'd know to reload.  (Which is why you are having to call load() on your texstates.)  Can you try calling load on Text.getDefaultFontTextureState()?  I'll add a reset method for future use.

Update!



Everything seems to work but not as it should.



This is what I have to do right now:



TextureManager.doTextureCleanup();

TextureManager.clearCache();

jframe.dispose();

jframe.setVisible();

DisplaySystem.getDisplaySystem().getCurrentContext().invalidateStates();

((LWJGLRenderer)DisplaySystem.getDisplaySystem().getRenderer()).clearFont();

load() on all TextureStates

updateRenderState on my 3D and Hud rootNode



Would like to reinit before I set the frame Visible but then I will have to rewrite/extend GL canvas since it only runs the GL thread when the frame is visible :). Will probably do that later.



There are some weird things tho, that maby you can help explain.



I have two different scenegraphs that I render separately one for my 3D scene and one 2D Hud(Ortho)



Right now the 3D scene is just a Box and the 2D Hud is some textured quads and a Text object.

After the above cleanup and reinit are done the AlphaState in the 2D hud doesn't work, thats why the defaultfont where just white blocks(before clearFont() they aren't even visible). But when I created my 3D scene with an AlphaState in it then the 2D Hud also start to work after a dispose().



[Q] So my question is how can adding a AlphaState to the 3D tree make the Hud tree work after a dispose() since they are rendered separately and don't share any states? Well I guess they are somehow since they use the same renderer. What I want now is to get the 2D Hud's AlphaState to work then I'm satisfied until my next problem pops up :slight_smile:

Oki I just noticed this behaviour regarding AlphaStates after dispose()


  1. If any leaf object in the OPAQUE queue has a blend enabled AlphaState all AlphaStates in all queues work (no matter what src function)


  2. If I don't have any AlphaStates in the OPAQUE queue and NO AlphaState in other queues have a src function with AlphaState.SB_ONE. AlphaStates don't work


  3. But if I have a single AlphaState in the TRANSPARENT or ORTHO queue with a src function AlphaState.SB_ONE. Then all AlphaStates start to work again.



    Anyone got a clue to what is going on? :X

No clue.  :(  A test case would help.

Here ya go :slight_smile:



This is a working one except from

                     ((LWJGLRenderer)DisplaySystem.getDisplaySystem().getRenderer()).clearFont();

Which I needed to add to the renderer to make the Text elements work. Not a beautiful hack, but will do until I get some reset method in the generic renderer :wink:


  1. Set as.setEnabled( false ); for the box in the scene node

    This will make no AlphaStates work after the dispose


  2. Uncomment

    //            as.setSrcFunction( AlphaState.SB_ONE );



    for the quad AlphaState in the hud node.

    Now the alphastates work again.



    So eighter there is a need for an alpha state in the OPAQUE queue or an alphastate with SB.ONE in the TRANSPARENT or ORTHO queue.



    They must be reseting something but I don't know what. Maby there are some default states missing in the renderer?



    note: the preDispose() and postDispose() are both executed after the jframe dispose() and setVisual() since the AWT-EventQueue thread is in charge of running the GL thread to so I can't really run preDispose() before in this short test code. But I don't think that should matter.


import java.awt.BorderLayout;
import java.awt.Canvas;
import java.awt.Dimension;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.concurrent.Callable;

import javax.swing.JButton;
import javax.swing.JFrame;

import org.obidobi.demo.jme.gui.DemoAwtApp;

import com.jme.image.Image;
import com.jme.image.Texture;
import com.jme.math.Vector3f;
import com.jme.renderer.Camera;
import com.jme.renderer.ColorRGBA;
import com.jme.renderer.Renderer;
import com.jme.renderer.lwjgl.LWJGLRenderer;
import com.jme.scene.Node;
import com.jme.scene.Spatial;
import com.jme.scene.Text;
import com.jme.scene.shape.Box;
import com.jme.scene.shape.Quad;
import com.jme.scene.state.AlphaState;
import com.jme.scene.state.RenderState;
import com.jme.scene.state.TextureState;
import com.jme.system.DisplaySystem;
import com.jme.util.GameTaskQueueManager;
import com.jme.util.TextureManager;
import com.jmex.awt.JMECanvas;
import com.jmex.awt.SimpleCanvasImpl;

public class DisposeTestCase
{
    private JFrame m_frame;
   
    private int m_width=512;
    private int m_height=512;

    Dimension m_screenSize = Toolkit.getDefaultToolkit().getScreenSize();

    private boolean m_isFullscreen;
    private CanvasImp m_canvas;

    private JButton m_toggleButton;

    public static void main( String[] args )
    {
        DisposeTestCase app = new DisposeTestCase();
        app.start();
    }

    public void start()
    {
        init();

        while( m_canvas.getGlCanvas() == null );

        new Thread()
        {
            { setDaemon( true ); }

            public void run()
            {
                while( true )
                {
                    if( m_frame.isVisible() )
                    {
                        m_canvas.getGlCanvas().repaint();
                        yield();
                    }
                }
            }
        }.start();
    }

    private void init()
    {
        m_frame = new JFrame("DisposeTest");
        m_frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);

        m_toggleButton = new JButton();
        m_toggleButton.addActionListener( new ActionListener()
            {
                public void actionPerformed( ActionEvent ae )
                {
                    toggleFullscreen();
                }
            });
        m_frame.getContentPane().add( m_toggleButton, BorderLayout.SOUTH);

        m_canvas = new CanvasImp( m_width, m_height );
        m_frame.getContentPane().add( m_canvas.getGlCanvas(), BorderLayout.CENTER );

        windowed();

        m_frame.setVisible(true);
    }

    private void toggleFullscreen()
    {
        m_canvas.preDispose();
        m_frame.dispose();
        if( m_isFullscreen )
        {
            windowed();
        }
        else
        {
            fullscreen();
        }
        m_frame.setVisible(true);
        m_canvas.postDispose();
    }
   
    private void fullscreen()
    {
        m_frame.setUndecorated(true);           
        m_frame.setExtendedState( JFrame.MAXIMIZED_BOTH );
        m_toggleButton.setText( "Windowed" );
        m_isFullscreen = true;       
    }
   
    private void windowed()
    {
        m_frame.setUndecorated(false);           
        m_frame.setExtendedState( JFrame.NORMAL );
        m_frame.setBounds( m_screenSize.width/2 - m_width/2,
                           m_screenSize.height/2 - m_height/2,
                           m_width, m_height);
        m_toggleButton.setText( "Fullscreen" );
        m_isFullscreen = false;       
    }   

    class CanvasImp extends SimpleCanvasImpl
    {
        Node m_hud;
        Node m_scene;
        private Canvas glCanvas;

        public CanvasImp( int width, int height )
        {
            super( width, height );
        }

        protected Canvas getGlCanvas()
        {
            if( glCanvas == null )
            {
                glCanvas = DisplaySystem.getDisplaySystem().createCanvas( width, height );
                glCanvas.setMinimumSize( new Dimension( 100, 100 ) );
                ( (JMECanvas)glCanvas ).setImplementor( this );
            }
            return glCanvas;
        }
       
        public void preDispose()
        {
            GameTaskQueueManager.getManager().update(
             new Callable<Object>()
             {
                 public Object call()
                     throws Exception
                 {
                     TextureManager.doTextureCleanup();
                     TextureManager.clearCache();
                     DisplaySystem.getDisplaySystem().getCurrentContext().invalidateStates();
                     ((LWJGLRenderer)DisplaySystem.getDisplaySystem().getRenderer()).clearFont();
                     return null;
                 }
             });
        }

        public void postDispose()
        {
            GameTaskQueueManager.getManager().update(
             new Callable<Object>()
             {
                 public Object call()
                     throws Exception
                 {
                     reloadRenderStates( m_hud );
                     m_hud.updateRenderState();
                     reloadRenderStates( m_scene );
                     m_scene.updateRenderState();
                     resetCamera();
                     return null;
                 }
             });
        }

        public void resetCamera()
        {
            Camera cam;
           
            cam = renderer.getCamera();

            /** Set up how our camera sees. */
            cam.setFrustumPerspective(45.0f,
                                      (float) glCanvas.getWidth()/(float)glCanvas.getHeight(),
                                      1,
                                      1000);
            Vector3f loc = new Vector3f(10.0f, 10.0f, 10.0f);
            Vector3f left = new Vector3f(-1.0f, 0.0f, 0.0f);
            Vector3f up = new Vector3f(0.0f, 1.0f, 0.0f);
            Vector3f dir = new Vector3f(0.0f, 0f, -1.0f);
            cam.setFrame(loc, left, up, dir);
            cam.lookAt( Vector3f.ZERO, Vector3f.UNIT_Y );
            cam.update();
        }

        public void simpleUpdate()
        {
           
        }
       
        public void simpleRender()
        {
            renderer.draw( m_scene );
            renderer.draw( m_hud );
        }
       
        public void simpleSetup()
        {
            Quad quad;
            m_scene = new Node( "Scene" );
           
            m_hud = new Node( "Hud" );
            m_hud.setRenderQueueMode( Renderer.QUEUE_ORTHO );
           
            quad = new Quad( "A", 100, 100 );
            quad.setLocalTranslation( new Vector3f( 100, 100, 0 ) );
            quad.setZOrder( 0 );
            quad.getBatch(0).setDefaultColor( ColorRGBA.blue );
            m_hud.attachChild( quad );

            quad = new Quad( "B", 100, 100 );
            quad.setLocalTranslation( new Vector3f( 150, 150, 0 ) );
            quad.setZOrder( -10 );
            TextureState ts;
            AlphaState as;
            ts = DisplaySystem.getDisplaySystem().getRenderer().createTextureState();
            ts.setTexture( TextureManager.loadTexture( getClass().getClassLoader().getResource(""),
                                                       Texture.MM_NONE,
                                                       Texture.MM_NONE,
                                                       Image.GUESS_FORMAT_NO_S3TC,
                                                       1.0f,
                                                       true ) );
            as = DisplaySystem.getDisplaySystem().getRenderer().createAlphaState();
            as.setBlendEnabled( true );
            as.setSrcFunction( AlphaState.SB_SRC_ALPHA );
//            as.setSrcFunction( AlphaState.SB_ONE );
            as.setDstFunction( AlphaState.DB_ONE_MINUS_SRC_ALPHA );
            quad.setRenderState( as );
            quad.setRenderState( ts );
            m_hud.attachChild( quad );

            Text text = Text.createDefaultTextLabel( "text" );
            text.print( "dispose() test" );
            text.setLocalTranslation( new Vector3f( 180, 50, 0 ) );
            m_hud.attachChild( text );

            Box box = new Box( "Box", Vector3f.ZERO, 4f, 4f, 4f );
            box.getBatch(0).setDefaultColor( ColorRGBA.gray );
            as = DisplaySystem.getDisplaySystem().getRenderer().createAlphaState();
            as.setEnabled( true );
            as.setBlendEnabled( true );
            as.setSrcFunction( AlphaState.SB_SRC_ALPHA );
            as.setDstFunction( AlphaState.DB_ONE_MINUS_SRC_ALPHA );
            box.setRenderState( as );
            m_scene.attachChild( box );
           
            m_hud.updateRenderState();
            m_scene.updateRenderState();
            resetCamera();
        }       
    }
   
    public static void reloadRenderStates( Spatial s )
    {
        final ArrayList<TextureState> list = new ArrayList<TextureState>();
        reloadRenderStates( s, list );
        for( TextureState ts : list )
        {
            ts.load();                   
        }
    }
   
    private static void reloadRenderStates( Spatial s, ArrayList<TextureState> list )
    {
        RenderState state;
        if( s instanceof Node )
        {
            for( Spatial child : ((Node)s).getChildren() )
            {
                reloadRenderStates( child, list );
            }
        }
        state = s.getRenderState( RenderState.RS_ALPHA );
        if( state != null )
        {
//            state.setNeedsRefresh( true );
        }
               
        state = s.getRenderState( RenderState.RS_TEXTURE );
        if( state != null )
        {
            list.add( (TextureState)state );
        }
    }
}