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;
}
}