Jumping into JOGL Blog

Version 2


    Announced in July, the pa rtnership 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 fromJava3D, 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 JOGLproject. 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.dllon Windows, liblogl.jnilib on Mac OS X, andlibjogl.so on Linux.

    The .jar file jogl.jar needs to be in yourCLASSPATH for compiling and running your code, while the native library file or files also need to be along thejava.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 orGLU 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:


    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 asGraphics.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 calledreshape(), 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 theGLDrawable 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 theGLDrawable 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 aGL 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 tackgl. on each of these calls. When a call toglu.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 withglVertex2i(). 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, adrawLine() 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 theGL_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 asGL_TRIANGLE_STRIP, in which the last two points of one triangle are reused as the first two points of the next, andGL_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 callglBegin 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 isglDrawPixels(), 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 withBufferedImage.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 aBufferedImage for which we define aRaster -- an object that wraps aDataBuffer of pixel data and aSampleModel that represents how image data is encoded in the buffer. In our case, the Raster uses data arranged as bytes and the standard sRGBcolor 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 theGraphics2D, 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 viaAffineTransformation: 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 Rasterwith 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


    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 thedisplay() 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.