ZOrder flip?

I have a simple app with a glCanvas inside a JFrame.



The only thing I have in my scene is two quads in a node with mode QUEUE_ORTHO.

These two quads have different ZOrder values.



Initialy they are drawn with lowest ZOrder infront. e.g. the one with -1 is drawn infront of 0.



My application has a funtion to swap between windowed and fullscreen mode like this:


private void toggleFullscreen()
    {
        m_frame.dispose();
        if( m_isFullscreen )
        {
            windowed();
        }
        else
        {
            fullscreen();
        }
        m_frame.setVisible(true);
    }
   
    private void fullscreen()
    {
        m_frame.setUndecorated(true);           
        m_frame.setMaximizedBounds( m_screenMaxBounds );
        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_screenMaxBounds.width/2 - m_width/2,
                           m_screenMaxBounds.height/2 - m_height/2,
                           m_width, m_height);
        m_toggleButton.setText( "Fullscreen" );
        m_isFullscreen = false;       
    }



The wierd thing is that after having done one toggle the ORTHO que is drawn with highest ZOrder infront.

Javadoc for dispose():
Releases all of the native screen resources used by this Window, its subcomponents, and all of its owned children. That is, the resources for these Components will be destroyed, any memory they consume will be returned to the OS, and they will be marked as undisplayable.


One thing I need to do after a dispose is reset the camera other than that everything seems to work. My question is how the ZOrder can flip after a dispose()? I have tried to look thru the source if there is anything that can affect this but without luck.


This is driving me crazy. Here is a simple runnable program so you can see what I mean.



We have a red square B(ZOrder=-10) getting drawn in front of a blue square A(ZOrder=0). One screen toggle and the drawing order is inverted.



From what I can see during a debug there is no difference in what is done. The orthoBucket is sorted the same the drawing order is done in the same order both before and after a dispose(). But visualy on the screen is clear they aren't ordered in the same way. Don't know how lwjgl works tho if there is some lowlevel GL that can affect the order that is not related to the order in which you call lwjgl's draw methods.


import java.awt.BorderLayout;
import java.awt.Canvas;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.util.concurrent.Callable;

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

import com.jme.math.Vector3f;
import com.jme.renderer.ColorRGBA;
import com.jme.renderer.Renderer;
import com.jme.scene.Node;
import com.jme.scene.shape.Quad;
import com.jme.system.DisplaySystem;
import com.jme.util.GameTaskQueue;
import com.jme.util.GameTaskQueueManager;
import com.jmex.awt.JMECanvas;
import com.jmex.awt.SimpleCanvasImpl;

public class ZOrderTest
{
    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 )
    {
        ZOrderTest app = new ZOrderTest();
        app.start();
    }

    public void start()
    {
        init();

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

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

            public void run()
            {
                try
                {
                    while( true )
                    {
                        if( m_frame.isVisible() )
                        {
                            m_canvas.getGlCanvas().repaint();
                        }
                        Thread.sleep( 1 );
                    }
                } catch( InterruptedException e ){}
            }
        }.start();
    }

    private void init()
    {
        m_frame = new JFrame("ZOrderTest");
        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( 512, 512 );
        m_frame.getContentPane().add( m_canvas.getGlCanvas(), BorderLayout.CENTER );

        windowed();
        m_frame.setVisible(true);
    }

    private void toggleFullscreen()
    {
        m_frame.dispose();
        if( m_isFullscreen )
        {
            windowed();
        }
        else
        {
            fullscreen();
        }
        m_frame.setVisible(true);
    }
   
    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
    {
        private Canvas glCanvas;

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

        protected Canvas getGlCanvas()
        {
            if( glCanvas == null )
            {
                doInit();
            }
            return glCanvas;
        }

        public void doInit()
        {
            glCanvas = DisplaySystem.getDisplaySystem().createCanvas( width, height );
            glCanvas.setMinimumSize( new Dimension( 100, 100 ) );
            ( (JMECanvas)glCanvas ).setImplementor( this );
        }

        public void simpleSetup()
        {
            Quad quad;
            Node node = new Node( "Ortho" );
            node.setRenderQueueMode( Renderer.QUEUE_ORTHO );
           
            quad = new Quad( "A", 200, 200 );
            quad.setLocalTranslation( new Vector3f( 100, 100, 0 ) );
            quad.setZOrder( 0 );
            quad.getBatch(0).setDefaultColor( ColorRGBA.blue );
            node.attachChild( quad );

            quad = new Quad( "B", 200, 200 );
            quad.setLocalTranslation( new Vector3f( 200, 200, 0 ) );
            quad.setZOrder( -10 );
            quad.getBatch(0).setDefaultColor( ColorRGBA.red );
            node.attachChild( quad );
           
            rootNode.attachChild( node );
        }       
    }
}

edit: removed my previous comment since its not very useful :slight_smile:



Its really acting funny, i'm playing with it now and get the same results.

if you remove the existing ZBufferState from the rootNode at the end of simpleSetup() then it seems to work.


            rootNode.attachChild( node );
            rootNode.clearRenderState(RenderState.RS_ZBUFFER);



i don't know the insides of jME good enough to explain why its not working with a enabled ZBufferState tho :).
Core-Dump said:

if you remove the existing ZBufferState from the rootNode at the end of simpleSetup() then it seems to work.


            rootNode.attachChild( node );
            rootNode.clearRenderState(RenderState.RS_ZBUFFER);



i don't know the insides of jME good enough to explain why its not working with a enabled ZBufferState tho :).


Oh interesting so somehow a dispose() will effect how the ZBuffer work. i'll investigate more :)

I think adding ortho nodes to a non-ortho node is generally a bad idea.

As i understand it, the ortho rendermode should be set at the root Level.

Displaying a frame that was disposed is a bug. If you call dispose() method on your frame, you have to reinitialize the frame from scratch to make sure all of the frame's subcomponents are in working condition.



What happens is your glCanvas is being disposed because it's a subcomponent in the frame. However the glCanvas is never reinitialized. So what you are seeing is a disposed version of glCanvas ready to be destroyed. The fact that anything at all is being displayed is a mere coincidence.

Core-Dump said:

I think adding ortho nodes to a non-ortho node is generally a bad idea.
As i understand it, the ortho rendermode should be set at the root Level.


Yeah your right. But it doesn't seem to matter. I have the scene nodes and my ORTHO node separated and do separate render.draw() on them. I still get the ZOrder flip when I have a ZBuffer in my scene. :x
lex said:

Displaying a frame that was disposed is a bug. If you call dispose() method on your frame, you have to reinitialize the frame from scratch to make sure all of the frame's subcomponents are in working condition.


The javadoc for dispose() says:
Releases all of the native screen resources used by this Window, its subcomponents, and all of its owned children. That is, the resources for these Components will be destroyed, any memory they consume will be returned to the OS, and they will be marked as undisplayable.

The Window and its subcomponents can be made displayable again by rebuilding the native resources with a subsequent call to pack or show. The states of the recreated Window and its subcomponents will be identical to the states of these objects at the point where the Window was disposed (not accounting for additional modifications between those actions).


so doing a
dispose()
.
.
setVisual(true);
is what is the online examples say you should do when switching between windowed and fullscreen mode.
What I do in my application after this is reinit the renderer and camera. Everything works just fine except that the ZOrder flip. That seem to be caused by the ZBufferState in my scene having been reset by the dispose().

I guess the only reliable solution to doing a dispose() is to save the scene and reloading it since the ZBufferState for example seems to be replace by a completely new and reset object and maby that will happen to other other states aswell?

With my current test. reinit of the Renderer, the Camera and the ZBufferState is enough. But since I don't really understand exactly what happens when dispose is called.. it don't seem safe :X

You are right… I did some testing, and dispose() is not the what's cause the problem. Try calling toggleFullscreen() a couple of time in init() method.

The ZBufferObject is the same one as well…



The problem seems to occur only when calling dispose() from awt event dispatch thread. Synchronizing repaint on an external variable doesn't work either. Very very strange.



This bug has me puzzled :confused:

Removing glCanvas prior to dispose() doesnt seem to be doing anything either.

Have you turned on Z testing?



ZBufferState zbs = Display.getDisplay().getRenderer().createZBufferState();

zbs.setEnabled(true);

myNode.setRenderState(zbs);



This got me when I started out with jMonkey – you can probably find the thread if you search for it.

Yeah, ZTesting is on, I even made a printout of ZState every update… the ZBufferObject is always the same and the testing is enabled.

Is Z writing enabled?

Does your pixel format contain depth?

lex said:

Try calling toggleFullscreen() a couple of time in init() method.


actually. When you call setVisual(true) for the first time in the init, it will take a little bit before the first paint is issued by the system. This paint will make the glCanvas call setup on its implementation. Setting up the camera, ZBuffer and stuff.

So your toggleFullscreen() in the init() was probably done before the camera and ZBuffer was created. If you do a Thread.sleep(1000); right after setVisual(true) in the init and then do toggleFullscreen(). The effect will be the same as when running from the awt event thread. So the dispose() is the culprit. Would just be interesting to figure out exactly what effect it has, if it is enough just to reinit the renderer, camera and ZBuffer.
lex said:

Yeah, ZTesting is on, I even made a printout of ZState every update... the ZBufferObject is always the same and the testing is enabled.


It seems to be as easy as just having to do an updateRenderStates() on my separate scene nodes after a dispose() atleast for the ZOrder to work :)