 | Level: Introductory John Zukowski (jaz@zukowski.net), President, JZ Ventures, Inc.
29 Apr 2003 Game developers and computer speed nuts alike will love Merlin's new Fullscreen Exclusive Mode (FEM) API, with which you can write directly to video memory with complete control over the graphics display. In this latest Magic with Merlin installment, Java programming expert John Zukowski introduces the power of this new Merlin feature.
Do you enjoy upsetting people by making your programs do annoying things? If you answered "yes," this month's tip is sure to be a pleaser. With J2SE 1.4, your Java programs can now change video modes and seize absolute control of the screen. You need not let anyone else play nicely; you literally can possess total control. For this god-like power, thank the new Fullscreen Exclusive Mode (FEM) API.
Even if you answered "no" and you don't get your kicks by annoying others, you'll find the FEM API has much to offer. The FEM API delivers complete control of the display by writing directly to video RAM -- perfect for game development, although other uses abound. Some programs, for instance, simply look and work better with a certain size screen. Read on to discover your inner control freak.
Change display modes
Let's begin by examining the FEM API's java.awt.DislayMode class, which wraps the screen dimensions and refresh rate for a particular display mode. The modes supported are specific to a system's hardware support.
To find out a specific system's supported modes, ask the GraphicsEnvironment. From that environment you get the default screen device, a GraphicDevice, from which you get the display modes, as seen in Listing 1:
Listing 1. Look up display modes
GraphicsEnvironment graphicsEnvironment =
GraphicsEnvironment.getLocalGraphicsEnvironment();
GraphicsDevice graphicsDevice =
graphicsEnvironment.getDefaultScreenDevice();
DisplayMode displayModes[] =
graphicsDevice.getDisplayModes();
|
You can also discover the current display mode with the getDisplayMode() method, as shown in Listing 2:
Listing 2. Get the current display mode
DisplayMode originalDisplayMode =
graphicsDevice.getDisplayMode();
|
Changing modes proves relatively easy, but you must first ask if the involved graphics device supports changing with the GraphicsDevice's isDisplayChangeSupported() method.
Once that's known, to change modes use the setDisplayMode() method, passing in the new mode. The display-mode change typically occurs in a try/finally
block such that the finally block resets code to the original mode. While that process is not absolutely required, it ensures a safe mode upon the program's completion. Listing 3 shows a typical pattern for changing display modes:
Listing 3. Change modes
GraphicsDevice graphicsDevice = ...
DisplayMode originalDisplayMode = graphicsDevice.getDisplayMode();
DisplayMode newDisplayMode = ...
try {
if (graphicsDevice.isDisplayChangeSupported()) {
graphicsDevice.setDisplayMode(newDisplayMode);
}
} finally {
graphicsDevice.setDisplayMode(originalDisplayMode);
}
|
Use fullscreen windows
With the FEM API, entering fullscreen windows is a snap: just pass a window to the GraphicsDevice's setFullScreenWindow() method, as shown in Listing 4. When you want to return to nonfullscreen mode, pass null into the method. Of course, first check whether the GraphicsDevice supports fullscreen mode by employing the isFullScreenSupported() method.
Listing 4. Enter fullscreen mode
GraphicsDevice graphicsDevice = ...
Window window = ...
try {
if (graphicsDevice.isFullScreenSupported()) {
graphicsDevice.setFullScreenWindow(window);
}
} finally {
graphicsDevice.setFullScreenWindow(null);
}
|
To illustrate everything you've learned so far, Listing 5 contains a complete example. The code in Listing 5 gets the display modes, picks one at random to change to, then displays a fullscreen window with the text "Hello, World!" The new display mode's characteristics display so you can see the specific screen size, refresh rate, and bit depth.
Listing 5. Mode changing example
import java.awt.*;
import javax.swing.*;
import java.util.Random;
public class DisplayModes {
public static void main(String args[]) {
GraphicsEnvironment graphicsEnvironment =
GraphicsEnvironment.getLocalGraphicsEnvironment();
GraphicsDevice graphicsDevice =
graphicsEnvironment.getDefaultScreenDevice();
DisplayMode displayModes[] = graphicsDevice.getDisplayModes();
DisplayMode originalDisplayMode = graphicsDevice.getDisplayMode();
JWindow window = new JWindow() {
public void paint(Graphics g) {
g.setColor(Color.blue);
g.drawString("Hello, World!", 50, 50);
}
};
try {
if (graphicsDevice.isFullScreenSupported()) {
graphicsDevice.setFullScreenWindow(window);
}
Random random = new Random();
int mode = random.nextInt(displayModes.length);
DisplayMode displayMode = displayModes[mode];
System.out.println(displayMode.getWidth() + "x" +
displayMode.getHeight() + " \t" + displayMode.getRefreshRate() +
" / " + displayMode.getBitDepth());
if (graphicsDevice.isDisplayChangeSupported()) {
graphicsDevice.setDisplayMode(displayMode);
}
Thread.sleep(1000);
} catch (InterruptedException e) {
} finally {
graphicsDevice.setDisplayMode(originalDisplayMode);
graphicsDevice.setFullScreenWindow(null);
}
System.exit(0);
}
} |
 |
Fullscreen rendering
Notice in Listing 5 that the window used for the fullscreen includes a perfectly functional paint() method. However, because the window is in fullscreen mode, the overhead required for the paint() method, how the method deals with clipping, and other associated display-handling behaviors are unnecessary. Indeed, the standard passive rendering proves overkill, slowing fullscreen application down. You needn't deal with such tasks as overlapping or resizing a window, for instance. Instead, take a more active approach and create a tight loop that handles the rendering itself.
If you're familiar with double buffering, you know it manages two Image objects in memory and swaps between them based upon which possesses the current display information. While one Image displays, you draw to the other and swap Image objects between each scene.
Graphics cards employ a similar concept, but instead of working with actual Java Image objects, they swap memory pages. The display changes when you swap memory pages so you need not copy an Image object from program memory to video memory; you merely change video pointers and the display changes. While the double-buffering concept still exists, instead of writing to an Image in program space, you draw directly to video memory space.
The BufferStrategy class masks which of the two aforementioned double-buffering methodologies is used and lets you take advantage of any hardware-based buffering the system offers. To create a BufferStrategy, tell the system the number of buffers you desire with the createBufferStrategy() method and swap between buffers with the getDrawGraphics() method, which returns the next buffer to work with. Conceptually that's all there is to it, but, as Listing 6 shows, the working code requires a bit more effort:
Listing 6. Use a BufferStrategy
JFrame frame = ...
frame.createBufferStrategy(2); // Number of buffers to have
BufferStrategy bufferStrategy = frame.getBufferStrategy();
while (!done()) { // Some condition to end
Graphics g = null;
try {
g = bufferStrategy.getDrawGraphics();
drawNextScene(g); // Method to draw to
} finally {
// Free resources
if (g != null) {
g.dispose();
}
}
bufferStrategy.show();
}
|
When using a BufferStrategy, you can't assume the last item you drew to the buffer remains valid; you must ask with the contentsLost() method. If lost, you must redraw the entire buffer. Otherwise, you need draw only what's changed since its last usage.
In addition to buffer support with BufferStrategy, the BufferCapabilities class lets you discover what capabilities -- such as full screen mode -- a strategy supports with the getCapabilities() method.
A working example
Putting all the pieces together produces the example program in Listing 7. Because of my limited artistic skills, don't look for a sophisticated game. You will, however, find a complete working example employing a BufferStrategy and fullscreen drawing modes. The example program purposely does not synchronize buffers by remembering what wasn't drawn to the current buffer -- letting you see multiple buffers at work more clearly. The program does randomly draw 100 rectangles to the screen, with a 1/10th-of-a-second delay between draws.
Listing 7. A working example
import java.awt.*;
import java.awt.image.*;
import javax.swing.*;
import java.util.Random;
public class MultipleBuffers {
public static void main(String args[]) {
GraphicsEnvironment graphicsEnvironment =
GraphicsEnvironment.getLocalGraphicsEnvironment();
GraphicsDevice graphicsDevice =
graphicsEnvironment.getDefaultScreenDevice();
DisplayMode displayModes[] = graphicsDevice.getDisplayModes();
DisplayMode originalDisplayMode = graphicsDevice.getDisplayMode();
JFrame frame = new JFrame();
frame.setUndecorated(true);
frame.setIgnoreRepaint(true);
try {
if (graphicsDevice.isFullScreenSupported()) {
graphicsDevice.setFullScreenWindow(frame);
}
Random random = new Random();
int mode = random.nextInt(displayModes.length);
DisplayMode displayMode = displayModes[mode];
System.out.println(displayMode.getWidth() + "x" +
displayMode.getHeight() + " \t" + displayMode.getRefreshRate() +
" / " + displayMode.getBitDepth());
if (graphicsDevice.isDisplayChangeSupported()) {
graphicsDevice.setDisplayMode(displayMode);
}
frame.createBufferStrategy(2);
BufferStrategy bufferStrategy = frame.getBufferStrategy();
int width = frame.getWidth();
int height = frame.getHeight();
for (int i=0; i<100; i++) {
Graphics g = bufferStrategy.getDrawGraphics();
g.setColor(new Color(random.nextInt()));
g.fillRect(random.nextInt(width),
random.nextInt(height), 100, 100);
bufferStrategy.show();
g.dispose();
Thread.sleep(100);
}
} catch (InterruptedException e) {
} finally {
graphicsDevice.setDisplayMode(originalDisplayMode);
graphicsDevice.setFullScreenWindow(null);
}
System.exit(0);
}
}
|
The example program would benefit from such enhancements as keeping the buffers synchronized or checking if the complete buffer must be redrawn when contents are lost. The former task requires remembering only the last rectangle drawn (and the color), while the latter requires remembering them all.
First the screen, then the known universe!
With the new Fullscreen Exclusive Mode API, Java development becomes a mainstream option for game development. The API replaces the need to work with platform-specific APIs like DirectX or OpenGL, instead relying on a standard API across all Java-enabled platforms. It's more than an aspiring programming despot could imagine.
Resources
About the author
Rate this page
|  |