Skip to main content

Jumping into JOGL

September 11, 2003

{cs.r.title}






Announced in July, the partnership of Sun and SGI to provide Java bindings to OpenGL gave a jolt to the Java community, particularly to desktop, graphics, and game developers. While some were disappointed to see Sun back away from Java3D, others were excited to see the popular and widely understood OpenGL exposed in a more direct fashion to Java developers.

A reference implementation of the Java/OpenGL binding is hosted on
java.net as the JOGL project.
This article will get you up and running with JOGL by describing:

  1. How to download and provision the JOGL library files.
  2. How to create a JOGL-powered AWT component that's wired up to receive and respond to events such as size changes and repaint requests.
  3. How to do 2D graphics in JOGL with simple graphic primitives and images.

Yes, 2D for now, to keep things simple. OpenGL excels at 3D, and we'll get there in a future
installment. But for now, consider the promising idea that as a hardware-accelerated
2D API, if JOGL were to be used as a rendering pipeline for Java2D, then everyday uses of
Java2D -- including Swing -- could gain a significant performance improvement.
In fact, this
is being done for Linux and Solaris in Java 1.5
.

Jumping In

JOGL currently supports four platforms:

  • Windows
  • Solaris
  • Mac OS X
  • Linux (on x86)

The JOGL site specifies minimum requirements for building the software on
each platform, but not for running it. The run requirements are clearly more
permissive; the build requirements specify Windows 2000 and Red Hat
Linux, but I've run this article's sample code on Windows XP Home and
Debian Linux. A few important
considerations are mentioned in the preliminary user's guide, which notes that J2SE 1.4 is a minimum requirement on all platforms, as is running in truecolor mode (15 bits or higher). Also, while the user's guide says that the minimum supported Mac OS X version is 10.3 -- currently available only as a pre-release seed to certain developers -- JOGL actually works with Mac OS X 10.2 and the "Java 1.4.1 Update 1" that was released on September 8.

Unless you're planning to contribute to the development of JOGL
itself, you don't need to check it out from CVS or worry about the
build requirements listed under the "Getting Started"
section of JOGL's home page. Instead, go over to the "Downloads" section and look at the precompiled binaries and documentation link. Here, you'll find several builds by date -- September 5, 2003 is the most recent as of this writing -- and inside of
each build folder, a list of platform-specific .zip files or tarballs,
plus a tarball of Javadocs.

Grab the distribution for your platform and decompress it on your
system. While the details slightly differ by platform, each
compressed file expands into a jogl.jar file and one or
two native shared-library files: jogl.dll and
jogl_cg.dll on Windows, liblogl.jnilib on
Mac OS X, and libjogl.so on Linux.

The .jar file jogl.jar needs to be in your CLASSPATH
for compiling and running your code, while the native library file or
files also need to be along the java.library.path at
run time. You have two options of where to place these files.

  • Put the .jar in the lib/ext directory inside of the Java home directory, and the native libraries in an equivalent always-checked directory (such as jre/bin on Windows, or /Library/Java/Extensions on Mac OS X [note: this path has been changed since this article was first posted, on advice from Apple engineers. - ed]). This approach gets you up and running quickly.

  • You could include the files with your code
    and point to them directly with -classpath and
    -Djava.library.path arguments on your command line (or the
    equivalent, if you use a third-party installer) to make it easier to deploy your JOGL app. This approach helps
    end users who may not want, or may not be able, to add files to these
    directories.

With either of these approaches, you should be able to compile and run this article's sample code, which consists of a single source file, JOGL2DBasics.java.

Unfamiliar Territory

A look at the Javadocs reveals a small number of classes, but don't
be deceived; there is a massive amount of functionality in
JOGL. Click on the GL or GLU classes to see
what I mean. Assuming they don't crash or hang your browser, you'll
find these classes are immense; much bigger than anything you'd
typically see in core Java. By my count, the GL class'
Javadocs list 2,433 fields and 1,856 methods!

What we have here is a C header file, wrapped in a very
straightforward manner by a Java class. It's the obvious approach,
and makes it easier to port C-based OpenGL code to JOGL, because you
know where to find fields and methods -- fields and functions from
gl.h should be in the GL class, anything
from glu.h should be in the GLU class,
etc.

This C-based approach isn't in any way object-oriented. There's no concept of
objects in any useful sense here, no idea that you're doing anything
but calling a library. This is an interesting contrast with QuickTime for Java, another case where Java classes wrap a vast C API, but in that case, Apple's engineers fashioned objects out of the structures
and functions to make something that feels a little more natural to
the Java programmer, if still more C-like than a typical JavaSoft API. One approach is not necessarily better than the
other: QuickTime's many structs may be better suited to OOP than
OpenGL's typical use of primitives and arrays. But the fact remains
that JOGL's very thin Java-to-native layer will make ports from C code
easier, and good OOP style harder.

On the other hand, there's no reason a higher-level API, perhaps Java3D itself, couldn't be built on top of JOGL. Such an arrangement would be the best of both worlds: developers of games and other performance-critical applications could use the low-level JOGL API, and others could choose a graphics API with a less-steep learning curve and still pick up hardware-accelerated performance.

JOGL Components and GLEventListener

To get into JOGL, we'll create a simple JOGL-based AWT component
that draws some 2D primitives. This article's sample code expands on
an example in a message on the JOGL forum, which in turn is an adaptation of the "firstAttempt" code example in F. S. Hill Jr.'s Computer Graphics using Open GL, Second Edition, a particularly popular textbook for learning graphics concepts and how to use them in
OpenGL.

The first thing we need to do to get JOGL into an AWT app is to
create a GLCanvas component:

GLCapabilities capabilities = new GLCapabilities();
GLCanvas canvas =
    GLDrawableFactory.getFactory().createGLCanvas(capabilities);

The obvious difference between this code and the typical means of
creating AWT components with constructors is that we have to ask this
GLCapabilities object for a component that is tuned to
the characteristics of the current display, such as its color
depth.

It might seem odd that the sample uses AWT instead of the more-popular Swing API. While the GLCanvas has a Swing equivalent, GLJPanel, the Swing version does not currently enjoy
hardware-accelerated rendering. That means it's slow, which for many
would defeat the whole purpose of using JOGL.

The next thing we have to do with our canvas is to register it as a
GLEventListener:

canvas.addGLEventListener(this);

The GLEventListener is primarily used to call back to our
component when it needs to repaint itself or deal with various
changes. The interface defines four methods:

  • init (GLDrawable drawable): called when OpenGL is initialized, and thus useful for any one-time-only setup work.

  • display (GLDrawable drawable): a request for the component to draw itself.

  • reshape (GLDrawable drawable, int i, int x, int width, int height): signals that the component's location or size has been changed.

  • displayChanged (GLDrawable drawable, boolean modeChanged, boolean deviceChanged): used to signal that the display mode or device has changed, such as when the user changes the color depth from 16-bit to 32-bit color (i.e., from "thousands" to "millions" of colors). It might also indicate that the window has been dragged from one monitor (a "device") to another, in operating systems that support such arrangements. As of this writing, this callback is not yet implemented.

Experienced AWT/Swing developers, particularly those who've created
their own components by painting with graphics primitives, will notice
how these callbacks resemble some of the AWT methods that are
typically overridden to create custom components, such
as Graphics.paint() as a request to re-image the
component, or Component.setBounds() as a signal that the
size and/or location has changed (in fact, the now-deprecated Java 1.0
equivalent of setBounds() was called
reshape(), just like in OpenGL).

In the sample application, init() is used to set the
colors for erasing and drawing to white and black, respectively, and to
set the size in pixels for drawing points:

GL gl = drawable.getGL(); 
gl.glClearColor( 1.0f, 1.0f, 1.0f, 1.0f );
gl.glColor3f( 0.0f, 0.0f, 0.0f );
gl.glPointSize(4.0f);

Notice that the GL object is retrieved from the
GLDrawable that is passed into the init()
method. All of the GLEventListener methods provide this
object, and the JOGL User's Guide encourages developers to always get a fresh reference
to the GL object from the GLDrawable on each
callback, rather than caching it in a field and reusing it. The
rationale for this has to do with threading issues within the AWT and
the dangers of making OpenGL calls from the wrong thread.

But what is this GL class? The docs define it as
"the basic interface to OpenGL." As mentioned above, its
design is almost like a straightforward dump of the gl.h
header file. In a sense, it's better not to think of it as an
object as all, but rather as handle for making method calls. If
you're porting from native OpenGL code, then you would expect
functions that start with a gl or constants that start with
a GL to be accessed via this GL instance.
In fact, the code in the init method is a very
straightforward port of the following C code from Hill:

glClearColor (1.0, 1.0, 1.0, 0.0);
glColor3f (0.0f, 0.0f, 0.0f);
glPointSize (4.0f);

So all we've had to do to port to JOGL is to tack gl. on
each of these calls. When a call to glu.h is necessary,
we'll get the GLU object from the GLDrawable
and make glu.-type calls.

The reshape() method sets or re-sets its size and
"viewport," which represents what part of the component is
being drawn into; in this simple case, this is always the entire
component. We also have to make some calls to indicate that we're
working in a simplistic 2D environment with no rotations,
translations, or other matrix-based transformations.

GL gl = drawable.getGL(); 
GLU glu = drawable.getGLU();
gl.glViewport( 0, 0, width, height );
gl.glMatrixMode( GL.GL_PROJECTION ); 
gl.glLoadIdentity();
glu.gluOrtho2D( 0.0, 450.0, 0.0, 375.0);

Finally, there's the display() method, a callback that
tells the component to perform its drawing. This will be called after
init() and reshape() at startup, and again
after any GUI events that could require a repaint, such as dragging
the component's window around, placing another window over the
component, etc. The display() method could also be
called by JOGL's Animator class, which exists to call
display() repeatedly from a loop. This has obvious use
for games, media, and other applications -- they perform animation by
slightly changing the component on each successive call to
display().

This article's sample code keeps things simple by drawing the same
thing on each call to display(). It erases the
component with gl.glClear (GL.GL_COLOR_BUFFER_BIT), then
makes a series of calls to helper methods that display various 2D
drawing commands in JOGL:

drawSomeDots(gl);
drawSomeLines(gl);
drawOpenPoly(gl);
drawClosedPoly(gl);
drawFilledRect (gl, Color.cyan.darker());
drawFilledPoly (gl, Color.red.darker());
drawTriangles (gl);
drawImage (gl);

Graphics Primitives in JOGL

The first method in the sample code is the "three dots"
example from Hill. Having passed in a GL, the code looks
like this:

gl.glBegin (GL.GL_POINTS);
gl.glVertex2i (100,50);
gl.glVertex2i (100,130);
gl.glVertex2i (150,130);
gl.glEnd ();

The flow here is straightforward: tell JOGL that we want to draw some
points, and then give their coordinates.

One important fact to be aware of is that in OpenGL (and thus JOGL),
the origin point (0,0) is at the bottom left, instead of the top
left, as in AWT and many other 2D imaging systems. So the points in
this example appear as in Figure 1.


Figure 1. Points drawn at (100,50), (100,130), and (150,130)

The next simple graphic primitive is the line. As with the
points, these are defined by just providing coordinates with
glVertex2i(). But by using GL.GL_LINES as
the argument to glBegin, the behavior changes from
drawing points to drawing lines between each pair of points:

gl.glBegin (GL.GL_LINES);
gl.glVertex2i (50, 200);
gl.glVertex2i (75, 250);
gl.glVertex2i (60, 200);
gl.glVertex2i (85, 250);
gl.glEnd();

Of course, it would be easy to write a library to wrap calls like this in a more Java-friendly fashion. For example, a drawLine() method would look like this:

public void drawLine (GL gl, Point p1, Point p2) {
   gl.glBegin (GL.GL_LINES);
   gl.glVertex2i (p1.x, p1.y);
   gl.glVertex2i (p2.x, p2.y);
   gl.glEnd();
}

Instead of drawing lines between pairs of points, we can use the
GL_LINE_STRIP behavior to connect each point in
sequence. A sequence drawn in this fashion ends at the last point.
Using GL_LINE_LOOP, an extra line is drawn from the final
point back to the first point. The different behaviors are shown in
Figure 2.


Figure 2. Drawing with GL_LINES, GL_LINE_STRIP, and
GL_LINE_LOOP

Another set of primitives is the various polygons that can be
drawn and filled with OpenGL. The simplest are triangles, which
create triangles from sets of three coordinates:

gl.glBegin (GL.GL_TRIANGLES);
gl.glVertex2i (400, 50);
gl.glVertex2i (400, 100);
gl.glVertex2i (420, 75);
gl.glVertex2i (425, 50);
gl.glVertex2i (425, 100);
gl.glVertex2i (445, 75);
gl.glEnd();

There are alternate triangle modes available, such as
GL_TRIANGLE_STRIP, in which the last two points of one
triangle are reused as the first two points of the next, and
GL_TRIANGLE_FAN, in which the first point provided is
connected to each subsequent pair of coordinates.

GL_QUADS provides a similar approach for defining
quadrangles as groups of four points. However, for basic rectangles,
which are used much more often than arbitrary quadrangles, it's easier
to define the points in a simple one-line call:

gl.glRecti (200, 50, 250, 150);

This defines a rectangle stretching from (200,50) to (250,150) and
fills it with the current color.

For shapes with an arbitrary number of points, you call
glBegin with the GL_POLYGON argument, and
then define the points of the polygon:

gl.glBegin (GL.GL_POLYGON);
gl.glVertex2i (300, 50);
gl.glVertex2i (350, 60);
gl.glVertex2i (375, 100);
gl.glVertex2i (325, 115);
gl.glVertex2i (300, 75);
gl.glEnd();

In all of these cases, the shape is filled with the current color,
which can be set with glColor3f.

The quoted code samples produce the rectangle, polygon, and
triangles seen in Figure 3.


Figure 3. Drawing with glRecti(), GL_POLYGON, and GL_TRIANGLES

Drawing Images

AWT programmers are probably familiar with similar graphics
primitives defined in the Graphics class:
drawLine(), drawRect(),
drawPolygon(), etc. One particularly useful method
in AWT is drawImage(). The OpenGL equivalent is
glDrawPixels(), though it takes a little care and feeding
to make it work as expected.

The call to glDrawPixels() takes a width and height,
two parameters to define the format of the image data, and then the
image data itself in the form of an array of one of the primitive
types (byte, short, int,
etc.).

It seems like it should be easy enough to let Java load an image,
for example, a small .gif of Duke found on this
page
, then get an array of pixels from that.

The first problem with this approach is one of format. We could
get an int[] of pixels from a Java image with
BufferedImage.getRGB(). Unfortunately, this returns our
data in Java's default ARGB format, meaning the 32-bit
ints are packed with 8 bits each of alpha mask
(translucency): red, green, and blue, in that order.
GL doesn't define a constant for this color model.

Moreover, the use of int is potentially hazardous on
machines with "little-endian" architectures, such as Intel
x86 CPUs. In the "little-endian" world, the least
significant 16 bits of a 32-bit integer come first, followed by
the most significant 16 bits. It's not a
problem when we're entirely in Java, but when we pass such an array
from Java to the native OpenGL, the endianness can turn what we
thought was ARGB into GBAR, meaning our colors and transparency get
scrambled.

And there's one more problem. Remember that OpenGL puts (0,0) at
the bottom left. Java images have (0,0) at the upper left, so as the
y coordinates increase and move further from the axis, the row order
makes the image look like it's upside down in the OpenGL world, as
illustrated in Figure 4.


Figure 4. duke_wave.gif image in Java coordinate space (left) and OpenGL coordinate space (right)

So, to provide glDrawPixels() with a suitable array,
we need to do the following:

  1. Convert to a color model that OpenGL understands.
  2. Use a byte array to keep the color values properly arranged.
  3. Flip the image vertically so that the row order puts the bottom of the image on the first row.

Fortunately, the Java 2D Graphics API provides everything we need
to do this, without resorting to bit-shifting voodoo, another benefit of having the complete set of J2SE APIs at our disposal.

The first two issues can be resolved by creating a
BufferedImage for which we define a Raster -- an object that wraps a DataBuffer of pixel data and a SampleModel that represents how image data is encoded in the buffer. In our case, the Raster uses data arranged as bytes and the standard sRGB color space:

WritableRaster raster = 
Raster.createInterleavedRaster (DataBuffer.TYPE_BYTE,
                                    dukeWidth,
                                    dukeHeight,
                                    4,
                                    null);
ComponentColorModel colorModel=
new ComponentColorModel (ColorSpace.getInstance(ColorSpace.CS_sRGB),
                             new int[] {8,8,8,8},
                             true,
                             false,
                             ComponentColorModel.TRANSLUCENT,
                             DataBuffer.TYPE_BYTE);
BufferedImage dukeImg =
    new BufferedImage (colorModel,
                       raster,
                       false,
                       null);

That solves the first two problems. Now, to flip the image, we
define an AffineTransform that moves the image into
negative y coefficients, then scales the pixels
into a reverse ordering by multiplying their y coefficients by -1,
which also moves them back into positive values. This
transformation is applied to the BufferedImage's
offscreen Graphics2D, and the image is drawn into the
Graphics2D, picking up the transformation in the process.

Graphics2D g = dukeImg.createGraphics();
AffineTransform gt = new AffineTransform();
gt.translate (0, dukeHeight);
gt.scale (1, -1d);
g.transform (gt);
g.drawImage (i, null, null);

Figure 5 illustrates this two-step process.


Figure 5. duke_wave.gif flipped via AffineTransformation: original, after translate, and after scale
(shown in Java coordinate space)

With the row order now corrected for the OpenGL coordinate space,
we finally have an image that can be sent to JOGL. The byte array dukeRGBA is fetched from the Raster with the following code:

DataBufferByte dukeBuf =
    (DataBufferByte)raster.getDataBuffer();
byte[] dukeRGBA = dukeBuf.getData();

The sample code draws a rectangle under the image, just to prove that we've preserved
the .gif transparency (the gray pixels in the figures, which were made
opaque to show the image bounds more clearly). Before drawing the
image, we need to enable alpha mask, and also call
glRasterPos2i() to indicate where we want the image to be drawn:

gl.glBlendFunc (GL.GL_SRC_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA);
gl.glEnable (GL.GL_BLEND);
gl.glColor3f (0.0f, 0.5f, 0.0f);
gl.glRecti (0, 300, 100, 330);
gl.glColor3f (0.0f, 0.0f, 0.0f);
gl.glRasterPos2i (10, 300);
gl.glDrawPixels (dukeWidth, dukeHeight,
                 gl.GL_RGBA, gl.GL_UNSIGNED_BYTE,
                 dukeRGBA);

Figure 6 shows the imported and flipped Duke, with his transparency
intact.


Figure 6. duke_wav.gif drawn atop rectangle in JOGL

Conclusion

Figure 7 shows the entire GLCanvas, as rendered by the
sample code.


Figure 7. JOGL2DBasics window

If you run JOGL2DBasics and resize the window, you'll notice that
all the graphics primitives are all scaled to suit the new window
dimensions, though the image is not. If you keep an eye on the
standard output, you'll also get a sense for what kinds of activity on
your desktop cause the display() and
reshape() methods to be called.

That completes this introduction to JOGL, the goal of which was to
simply help you get up and running with JOGL by compiling and running
a set of 2D graphics calls. Future installments will delve into 3D,
animation, and other topics relevant to high-performance graphics in Java.

Chris Adamson is the former editor of java.net.
Related Topics >> Programming   |   

Comments

I read this article it really

I read this article it really was helpful. But one thing is still not working as it should. The gl.glColor3f (0.0f, 0.5f, 0.0f); command followed by gl.glVertex... command fails to change any color in my machine. I have checked many sites to figure out what is amiss but to no avail. I downloaded the Sample.java from this site and run launched it. I get the objects displayed but all in 'red'!!! Should I change anything, the frame just goes black!!!! is jogl really such a complicated stuff with no way around??? pls help