Agile2D Porting Guide

This document describes steps and hints for porting applications to the Agile2D OpenGL Renderer.

Converting a Swing Application to Agile2D

Agile2D works by replacing a top-level Swing JFrame with an AgileJFrame. If your application contains the code:

    
    JFrame frame = new JFrame();
    frame.getContentsPane().add(...);

you should simply be able to write:

    JFrame frame = new AgileJFrame();
    frame.getContentsPane().add(...);

and the contents of the window will be rendered by Agile2D.

Some applications subclass JFrame, e.g.

    class MyApp extends JFrame { ...

To convert these apps to use Agile2D, use:

    class MyApp extends AgileJFrame { ...

Disabling double buffering

Most Swing applications rely on the Swing double buffering mechanism to perform flicker-free screen updates. The good news is that the Agile2D OpenGL implementation automatically replaces the Swing double buffering mechanism with OpenGL double buffering in AgileJFrames. This means that many Swing applications will convert to Agile2D without problems.

Some applications have their own double buffering code. The code might look something like:

    public void paintComponent(Graphics g) {
        if (buf == null) buf = new BufferedImage(...);
        
        Graphics g2 = buf.getGraphics();
        renderTo(g2); // Draw into buf
        g2.dispose();
        
        g.drawImage(buf, 0, 0, null);
    }    

For these applications to be performant in the Agile2D OpenGL renderer, this form of double-buffering code should be disabled. One way to do this is to write a conditional like:

    public void paintComponent(Graphics g) {
        if (Agile2D.isOpenGLGraphics(g)) {
            renderTo(g);
        } else {
            ... do double buffering code here
        }
    }

Animated images

In some cases, your application may require a dynamic or animated image, whose contents change over time.

By default, the Agile2D OpenGL Renderer assumes that AWT images are static - that their contents never change. This is because Agile2D caches AWT images using OpenGL textures, and updating the texture contents is slow.

Unfortunately, Java provides no public mechanism for detecting when an image's contents change, so there is no automatic way for Agile2D to figure out when its version of a Java image is out of date. This means that Agile2D may render some images incorrectly.

To address this, you can use the rendering hint Agile2D.KEY_IMMUTABLE_IMAGE_HINT to tell Agile2D whether it is safe to cache an image's contents or not. The default value is Boolean.TRUE (i.e. images are treated as immutable). Set this to Boolean.FALSE when you are rendering a dynamic image, and the image's contents will be updated every render. e.g.

void drawSomeImages(Graphics2D g) {
    // By default Agile2D caches information about images 
    // so they render quickly
    g.drawImage(image1, 10, 10, null);

    // You can disable caching of image data
    g.setRenderingHint(Agile2D.KEY_IMMUTABLE_IMAGE_HINT, 
                       Boolean.FALSE);

    g.drawImage(animatedImage, 100, 100, null);

    // Turn caching back on afterwards!
    g.setRenderingHint(Agile2D.KEY_IMMUTABLE_IMAGE_HINT, 
                       Boolean.TRUE);
    
}

Agile2D.KEY_IMMUTABLE_IMAGE_HINT

Set this to true if you know that an image you are drawing never changes its contents from frame to frame. Set it to false if you know that an image's contents may change. The defaults is true - Agile2D assumes images never change their contents.

The Agile2D OpenGL renderer draws images by copying the image data to OpenGL textures. If this hint is Boolean.TRUE, Agile2D caches these textures and reuses them from frame to frame. If this is Boolean.FALSE, Agile2D recreates the texture data each time an image is rendered.

Note that if your image changes infrequently, you can still cache it and set the value of the rendering hint to false prior to drawing it when it has changed. Agile2D will reuse the cached memory and change its contents then. The cached memory is definitively freed only when the image is garbage collected.


Shapes

OpenGL only provides three shape primitives: triangles, quadrilateras, and convex polygons. There is a line primitive, but it is only useful for thin lines. There are no line cap or join styles.

To fill a shape in OpenGL, Agile2D must tesselate the shape, converting the shape into a series of triangles. Similarly, to draw a stroked line, Agile2D obtains the outline of the stroke, tesselates that outline, and then fills the tesselation.

Tesselation is slow. On the other hand, some shapes do not change their geometry after they have been created, so some tesselations can be cached.

Unfortunately, Java provides no mechanism for creating immutable shapes, which could always be cached. By default, Agile2D assumes that all shapes are mutable, and may change. It does not cache tesselations of shapes by default.

The immutable shape hint can be used to turn on caching for a shape.

class Example {
    Shape shape;
    Example() { ... add stuff to shape }

    void paintComponent(Graphics2D g) {

        // Enable caching of shape tesselations
        g.setRenderingHint(Agile2D.KEY_IMMUTABLE_SHAPE_HINT, 
                           Boolean.TRUE);

        g.fill(shape);

        // Restore rendering hint afterwards!
        g.setRenderingHint(Agile2D.KEY_IMMUTABLE_SHAPE_HINT, 
                           Boolean.FALSE);
    }
    
}

If you really know that a shape you are drawing is a simple convex polygon, such as a pentagon, you can tell Agile2D to avoid tesselation and render the shape as an OpenGL GL_POLYGON. This may not look pretty if the polygon isn't convex, but it is much faster if it is:

class Example {
    static int xPts[] = { ... };
    static int yPts[] = { ... };

    Shape poly = new Polygon(xPts, yPts, xPts.length);

    Example() { }

    void paintComponent(Graphics2D g) {

        // Enable treating shapes and strokes as convex
        g.setRenderingHint(Agile2D.KEY_CONVEX_SHAPE_HINT, 
                           Boolean.TRUE);

        g.fill(shape);

        // Restore rendering hint afterwards!
        g.setRenderingHint(Agile2D.KEY_CONVEX_SHAPE_HINT,
                           Boolean.FALSE);
    }
    
}

Agile2D.KEY_IMMUTABLE_SHAPE_HINT

Set this to true if you know that a shape you are drawing never changes from frame to frame. Set it to false if you aren't certain whether the shape changes.

When Agile2D OpenGL draws a shape, it tesselates the shape into OpenGL triangles. If this hint is Boolean.TRUE, Agile2D assumes that the Shape's geometry never changes, and it caches the tesselation triangles for reuse from frame to frame. If this hint is Boolean.FALSE, Agile2D recreates the tesselations each time a shape is rendered.

Agile2D.KEY_CONVEX_SHAPE_HINT

(Advanced) Set this to true if you know that a Shape or Polygon you are drawing is convex. Set it to false if you are not certain whether a shape is convex.

For convex shapes and polygons, the Agile2D OpenGL renderer can avoid tesselating the polygon, and instead use glBegin(GL_POLYGON) ... glEnd() to render the polygon. This is much faster to render, but looks incorrect if the polygon is concave.


Extensions

We have implemented an extension to use OpenGL Vertex Arrays from Java in a portable way. The class agile2d.geom.VertexArray contains a list of simple shapes (points, lines, triangles and quadrilaterals. These simple shapes can be rendered directly by the hardware using OpenGL. To insure compatibility with standard Java, the VertexArray class implements the draw and fill methods.

Drawing of filling a list of simple shapes is not so useful if individual graphical attributes cannot be changed for each. The class agile2d.geom.VertexAttributes is designed to pass colors and other attributes supported by OpenGL. We plan to implement texture attributes but for now, we only support colors. One color has to be specified for each shape vertex and OpenGL can perform a very fast color interpolation of the vertex colors for each shape. On most graphic cards, this operation doesn't cost any added time. The colors can also be translucent.

Rendering vertex arrays is very fast. On a Laptop accelerated card, 625,000 random quadrilaterals using random translucent colors can be rendered per second. The speed is the same for interpolated colors or flat colors. Java can only render about 10,000 of these primitives per second: a 65 times increase. When color interpolation is used, this number drops around 5000 per second.

Here is an example that initialize a VertexArray and a VertexAttributes:

    va = new VertexArray();
    ca = new VertexAttributes();
    va.setMode(VertexArray.MODE_QUADS);
    for (int i = 0; i < 10000; i++) {
	float x1 = random(min, max);
	float y1 = random(min, max);
	float width = random(10, 20);
	float height = random(10, 20);
	va.addRect(x1, y1, width, height);
	ca.addColor(randomColor());
	ca.addColor(randomColor());
	ca.addColor(randomColor());
	ca.addColor(randomColor());
    }

Here is how to fill the VertexArray using the VertexAttributes:

public void paint(Graphics g) {
    ca.setSmooth(smooth);
    va.fill(g, ca);
}

This feature is experimental and can change in the future. If you have comments or ideas of similar extensions, please, let us know.

[Back to agile home]

Copyright 2002 by University of Maryland, USA All rights reserved.