Can't initialize JMECanvasImplementor with hidden canvas (Mac OS X)

I am trying to write a SWING application where the JME canvas is HIDDEN at the beginning, on Mac OS X

I can't find a way to ensure that the canvas is initialized correctly (e.g. the renderer is created) unless the Canvas is visible on screen.



Did anyone experienced the same issue, or knows a solution/workaround?



regards,

andrea


I don't know if this would help, but you could always create your render canvas and put it in a frame that is not visible. Another option would be to create your game with a Dummy siplay, but I don't know if you could later on change it.



Finally, what you could do, is not initialize jME at all when your application starts, and only later bring it up, when is appropriate.

Hi duenez, thank you for your answer; what you say is right but it's not what I mean (code follows): while I can create the canvas correctly and added to the Swing window correctly, I can't have the implementor setup when I want. There is no way to have the doSetup implementor function executed, not even if I force it. I need the renderer created when the window is hidden to load models and setup the scene even from command line.

Sample code follows: it's the very same JMESwingTest test with a tab panel, with the jme canvas being in the second page (and thus, hidden at the beginning). When you execute this, you get this log:



Jan 25, 2008 7:59:47 AM com.jme.input.joystick.DummyJoystickInput <init>

INFO: Joystick support is disabled

Jan 25, 2008 7:59:47 AM com.jme.system.lwjgl.LWJGLDisplaySystem <init>

INFO: LWJGL Display System created.



AFTER you switch to the second tab, you get:



Jan 25, 2008 8:00:21 AM com.jme.renderer.lwjgl.LWJGLRenderer <init>

INFO: LWJGLRenderer created. W:  640H: 480

Jan 25, 2008 8:00:21 AM com.jme.renderer.AbstractCamera <init>

INFO: Camera created.

Jan 25, 2008 8:00:21 AM com.jme.util.lwjgl.LWJGLTimer <init>

INFO: Timer resolution: 1000 ticks per second

Jan 25, 2008 8:00:21 AM com.jme.scene.Node <init>

INFO: Node created.

Jan 25, 2008 8:00:21 AM com.jme.scene.Node attachChild

INFO: Child (Box) attached to this node (rootNode)



What I need is to have the creation of the renderer ("INFO: LWJGLRenderer created. W:  640H: 480") BEFORE that the canvas becomes visible.



For clarity, from the JMESwingTest I just changed the initialization line:


            contentPane.add(comp, BorderLayout.CENTER);


into:


            // contentPane.add(comp, BorderLayout.CENTER);
            JTabbedPane tabbedPane = new JTabbedPane();
            tabbedPane.addTab( "Page 1", new JPanel() );
            tabbedPane.addTab( "Page 2", comp );
            tabbedPane.addTab( "Page 3", new JPanel() );
            contentPane.add(tabbedPane, BorderLayout.CENTER);



Here is the sample code to reproduce the issue.


package com.agentili;

import java.awt.BorderLayout;
import java.awt.Canvas;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.MouseEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.util.concurrent.Callable;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.swing.*;

import com.jme.bounding.BoundingBox;
import com.jme.image.Texture;
import com.jme.input.InputHandler;
import com.jme.input.KeyInput;
import com.jme.input.action.InputAction;
import com.jme.input.action.InputActionEvent;
import com.jme.math.FastMath;
import com.jme.math.Quaternion;
import com.jme.math.Vector3f;
import com.jme.renderer.Renderer;
import com.jme.scene.shape.Box;
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.JMECanvasImplementor;
import com.jmex.awt.SimpleCanvasImpl;
import com.jmex.awt.input.AWTMouseInput;
import jmetest.util.JMESwingTest;

/**
 * <code>JMESwingTest</code> is a test demoing the JMEComponent and
 * HeadlessDelegate integration classes allowing jME generated graphics to be
 * displayed in a AWT/Swing interface.
 *
 * Note the Repaint thread and how you grab a canvas and add an implementor to it.
 *
 * @author Joshua Slack
 * @version $Id: JMESwingTest.java,v 1.18 2007/08/17 10:34:35 rherlitz Exp $
 */

public class JMESwingTestMac {
    private static final Logger logger = Logger.getLogger(JMESwingTest.class
            .getName());

    int width = 640, height = 480;

    // Swing frame
    private SwingFrame frame;

    public JMESwingTestMac() {
        frame = new SwingFrame();
        // center the frame
        frame.setLocationRelativeTo(null);
        // show frame
        frame.setVisible(true);
    }

    /**
     * Main Entry point...
     *
     * @param args
     *            String[]
     */
    public static void main(String[] args) {

        try {
            UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
        } catch (Exception e) {
            logger.logp(Level.SEVERE, JMESwingTest.class.toString(), "main(args)", "Exception", e);
        }
        new JMESwingTestMac();
    }

    // **************** SWING FRAME ****************

    // Our custom Swing frame... Nothing really special here.
    class SwingFrame extends JFrame {
        private static final long serialVersionUID = 1L;

        JPanel contentPane;
        JPanel mainPanel = new JPanel();
        Canvas comp = null;
        JButton coolButton = new JButton();
        JButton uncoolButton = new JButton();
        JPanel spPanel = new JPanel();
        JScrollPane scrollPane = new JScrollPane();
        JTree jTree1 = new JTree();
        JCheckBox scaleBox = new JCheckBox("Scale GL Image");
        JPanel colorPanel = new JPanel();
        JLabel colorLabel = new JLabel("BG Color:");
        JMECanvasImplementor impl;

        // Construct the frame
        public SwingFrame() {
            addWindowListener(new WindowAdapter() {
                public void windowClosing(WindowEvent e) {
                    dispose();
                }
            });

            init();
            pack();


            // MAKE SURE YOU REPAINT SOMEHOW OR YOU WON'T SEE THE UPDATES...
            new Thread() {
                { setDaemon(true); }
                public void run() {
                    while (true) {
                        comp.repaint();
                        yield();
                    }
                }
            }.start();


        }

        // Component initialization
        private void init() {
            contentPane = (JPanel) this.getContentPane();
            contentPane.setLayout(new BorderLayout());

            mainPanel.setLayout(new GridBagLayout());

            setTitle("JME - SWING INTEGRATION TEST");

            //


GL STUFF

            // make the canvas:
            comp = DisplaySystem.getDisplaySystem("lwjgl").createCanvas(width, height);

            // add a listener... if window is resized, we can do something about it.
            comp.addComponentListener(new ComponentAdapter() {
                public void componentResized(ComponentEvent ce) {
                    doResize();
                }
            });
            KeyInput.setProvider( KeyInput.INPUT_AWT );
            AWTMouseInput.setup( comp, false );

                    // Important!  Here is where we add the guts to the panel:
            impl = new MyImplementor(width, height);
            JMECanvas jmeCanvas = ( (JMECanvas) comp );
            jmeCanvas.setImplementor(impl);
            jmeCanvas.setUpdateInput( true );

            //
END OF GL STUFF

            coolButton.setText("Cool Button");
            uncoolButton.setText("Uncool Button");

            colorPanel.setBackground(java.awt.Color.black);
            colorPanel.setToolTipText("Click here to change Panel BG color.");
            colorPanel.setBorder(BorderFactory.createRaisedBevelBorder());
            colorPanel.addMouseListener(new java.awt.event.MouseAdapter() {
                public void mouseClicked(MouseEvent e) {
                    final java.awt.Color color = JColorChooser.showDialog(
                            SwingFrame.this, "Choose new background color:",
                            colorPanel.getBackground());
                    if (color == null)
                        return;
                    colorPanel.setBackground(color);
                    Callable<?> call = new Callable<Object>() {
                        public Object call() throws Exception {
                            comp.setBackground(color);
                            return null;
                        }
                    };
                    GameTaskQueueManager.getManager().render(call);
                }
            });

            scaleBox.setOpaque(false);
            scaleBox.setSelected(true);
            scaleBox.addActionListener(new ActionListener() {
                public void actionPerformed(ActionEvent e) {
                    if (comp != null)
                        doResize();
                }
            });

            spPanel.setLayout(new BorderLayout());
            contentPane.add(mainPanel, BorderLayout.WEST);
            mainPanel.add(scaleBox,
                    new GridBagConstraints(0, 0, 1, 1, 0.0, 0.0,
                            GridBagConstraints.CENTER,
                            GridBagConstraints.HORIZONTAL, new Insets(5, 5, 0,
                                    5), 0, 0));
            mainPanel.add(colorLabel,
                    new GridBagConstraints(0, 1, 1, 1, 0.0, 0.0,
                            GridBagConstraints.CENTER,
                            GridBagConstraints.HORIZONTAL, new Insets(5, 5, 0,
                                    5), 0, 0));
            mainPanel.add(colorPanel, new GridBagConstraints(0, 2, 1, 1, 0.0,
                    0.0, GridBagConstraints.CENTER, GridBagConstraints.NONE,
                    new Insets(5, 5, 0, 5), 25, 25));
            mainPanel.add(coolButton,
                    new GridBagConstraints(0, 3, 1, 1, 0.0, 0.0,
                            GridBagConstraints.CENTER,
                            GridBagConstraints.HORIZONTAL, new Insets(5, 5, 0,
                                    5), 0, 0));
            mainPanel.add(uncoolButton,
                    new GridBagConstraints(0, 4, 1, 1, 0.0, 0.0,
                            GridBagConstraints.CENTER,
                            GridBagConstraints.HORIZONTAL, new Insets(5, 5, 0,
                                    5), 0, 0));
            mainPanel.add(spPanel, new GridBagConstraints(0, 5, 1, 1, 1.0, 1.0,
                    GridBagConstraints.CENTER, GridBagConstraints.BOTH,
                    new Insets(5, 5, 0, 5), 0, 0));
            spPanel.add(scrollPane, BorderLayout.CENTER);

            scrollPane.setViewportView(jTree1);
            comp.setBounds(0, 0, width, height);
            //contentPane.add(comp, BorderLayout.CENTER);

            JTabbedPane tabbedPane = new JTabbedPane();
            tabbedPane.addTab( "Page 1", new JPanel() );
            tabbedPane.addTab( "Page 2", comp );
            tabbedPane.addTab( "Page 3", new JPanel() );
            contentPane.add(tabbedPane, BorderLayout.CENTER);
        }

        protected void doResize() {
            if (scaleBox != null && scaleBox.isSelected()) {
                impl.resizeCanvas(comp.getWidth(), comp.getHeight());
            } else {
                impl.resizeCanvas(width, height);
            }
        }

        // Overridden so we can exit when window is closed
        protected void processWindowEvent(WindowEvent e) {
            super.processWindowEvent(e);
            if (e.getID() == WindowEvent.WINDOW_CLOSING) {
                System.exit(0);
            }
        }
    }


    // IMPLEMENTING THE SCENE:

    class MyImplementor extends SimpleCanvasImpl {

        private Quaternion rotQuat;
        private float angle = 0;
        private Vector3f axis;
        private Box box;
      long startTime = 0;
      long fps = 0;
        private InputHandler input;

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

        public void simpleSetup() {

            // Normal Scene setup stuff...
            rotQuat = new Quaternion();
            axis = new Vector3f(1, 1, 0.5f);
            axis.normalizeLocal();

            Vector3f max = new Vector3f(5, 5, 5);
            Vector3f min = new Vector3f(-5, -5, -5);

            box = new Box("Box", min, max);
            box.setModelBound(new BoundingBox());
            box.updateModelBound();
            box.setLocalTranslation(new Vector3f(0, 0, -10));
            box.setRenderQueueMode(Renderer.QUEUE_SKIP);
            rootNode.attachChild(box);

            box.setRandomColors();

            TextureState ts = renderer.createTextureState();
            ts.setEnabled(true);
            ts.setTexture(TextureManager.loadTexture(JMESwingTest.class
                    .getClassLoader().getResource(
                            "jmetest/data/images/Monkey.jpg"),
                    Texture.MM_LINEAR, Texture.FM_LINEAR));

            rootNode.setRenderState(ts);
            startTime = System.currentTimeMillis() + 5000;

            input = new InputHandler();
            input.addAction( new InputAction() {
                public void performAction( InputActionEvent evt ) {
                    logger.info( evt.getTriggerName() );
                }
            }, InputHandler.DEVICE_MOUSE, InputHandler.BUTTON_ALL, InputHandler.AXIS_NONE, false );
        }

        public void simpleUpdate() {
            input.update( tpf );

            // Code for rotating the box... no surprises here.
            if (tpf < 1) {
                angle = angle + (tpf * 25);
                if (angle > 360) {
                    angle = 0;
                }
            }
            rotQuat.fromAngleNormalAxis(angle * FastMath.DEG_TO_RAD, axis);
            box.setLocalRotation(rotQuat);

         if (startTime > System.currentTimeMillis()) {
            fps++;
         } else {
            long timeUsed = 5000 + (startTime - System.currentTimeMillis());
            startTime = System.currentTimeMillis() + 5000;
            logger.info(fps + " frames in " + (timeUsed / 1000f) + " seconds = "
                  + (fps / (timeUsed / 1000f))+" FPS (average)");
            fps = 0;
         }
        }
    }
}

Found this, I'm probably not the first one to have this problem:

http://www.jmonkeyengine.com/jmeforum/index.php?topic=3823.0

From LWJGLDisplaySystem you can see that the renderer, context, etc are created after a call to createWindow or createHeadlessWindow is performed. Perhaps you could trick the system into calling one of those methods, and later switch to the one you actually need.

Has anyone found a way to initialize the LWJGLCanvas when it's hidden?

Hm, I had a similar problem a while ago (under Windows). I wanted to start the renderer before displaying the canvas, so I'd be able to create RenderStates, even with the canvas not yet visible.



I tried various solutions. The first was to make the canvas visible, but outside the screen (e.g. by setting the location of the window containing the canvas to (-1000, -1000)), but this way the renderer didn't get initialized. It was only initialized when the window containing the canvas was moved into the visible screen area.

The second solution I tried was to display the canvas for a short moment and then hide it again as soon as the first setup-update cycle had finished, but this didn't work either, whyever that was.



Finally, I stored all RenderStates that had to be set up before rendering startup and initialized them in the setup method of the Implementor. This worked, but it wasn't quite the simple solution I had hoped for…