[CLOSED] MacOS: AWT components covered up when using Display.setParent

Started by Jamie-threerings, September 26, 2013, 21:19:38

Previous topic - Next topic

Jamie-threerings

I'm a developer on tripleplay (https://github.com/threerings/tripleplay), which uses LWJGL, though my direct experience with LWJGL is limited.

It's important for my project that uses tripleplay to be able to show native text fields on top of a GL display (tripleplay's UI is built using said elements). There are a few good reasons for wanting native fields, as opposed to home grown, such as input methods, copy & paste and keyboard settings.

The current strategy is to use Display.setParent to embed the GL root within a JFrame and add fields directly to the JFrame's layered pane. This works on Linux, but not on Mac. I don't currently have a Windows test setup.

Here's my sample program:

package test;

import org.lwjgl.LWJGLException;
import org.lwjgl.opengl.Display;
import org.lwjgl.opengl.DisplayMode;
import org.lwjgl.opengl.GL11;

import java.awt.Canvas;
import java.awt.Color;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;

import javax.swing.JFrame;
import javax.swing.JLabel;

public class LwjglOverlayTest extends JFrame
{
    public static void main(String args[])
    {
        System.out.println("Java version: " + System.getProperty("java.version"));
        System.out.println("Java vendor: " + System.getProperty("java.vendor"));
        new LwjglOverlayTest().startGame();
    }

    Canvas canvas = new Canvas();

    LwjglOverlayTest()
    {
        setTitle(getClass().getSimpleName());
        setSize(640, 480);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        canvas.setBounds(0, 0, getSize().width, getSize().height);
        canvas.setFocusable(false);
        getContentPane().add(canvas);
        getContentPane().setBackground(Color.BLACK);

        JLabel label = new JLabel("Hello World");
        label.setForeground(Color.WHITE);
        label.setBounds(20, 20, 100, 20);
        getLayeredPane().add(label);

        addKeyListener(new KeyAdapter() {
            @Override public void keyTyped (KeyEvent e) {
                if (e.getKeyChar() == 'a') {
                    if (gameState == 0) startGame(); else stopGame();
                }
            }
        });
        this.pack();
        setVisible(true);
    }

    Thread gameThread;
    volatile int gameState;

    void stopGame ()
    {
        gameState = 2;
        while (gameState == 2) {
            try {
                Thread.sleep(1);
            } catch (InterruptedException ex) {
            }
        }
    }

    void startGame ()
    {
        gameState = 1;
        gameThread = new Thread() {
            @Override
            public void run() {
                try {
                    Display.setParent(canvas);
                    //Display.create(new PixelFormat().withBitsPerPixel(32).withDepthBits(24).withStencilBits(8));
                    Display.setDisplayMode(new DisplayMode(640, 480));
                    Display.create();

                    // init OpenGL
                    GL11.glMatrixMode(GL11.GL_PROJECTION);
                    GL11.glLoadIdentity();
                    GL11.glOrtho(0, 640, 0, 480, 1, -1);
                    GL11.glMatrixMode(GL11.GL_MODELVIEW);

                    while (gameState == 1) {
                        render();
                    }

                    Display.destroy();
                } catch (LWJGLException e) {
                    e.printStackTrace();
                }

                gameState = 0;
            }
        };
        gameThread.setName("rendering");
        gameThread.start();
    }

    void render ()
    {
        // Clear the screen and depth buffer
        GL11.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT);

        // set the color of the quad (R,G,B,A)
        GL11.glColor3f(0.5f,0.5f,1.f);

        // draw quad
        GL11.glBegin(GL11.GL_QUADS);
        GL11.glVertex2f(100,100);
        GL11.glVertex2f(100+200,100);
        GL11.glVertex2f(100+200,100+200);
        GL11.glVertex2f(100,100+200);
        GL11.glEnd();

        // Display.sync(45);
        Display.update();
    }
}


I've attached screenshots from Linux (Oracle/Java7) and Mac (Apple/Java6). It's using JLabel instead of JTextField but the result is the same. On Mac, I also tried with Java 7 and got the same thing.

Is there some way to achieve this goal or fix the MacOS issue here? I've looked over the Display code briefly; nothing jumped out at me.

Fool Running

This is an issue with any heavy-weight components (which LWJGL's Display is): http://wiki.answers.com/Q/In_Java_what_is_the_difference_between_heavyweight_and_lightweight_components

Heay-weight components will always show over light-weight components (I don't know why it seems to work in Linux for you :P).

One option is to just focus on using AWT. Unfortunately, this limits what you can do GUI-wise. Another option might be: http://www.oracle.com/technetwork/articles/java/mixing-components-433992.html , but that seems to be only in newer versions of Java (which you said you tried so it may not work with Display).
Programmers will, one day, rule the world... and the world won't notice until its too late.Just testing the marquee option ;D

Jamie-threerings

Thanks for the feedback. We could probably figure out how to do this with heavyweight components, but alas, those also do not work.

A colleague of mine has written the below test using only AWT heavyweight components. As with the original JLabel test, this fails in both Oracle 1.7 and Apple 1.6. It uses a text field, which is visible until LWJGL is toggled on, and then it is presumably obscured by the display/canvas. The toggle is controlled by a button, which also disappears, though is still clickable to destroy the display.

package test;

import org.lwjgl.LWJGLException;
import org.lwjgl.opengl.Display;
import org.lwjgl.opengl.DisplayMode;
import org.lwjgl.opengl.GL11;

import java.awt.*;
import java.awt.event.*;

public class LwjglOverlayTest
{
    public static void main(String args[])
    {
        System.out.println("Java version: " + System.getProperty("java.version"));
        System.out.println("Java vendor: " + System.getProperty("java.vendor"));
        new LwjglOverlayTest();//.startGame();
    }

    Frame frame = new Frame(getClass().getSimpleName());
    Canvas canvas = new Canvas();

    public LwjglOverlayTest()
    {
        frame.setLayout(null);

        TextField field = new TextField("Hello World", 40);
        field.setBounds(20, 50, 100, 20);
        frame.add(field);

        Button button = new Button("Toggle LWJGL");
        button.addActionListener(new ActionListener() {
            public void actionPerformed (ActionEvent evt) {
                if (gameState == 0) startGame(); else stopGame();
            }
        });
        button.setBounds(150, 50, 150, 20);
        frame.add(button);

        canvas.setBounds(0, 0, 640, 480);
        canvas.setFocusable(false);
        canvas.setBackground(Color.BLUE);
        frame.setBackground(Color.BLACK);
        frame.add(canvas);

        // this.pack();
        frame.setSize(640, 480);
        frame.setVisible(true);
    }

    Thread gameThread;
    volatile int gameState;

    void stopGame ()
    {
        gameState = 2;
        while (gameState == 2) {
            try {
                Thread.sleep(1);
            } catch (InterruptedException ex) {
            }
        }
    }

    void startGame ()
    {
        gameState = 1;
        gameThread = new Thread() {
            @Override
            public void run() {
                try {
                    Display.setParent(canvas);
                    //Display.create(new PixelFormat().withBitsPerPixel(32).withDepthBits(24).withStencilBits(8));
                    Display.setDisplayMode(new DisplayMode(640, 480));
                    Display.create();

                    // init OpenGL
                    GL11.glMatrixMode(GL11.GL_PROJECTION);
                    GL11.glLoadIdentity();
                    GL11.glOrtho(0, 640, 0, 480, 1, -1);
                    GL11.glMatrixMode(GL11.GL_MODELVIEW);

                    while (gameState == 1) {
                        render();
                    }

                    Display.destroy();
                } catch (LWJGLException e) {
                    e.printStackTrace();
                }

                gameState = 0;
            }
        };
        gameThread.setName("rendering");
        gameThread.start();
    }

    void render ()
    {
        // Clear the screen and depth buffer
        GL11.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT);

        // set the color of the quad (R,G,B,A)
        GL11.glColor3f(0.5f,0.5f,1.f);

        // draw quad
        GL11.glBegin(GL11.GL_QUADS);
        GL11.glVertex2f(100,100);
        GL11.glVertex2f(100+200,100);
        GL11.glVertex2f(100+200,100+200);
        GL11.glVertex2f(100,100+200);
        GL11.glEnd();

        // Display.sync(45);
        Display.update();
    }
}

Fool Running

Your test-case worked fine for me (Windows 7, Java 1.7). Have you tried playing around with the order that the components are added to the frame?
Programmers will, one day, rule the world... and the world won't notice until its too late.Just testing the marquee option ;D

Jamie-threerings

The problem seems to be Mac only. It works on Linux, and now you've confirmed it works on Windows :)

kappa

I've had a look into trying to fix this issue (thanks for the test case), unfortunately it seems the problem is with the JVM and the way JAWT CALayer API is implemented and therefore I don't think it can be fixed from our end.

It does boil down to the LWJGL Canvas/CALayer being a heavyweight component on top of the lightweight AWT/Swing components. Unlike OS X the Linux and Windows AWT components are currently heavyweight hence why it works there.

As far as I can tell on OS X all AWT/Swing content is rendered offscreen and then into a single CALayer. The JAWT CALayer API simply sticks the user (LWJGL) CALayer on top of this CALayer, so overlapping is not technically possible with the current JVM implementation.

If Oracle do decide to address this issue they'd probably need to either split each AWT/Swing component into individual CALayers or go for a solution such as having two AWT/Swing CALayers for each user added CALayer (through the JAWT CALayer API), one above it and one below and move the AWT/Swing components between them accordingly.

Unfortunately with Oracle's current development focus moving to JavaFX IMO its unlikely AWT/Swing on OS X will get a rewrite to its renderer to fix this issue.

In the long run, you should probably consider going for an OpenGL GUI, since AWT/Swing on OS X is using fake native components anyway. Using one of the existing LWJGL OpenGL GUI libraries might be a good option if you don't want to write your own, especially the TWL library which is similar to AWT/Swing and pretty features complete (enough for someone to port Eclipse to it).