Using Overlay to draw a Swing GUI over a GLCanvas

Greetings everybody

We are developing a 3D game with JOGL with its user interface made completely in Swing [1]. The widgets are also made translucent by the look-and-feel. The 3D scene is placed in a GLJPanel and the GUI widgets are placed in a JLayeredPane on top of the scene, like a HUD. Unfortunately the game is running very very slow - just about 10 fps [2].

Now we want to use the GLCanvas for the 3D scene and because GLCanvas is heavyweight, we want use the Overlay class for drawing the Swing components on it. Is there a best-practice on how the overlay class is used? Currently it works, but it doesn’t display translucent widgets correctly - the content of some widgets is drawn multiple times in the other widgets. I suppose the problem lies in the custom RepaintManager that we use. I would be very thankful for any tips on using the Overlay class in general, or in conjunction with translucent components.

Below I have included some parts of the code. The important classes are SceneCanvas (a GLCanvas and GLEventListener), a HUDPane (the JLayeredPane with the Swing Components), and a HUDRepaintManager. We have overriden the paint(Graphics g) method of the HUDPane and call it with a Graphics object created from the Overlay. In the HUDRepaintManager we are tracking the dirty region that must be re-drawn (with markDirty()) on the Overlay.

[1]: I have read that it is better to use pure OpenGL like FengGUI does, but the GUI is already implemented and it would be very hard to port it. Furthermore we use a very customized look and feel and I don’t know if FengGUI supports that.
[2]: We tried with the OpenGL pipeline, but it worked correctly only on a few machines, on the rest the game and some demo apps with the pipeline enabled were not displaying correctly.


class SceneCanvas extends GLCanvas implements GLEventListener 
{
    // initializes the GUI
    // creates the Overlay
    // starts an Animator

    public void init(GLAutoDrawable gld)
    {
         // initialize the GL
         ...
    }

    public void display(GLAutoDrawable gld) 
    {
        ...

        Graphics2D g2d = (Graphics2D) overlay.createGraphics();
 
        if (overlay.contentsLost()) 
        {
            // clear the background
            g2d.setBackground(TRANSPARENT_BLACK);
            g2d.clearRect(0, 0, getWidth(), getHeight());

            // get the dirty region from the custom RepaintManager
            Rectangle dirty = hudRepaintManager.getAndResetDirtyRegion();
            g2d.setClip(dirty);
            
            // paint the GUI components
            hudPane.paint(g2d);
            
            overlay.markDirty(dirty.x, dirty.y, dirty.width, dirty.height);
        }

        g2d.dispose();

        // reset texture units for before drawing the overlay
        GLTextureManager.reset(gl);
        gl.glColor4f(1, 1, 1, 1);

        // show the overlay
        overlay.drawAll();
    }
}

class HUDPane extends JLayeredPane 
{
    public void paint(Graphics g)
    {
        // we paint only if we are called from the GL-context
        if (GLContext.getCurrent() != null)
        {
            // the Graphics object comes from the Overlay
            // draw everything into overlay
            super.paint(g); // paints the child-components too
        }
    }
}

class HUDRepaintManager extends RepaintManager
{
    private boolean hasDirtyRegion = false;
    private int dirtyMinX = Integer.MAX_VALUE;
    private int dirtyMaxX = Integer.MIN_VALUE;
    private int dirtyMinY = Integer.MAX_VALUE;
    private int dirtyMaxY = Integer.MIN_VALUE;

    private HUDPane hudPane;

    public HUDRepaintManager(HUDPane hudPane)
    {
        super();
        this.hudPane = hudPane;
    }

    public Rectangle getAndResetDirtyRegion()
    {
        Rectangle dirty = hasDirtyRegion ? getDirtyRectangle() : null;

        dirtyMinX = Integer.MAX_VALUE;
        dirtyMaxX = Integer.MIN_VALUE;
        dirtyMinY = Integer.MAX_VALUE;
        dirtyMaxY = Integer.MIN_VALUE;
        hasDirtyRegion = false;

        return dirty;
    }

    public boolean hasDirtyRegion() { return hasDirtyRegion; }

    @Override
    public void addDirtyRegion(JComponent c, int x, int y, int w, int h)
    {
        super.addDirtyRegion(c, x, y, w, h);

        // check if component is part of hudPane component tree
        if (!isPartOfComponentTree(c, hudPane))
            return;

        // translate to container
        Rectangle rect = SwingUtilities.convertRectangle(
                c, new Rectangle(x, y, w, h), hudPane);
        x = rect.x;
        y = rect.y;
        w = rect.width;
        h = rect.height;

        // set min, max
        if (x < dirtyMinX)
        {
            dirtyMinX = x;
            hasDirtyRegion = true;
        }
        if (y < dirtyMinY)
        {
            dirtyMinY = y;
            hasDirtyRegion = true;
        }
        int xW = x + w;
        if (xW > dirtyMaxX)
        {
            dirtyMaxX = xW;
            hasDirtyRegion = true;
        }
        int yH = y + h;
        if (yH > dirtyMaxY)
        {
            dirtyMaxY = yH;
            hasDirtyRegion = true;
        }
    }

    private Rectangle getDirtyRectangle()
    {
        return new Rectangle(dirtyMinX, dirtyMinY,
                (dirtyMaxX - dirtyMinX),
                (dirtyMaxY - dirtyMinY));
    }

    private boolean isPartOfComponentTree(Component c, Component pane)
    {
        if (c == pane) return true;

        if (pane instanceof Container)
        {
            if (((Container) pane).getComponentCount() == 0) return false;

            for (Component cc : ((Container) pane).getComponents())
            {
                if (cc == c) return true;

                boolean b = isPartOfComponentTree(c, cc);

                if (b) return true;
            }
        }
        return false;
    }
}

We haven’t yet tried advanced usage like this with the Overlay and it’s possible there might be problems. First I’d suggest you make your RepaintManager as simple as possible, or try to avoid its usage altogether. The XTrans demo in the jogl-demos workspace does some off-screen rendering of Swing components and you might be able to pick up some techniques from it. In particular it avoids the use of a custom RepaintManager. It has a customized JDesktopPane subclass and, in it, explicitly sets isOptimizedDrawingEnabled(false) (this helps make the JScrollPane work properly), as well as turns off double-buffering for all Components added to it. The latter might be part of your problem.

Also, when clearing portions of the Graphics, this may be better usage:


          Composite composite = g.getComposite();
          g.setComposite(AlphaComposite.Clear);
          g.fillRect(rect.x(), rect.y(), rect.w(), rect.h());
          g.setComposite(composite);