Full-Screen Exclusive Mode API

«« Previous
Next »»

1. Full-Screen Exclusive Mode


Programmers who use Microsoft's DirectX API may already be familiar with full-screen exclusive mode. Other programmers may be somewhat new to the concept. In either case, full-screen exclusive mode is a powerful feature of J2SE™ version 1.4 that allows the programmer to suspend the windowing system so that drawing can be done directly to the screen.

This is a slight paradigm shift from the usual kind of GUI program in many ways. In traditional Java GUI programs, the AWT is responsible for propagating paint events from the operating system, through the event dispatch thread, and by calling AWT's Component.paint method when appropriate. In full-screen exclusive applications, painting is usually done actively by the program itself. Additionally, a traditional GUI application is limited to the bit depth and size of the screen chosen by the user. In a full-screen exclusive application, the program can control the bit depth and size (display mode) of the screen. Finally, many more advanced techniques, such as page flipping (discussed below) and stereo buffering (utilizing systems which use a separate set of frames for each eye) require, on some platforms, that an application first be in full-screen exclusive mode.

Hardware-Accelerated Image Basics

To understand the full-screen exclusive mode API, you need to understand some basic principles of hardware-accelerated images. The VolatileImage interface encapsulates a surface which may or may not take advantage of hardware acceleration. Such surfaces may lose their hardware acceleration or their memory at the behest of the operating system (hence, the name 'volatile'). See the VolatileImage Tutorial (coming soon) for more information on volatile images.

Full-screen exclusive mode is handled through a java.awt.GraphicsDevice object. For a list of all available screen graphics devices (in single or multi-monitor systems), you can call the method getScreenDevices on the local java.awt.GraphicsEnvironment; for the default (primary) screen (the only screen on a single-monitor system), you can call the method getDefaultScreenDevice.

Once you have the graphics device, you can call one of the following methods:

◈ public boolean isFullScreenSupported()

This method returns whether or not full-screen exclusive mode is available. On systems where full-screen exclusive mode is not available, it is probably better to run an application in windowed mode with a fixed size rather than setting a full-screen window.

◈ public void setFullScreenWindow(Window w)

Given a window, this method enters full-screen exclusive mode using that window. If full-screen exclusive mode is not available, the window is positioned at (0,0) and resized to fit the screen. Use this method with a null parameter to exit full-screen exclusive mode.

Programming Tips

Here are some tips about programming using full-screen exclusive mode:

◈ Check for isFullScreenSupported before entering full-screen exclusive mode. If it isn't supported, performance may be degraded.
◈ Entering and exiting full-screen mode is more robust when using a try...finally clause. This is not only good coding practice, but it also prevents your program from staying in full-screen exclusive mode longer than it should:
GraphicsDevice myDevice;
Window myWindow;

try {
    myDevice.setFullScreenWindow(myWindow);
    ...
} finally {
    myDevice.setFullScreenWindow(null);
}

◈ Most full-screen exclusive applications are better suited to use undecorated windows. Turn off decorations in a frame or dialog using the setUndecorated method.
◈ Full-screen exclusive applications should not be resizable, since resizing a full-screen application can cause unpredictable (or possibly dangerous) behavior.
◈ For security reasons, the user must grant fullScreenExclusive permission when using full-screen exclusive mode in an applet.

2. Display Mode


Once an application is in full-screen exclusive mode, it may be able to take advantage of actively setting the display mode. A display mode (java.awt.DisplayMode) is composed of the size (width and height of the monitor, in pixels), bit depth (number of bits per pixel), and refresh rate (how frequently the monitor updates itself). Some operating systems allow you to use multiple bit depths at the same time, in which case the special value BIT_DEPTH_MULTI is used for the value of bit depth. Also, some operating systems may not have any control over the refresh rate (or you may not care about the refresh rate setting). In this case, the special value REFRESH_RATE_UNKNOWN is used for the refresh rate value.

How to Set the Display Mode

To get the current display mode, simply call the getDisplayMode method on your graphics device. To obtain a list of all possible display modes, call the getDisplayModes method. Both getDisplayMode and getDisplayModes can be called at any time, regardless of whether or not you are in full-screen exclusive mode.

Before attempting to change the display mode, you should first call the isDisplayChangeSupported method. If this method returns false, the operating system does not support changing the display mode.

Changing the display mode can only be done when in full-screen exclusive mode. To change the display mode, call the setDisplayMode method with the desired display mode. A runtime exception will be thrown if the display mode is not available, if display mode changes are not supported, or if you are not running in full-screen exclusive mode.

Reasons for Changing the Display Mode

The main reason for setting the display mode is performance. An application can run much more quickly if the images it chooses to display share the same bit depth as the screen. Also, if you can count on the display to be a particular size, it makes drawing to that display much simpler, since you do not have to scale things down or up depending on how the user has set the display.

Programming Tips

Here are some tips for choosing and setting the display mode:

◈ Check the value returned by the isDisplayChangeSupported method before attempting to change the display mode on a graphics device.
◈ Make sure you are in full-screen exclusive mode before attempting to change the display mode.
◈ As with using full-screen mode, setting the display mode is more robust when in a try...finally clause:

GraphicsDevice myDevice;
Window myWindow;
DisplayMode newDisplayMode;
DisplayMode oldDisplayMode
    = myDevice.getDisplayMode();

try {
    myDevice.setFullScreenWindow(myWindow);
    myDevice.setDisplayMode(newDisplayMode);
    ...
} finally {
    myDevice.setDisplayMode(oldDisplayMode);
    myDevice.setFullScreenWindow(null);
}

◈ When choosing a display mode for your application, you may want to keep a list of preferred display modes, then choose the best one from the list of available display modes.
◈ As a fallback, if the display mode you desire is not available, you may want to simply run in windowed mode at a fixed size.

3. Passive vs. Active Rendering


As mentioned before, most full-screen applications usually function better if they are at the helm during drawing. In traditional windowed GUI applications, the question of when to paint is usually handled by the operating system. When operating in a windowed environment, this makes perfect sense. A windowed application does not know when the user is going to move, resize, expose, or cover an application by another window until it actually happens. In a Java GUI application, the operating system delivers a paint event to the AWT, which figures out what needs to be painted, creates a java.awt.Graphics object with the appropriate clipping region, then calls the paint method with that Graphics object:

// Traditional GUI Application paint method:
// This can be called at any time, usually
// from the event dispatch thread
public void paint(Graphics g) {
    // Use g to draw my Component
}

This is sometimes referred to as passive rendering. As you can imagine, such a system incurs a lot of overhead, much to the annoyance of many performance-sensitive AWT and Swing programmers.

When in full-screen exclusive mode, you don't have to worry anymore about the window being resized, moved, exposed, or occluded (unless you've ignored my suggestion to turn off resizing). Instead, the application window is drawn directly to the screen (active rendering). This simplifies painting quite a bit, since you don't ever need to worry about paint events. In fact, paint events delivered by the operating system may even be delivered at inappropriate or unpredictable times when in full-screen exclusive mode.

Instead of relying on the paint method in full-screen exclusive mode, drawing code is usually more appropriately done in a rendering loop:

public void myRenderingLoop() {
    while (!done) {
        Graphics myGraphics = getPaintGraphics();
        // Draw as appropriate using myGraphics
        myGraphics.dispose();
    }
}

Such a rendering loop can done from any thread, either its own helper thread or as part of the main application thread.

Programming Tips

Some tips about using active rendering:

◈ Don't put drawing code in the paint routine. You may never know when that routine may get called! Instead, use another method name, such as render(Graphics g), which can be called from the paint method when operating in windowed mode, or alternately called with its own graphics from the rendering loop.
◈ Use the setIgnoreRepaint method on your application window and components to turn off all paint events dispatched from the operating system completely, since these may be called during inappropriate times, or worse, end up calling paint, which can lead to race conditions between the AWT event thread and your rendering loop.
◈ Separate your drawing code from your rendering loop, so that you can operate fully under both full-screen exclusive and windowed modes.
◈ Optimize your rendering so that you aren't drawing everything on the screen at all times (unless you are using page-flipping or double-buffering, both discussed below).
◈ Do not rely on the update or repaint methods for delivering paint events.
◈ Do not use heavyweight components, since these will still incur the overhead of involving the AWT and the platform's windowing system.
◈ If you use lightweight components, such as Swing components, you may have to fiddle with them a bit so that they draw using your Graphics, and not directly as a result of calling the paint method. ◈ Feel free to call Swing methods such as paintComponents, paintComponent, paintBorder, and paintChildren directly from your rendering loop.
◈ Feel free to use passive rendering if you just want a simple full-screen Swing or AWT application, but remember that paint events may be somewhat unreliable or unnecessary while in full-screen exclusive mode. Additionally, if you use passive rendering, you will not be able to use more advanced techniques such as page-flipping. Finally, be very careful to avoid deadlocks if you decide to use both active and passive rendering simultaneously--this approach is not recommended.

4. Double Buffering and Page Flipping


Suppose you had to draw an entire picture on the screen, pixel by pixel or line by line. If you were to draw such a thing directly to the screen (using, say, Graphics.drawLine), you would probably notice with much disappointment that it takes a bit of time. You will probably even notice visible artifacts of how your picture is drawn. Rather than watching things being drawn in this fashion and at this pace, most programmers use a technique called double-buffering.

The traditional notion of double-buffering in Java applications is fairly straightforward: create an offscreen image, draw to that image using the image's graphics object, then, in one step, call drawImage using the target window's graphics object and the offscreen image. You may have already noticed that Swing uses this technique in many of its components, usually enabled by default, using the setDoubleBuffered method.

The screen surface is commonly referred to as the primary surface, and the offscreen image used for double-buffering is commonly referred to as the back buffer. The act of copying the contents from one surface to another is frequently referred to as a block line transfer, or blitting (blt is typically pronounced "blit" and shouldn't be confused with a BLT sandwich).


The primary surface is usually manipulated through the graphics object of any showing component; when in full-screen mode, any operation using the graphics of the full-screen window is a direct manipulation of screen memory. For this reason, you can take advantage of other capabilities in full-screen exclusive mode that may otherwise be unavailable due to the overhead of the windowing system. One such technique that is only available in full-screen exclusive mode is a form of double-buffering called page-flipping.

Page Flipping

Many graphics cards have the notion of a video pointer, which is simply an address in video memory. This pointer tells the graphics card where to look for the contents of the video to be displayed during the next refresh cycle. In some graphics cards and on some operating systems, this pointer can even be manipulated programmatically. Suppose you created a back buffer (in video memory) of the exact width, height, and bit depth of the screen, then drew to that buffer the same way as you would using double-buffering. Now imagine what would happen if, instead of blitting your image to the screen as in double-buffering, you simply changed the video pointer to your back buffer. During the next refresh, the graphics card would now use your image to display. This switch is called page-flipping, and the performance gain over blt-based double-buffering is that only a single pointer needs to be moved in memory as opposed to copying the entire contents from one buffer to another.

When a page flip occurs, the pointer to the old back buffer now points to the primary surface and the pointer to the old primary surface now points to the back buffer memory. This sets you up automatically for the next draw operation.


Sometimes it is advantageous to set up multiple back buffers in a flip chain. This is particularly useful when the amount of time spent drawing is greater than the monitor's refresh rate. A flip chain is simply two or more back buffers (sometimes called intermediary buffers) plus the primary surface (this is sometimes called triple-buffering, quadruple-buffering, etc.). In a flip chain, the next available back buffer becomes the primary surface, etc., all the way down to the rearmost back buffer that is used for drawing.

Benefits of Double-Buffering and Page-Flipping

If your performance metric is simply the speed at which double-buffering or page-flipping occurs versus direct rendering, you may be disappointed. You may find that your numbers for direct rendering far exceed those for double-buffering and that those numbers far exceed those for page-flipping. Each of these techniques is for used for improving perceived performance, which is much more important in graphical applications than numerical performance.

Double-buffering is used primarily to eliminate visible draws which can make an application look amateurish, sluggish, or appear to flicker. Page-flipping is used primarily to also eliminate tearing, a splitting effect that occurs when drawing to the screen happens faster than the monitor's refresh rate. Smoother drawing means better perceived performance and a much better user experience.

5. BufferStrategy and BufferCapabilities


BufferStrategy

In Java 2 Standard Edition, you don't have to worry about video pointers or video memory in order to take full advantage of either double-buffering or page-flipping. The new class java.awt.image.BufferStrategy has been added for the convenience of dealing with drawing to surfaces and components in a general way, regardless of the number of buffers used or the technique used to display them.

A buffer strategy gives you two all-purpose methods for drawing: getDrawGraphics and show. When you want to start drawing, get a draw graphics and use it. When you are finished drawing and want to present your information to the screen, call show. These two methods are designed to fit rather gracefully into a rendering loop:

BufferStrategy myStrategy;

while (!done) {
    Graphics g = myStrategy.getDrawGraphics();
    render(g);
    g.dispose();
    myStrategy.show();
}

Buffer strategies have also been set up to help you monitor VolatileImage issues. When in full-screen exclusive mode, VolatileImage issues are especially important because the windowing system can sometimes take back the video memory it has given you. One important example is when the user presses the ALT+TAB key combination in Windows--suddenly your full-screen program is running in the background and your video memory is lost. You can call the contentsLost method to find out if this has happened. Similarly, when the windowing system returns your memory to you, you can find out using the contentsRestored method.

BufferCapabilities

As mentioned before, different operating systems, or even different graphics cards on the same operating system, have different techniques available at their disposal. These capabilities are exposed for you so that you can pick the best technique for your application.

The class java.awt.BufferCapabilities encapsulates these capabilities. Every buffer strategy is controlled by its buffer capabilities, so picking the right ones for your application is very crucial. To find out what capabilities are available, call the getBufferCapabilities method from the GraphicsConfiguration objects available on your graphics device.

The capabilities available in Java 2 Standard Edition version 1.4 are:

◈ isPageFlipping
This capability returns whether or not hardware page-flipping is available on this graphics configuration.

◈ isFullScreenRequired
This capability returns whether or not full-screen exclusive mode is required before hardware page-flipping should be attempted.

◈ isMultiBufferAvailable
This capability returns whether or not multiple buffering (two or more back buffers plus the primary surface) in hardware is available.

◈ getFlipContents
This capability returns a hint of the technique used to do hardware page-flipping. This is important because the contents of the back buffer after a show are different depending on the technique used. The value returned can be null (if isPageFlipping returns false) or one of the following values. Any value can be specified for a buffer strategy so long as the isPageFlipping method returns true, though performance will vary depending on the available capabilities.

◈ FlipContents.COPIED
This value means that the contents of the back buffer are copied to the primary surface. A "flip" is probably performed as a hardware blt, which means that hardware double-buffering is probably done using blitting instead of true page-flipping. This should (in theory) be faster, or at least as fast, as blitting from a VolatileImage to the primary surface, though your mileage may vary. The contents of the back buffer are the same as the primary surface after a flip.

◈ FlipContents.BACKGROUND
This value means that the contents of the back buffer have been cleared with the background color. Either a true page-flip or a blt has occurred.

◈ FlipContents.PRIOR
This value means that the contents of the back buffer are now the contents of the old primary surface, and vice versa. Generally this value indicates that true page-flipping occurs, though this is not guaranteed and, once again, your mileage on this operation may vary.
◈ FlipContents.UNKNOWN
This value means that the contents of the back buffer are undefined after a flip. You may have to experiment to find which technique works best for you (or you may not care), and you will definitely have to set up the contents of the back buffer yourself each time you draw.

To create a buffer strategy for a component, call the createBufferStrategy method, supplying the number of buffers desired (this number includes the primary surface).  If any particular buffering technique is desired, supply an appropriate BufferCapabilities object. Note that when you use this version of the method, you must catch an AWTException in the event that your choice is not available. Also note that these methods are only available on Canvas and Window.

Once a particular buffer strategy has been created for a component, you can manipulate it using the getBufferStrategy method. Note that this method is also only available for canvases and windows.

Programming Tips

Some tips about using buffer capabilities and buffer strategies:

◈ Getting, using, and disposing a graphics object are more robust in a try...finally clause:
BufferStrategy myStrategy;

while (!done) {
    Graphics g;
    try {
        g = myStrategy.getDrawGraphics();
        render(g);
    } finally {
        g.dispose();
    }
    myStrategy.show();
}

◈ Check the available capabilities before using a buffer strategy.
◈ For best results, create your buffer strategy on a full-screen exclusive window. Make sure you check the isFullScreenRequired and isPageFlipping capabilities before using page-flipping.
◈ Don't make any assumptions about performance. Tweak your code as necessary, but remember that different operating systems and graphics cards have different capabilities. Profile your application!
◈ You may want to subclass your component to override the createBufferStrategy method. Use an algorithm for choosing a strategy that is best suited to your application. The FlipBufferStrategy and  BltBufferStrategy inner classes are protected and can be subclassed.
◈ Don't forget that you may lose your drawing surfaces!  Be sure to check contentsLost and contentsRestored before drawing. All buffers that have been lost have to be redrawn when they are restored.
◈ If you use a buffer strategy for double-buffering in a Swing application, you probably want to turn off double-buffering for your Swing components, since they will already be double-buffered. Video memory is somewhat valuable and should only be used whenever absolutely necessary.
◈ It may be end up being wasteful to use more than one back buffer. Multi-buffering is only useful when the drawing time exceeds the time spent to do a show. Profile your application!

6. Examples


CapabilitiesTest demonstrates the different buffering capabilities available for the machine on which it is run.

import java.applet.Applet;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

/**
 * This class wraps a graphics configuration so that it can be
 * displayed nicely in components.
 */
class GCWrapper {
    private GraphicsConfiguration gc;
    private int index;
    
    public GCWrapper(GraphicsConfiguration gc, int index) {
        this.gc = gc;
        this.index = index;
    }
    
    public GraphicsConfiguration getGC() {
        return gc;
    }
    
    public String toString() {
        return gc.toString();
    }
}

/**
 * Main frame class.
 */
public class CapabilitiesTest extends JFrame implements ItemListener {
    
    private JComboBox gcSelection = new JComboBox();
    private JCheckBox imageAccelerated = new JCheckBox("Accelerated", false);
    private JCheckBox imageTrueVolatile = new JCheckBox("Volatile", false);
    private JCheckBox flipping = new JCheckBox("Flipping", false);
    private JLabel flippingMethod = new JLabel("");
    private JCheckBox fullScreen = new JCheckBox("Full Screen Only", false);
    private JCheckBox multiBuffer = new JCheckBox("Multi-Buffering", false);
    private JCheckBox fbAccelerated = new JCheckBox("Accelerated", false);
    private JCheckBox fbTrueVolatile = new JCheckBox("Volatile", false);
    private JCheckBox bbAccelerated = new JCheckBox("Accelerated", false);
    private JCheckBox bbTrueVolatile = new JCheckBox("Volatile", false);
    
    public CapabilitiesTest(GraphicsDevice dev) {
        super(dev.getDefaultConfiguration());
        addWindowListener(new WindowAdapter() {
            public void windowClosing(WindowEvent ev) {
                System.exit(0);
            }
        });
        initComponents(getContentPane());
        GraphicsConfiguration[] gcs = dev.getConfigurations();
        for (int i = 0; i < gcs.length; i++) {
            gcSelection.addItem(new GCWrapper(gcs[i], i));
        }
        gcSelection.addItemListener(this);
        gcChanged();
    }
    
    /**
     * Creates and lays out components in the container.
     * See the comments below for an organizational overview by panel.
     */
    private void initComponents(Container c) {
        // +=c============================================+
        // ++=gcPanel======================================+
        // ++                    [gcSelection]                     +
        // ++=capsPanel=====================================+
        // +++=imageCapsPanel===============================+
        // +++ [imageAccelerated]                                  +
        // +++ [imageTrueVolatile]                                 +
        // +++=bufferCapsPanel===============================+
        // ++++=bufferAccessCapsPanel=========================+
        // +++++=flippingPanel===============================+
        // +++++ [flipping]                                        +
        // +++++=fsPanel====================================+
        // +++++ [indentPanel][fullScreen]                         +
        // +++++=mbPanel===================================+
        // +++++ [indentPanel][multiBuffer]                        +
        // ++++=buffersPanel==================================+
        // +++++=fbPanel===============+=bbPanel==============+
        // +++++                       +                           +
        // +++++ [fbAccelerated]       + [bbAccelerated]           +
        // +++++                       +                           +
        // +++++ [fbTrueVolatile]      + [bbTrueVolatile]          +
        // +++++                       +                           +
        // +=============================================+
        c.setLayout(new BorderLayout());
        // Graphics Config
        JPanel gcPanel = new JPanel(new FlowLayout(FlowLayout.CENTER));
        c.add(gcPanel, BorderLayout.NORTH);
        gcSelection.setPreferredSize(new Dimension(400, 30));
        gcPanel.add(gcSelection);
        // Capabilities
        JPanel capsPanel = new JPanel(new BorderLayout());
        c.add(capsPanel, BorderLayout.CENTER);
        // Image Capabilities
        JPanel imageCapsPanel = new JPanel(new GridLayout(2, 1));
        capsPanel.add(imageCapsPanel, BorderLayout.NORTH);
        imageCapsPanel.setBorder(BorderFactory.createTitledBorder(
            "Image Capabilities"));
        imageAccelerated.setEnabled(false);
        imageCapsPanel.add(imageAccelerated);
        imageTrueVolatile.setEnabled(false);
        imageCapsPanel.add(imageTrueVolatile);
        // Buffer Capabilities
        JPanel bufferCapsPanel = new JPanel(new BorderLayout());
        capsPanel.add(bufferCapsPanel, BorderLayout.CENTER);
        bufferCapsPanel.setBorder(BorderFactory.createTitledBorder(
            "Buffer Capabilities"));
        // Buffer Access
        JPanel bufferAccessCapsPanel = new JPanel(new GridLayout(3, 1));
        bufferAccessCapsPanel.setPreferredSize(new Dimension(300, 88));
        bufferCapsPanel.add(bufferAccessCapsPanel, BorderLayout.NORTH);
        // Flipping
        JPanel flippingPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
        bufferAccessCapsPanel.add(flippingPanel);
        flippingPanel.add(flipping);
        flipping.setEnabled(false);
        flippingPanel.add(flippingMethod);
        // Full-screen
        JPanel fsPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
        bufferAccessCapsPanel.add(fsPanel);
        JPanel indentPanel = new JPanel();
        indentPanel.setPreferredSize(new Dimension(30, 30));
        fsPanel.add(indentPanel);
        fsPanel.add(fullScreen);
        fullScreen.setEnabled(false);
        // Multi-buffering
        JPanel mbPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
        bufferAccessCapsPanel.add(mbPanel);
        indentPanel = new JPanel();
        indentPanel.setPreferredSize(new Dimension(30, 30));
        mbPanel.add(indentPanel);
        mbPanel.add(multiBuffer);
        multiBuffer.setEnabled(false);
        // Front and Back Buffer Capabilities
        JPanel buffersPanel = new JPanel(new GridLayout(1, 2));
        bufferCapsPanel.add(buffersPanel, BorderLayout.CENTER);
        // Front Buffer
        JPanel fbPanel = new JPanel(new GridLayout(2, 1));
        fbPanel.setBorder(BorderFactory.createTitledBorder(
            "Front Buffer"));
        buffersPanel.add(fbPanel);
        fbPanel.add(fbAccelerated);
        fbAccelerated.setEnabled(false);
        fbPanel.add(fbTrueVolatile);
        fbTrueVolatile.setEnabled(false);
        // Back Buffer
        JPanel bbPanel = new JPanel(new GridLayout(2, 1));
        bbPanel.setPreferredSize(new Dimension(250, 80));
        bbPanel.setBorder(BorderFactory.createTitledBorder(
            "Back and Intermediate Buffers"));
        buffersPanel.add(bbPanel);
        bbPanel.add(bbAccelerated);
        bbAccelerated.setEnabled(false);
        bbPanel.add(bbTrueVolatile);
        bbTrueVolatile.setEnabled(false);
    }
    
    public void itemStateChanged(ItemEvent ev) {
        gcChanged();
    }
    
    private void gcChanged() {
        GCWrapper wrap = (GCWrapper)gcSelection.getSelectedItem();
        //assert wrap != null;
        GraphicsConfiguration gc = wrap.getGC();
        //assert gc != null;
        //Image Caps
        ImageCapabilities imageCaps = gc.getImageCapabilities();
        imageAccelerated.setSelected(imageCaps.isAccelerated());
        imageTrueVolatile.setSelected(imageCaps.isTrueVolatile());
        // Buffer Caps
        BufferCapabilities bufferCaps = gc.getBufferCapabilities();
        flipping.setSelected(bufferCaps.isPageFlipping());
        flippingMethod.setText(getFlipText(bufferCaps.getFlipContents()));
        fullScreen.setSelected(bufferCaps.isFullScreenRequired());
        multiBuffer.setSelected(bufferCaps.isMultiBufferAvailable());
        // Front buffer caps
        imageCaps = bufferCaps.getFrontBufferCapabilities();
        fbAccelerated.setSelected(imageCaps.isAccelerated());
        fbTrueVolatile.setSelected(imageCaps.isTrueVolatile());
        imageCaps = bufferCaps.getFrontBufferCapabilities();
        // Back buffer caps
        imageCaps = bufferCaps.getBackBufferCapabilities();
        bbAccelerated.setSelected(imageCaps.isAccelerated());
        bbTrueVolatile.setSelected(imageCaps.isTrueVolatile());
    }
    
    private static String getFlipText(BufferCapabilities.FlipContents flip) {
        if (flip == null) {
            return "";
        } else if (flip == BufferCapabilities.FlipContents.UNDEFINED) {
            return "Method Unspecified";
        } else if (flip == BufferCapabilities.FlipContents.BACKGROUND) {
            return "Cleared to Background";
        } else if (flip == BufferCapabilities.FlipContents.PRIOR) {
            return "Previous Front Buffer";
        } else { // if (flip == BufferCapabilities.FlipContents.COPIED)
            return "Copied";
        }
    }
    
    public static void main(String[] args) {
        GraphicsEnvironment ge =
            GraphicsEnvironment.getLocalGraphicsEnvironment();
        GraphicsDevice[] devices = ge.getScreenDevices();
        for (int i = 0; i < devices.length; i++) {
            CapabilitiesTest tst = new CapabilitiesTest(devices[i]);
            tst.pack();
            tst.setVisible(true);
        }
    }
}

DisplayModeTest shows a Swing application that uses passive rendering. If full-screen exclusive mode is available, it will enter full-screen exclusive mode. If display mode changes are allowed, it allows you to switch between display modes.

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.table.*;

class DisplayModeModel extends DefaultTableModel {
    private DisplayMode[] modes;

    public DisplayModeModel(DisplayMode[] modes) {
        this.modes = modes;
    }

    public DisplayMode getDisplayMode(int r) {
        return modes[r];
    }

    public String getColumnName(int c) {
        return DisplayModeTest.COLUMN_NAMES[c];
    }

    public int getColumnCount() {
        return DisplayModeTest.COLUMN_WIDTHS.length;
    }

    public boolean isCellEditable(int r, int c) {
        return false;
    }

    public int getRowCount() {
        if (modes == null) {
            return 0;
        }
        return modes.length;
    }

    public Object getValueAt(int rowIndex, int colIndex) {
        DisplayMode dm = modes[rowIndex];
        switch (colIndex) {
            case DisplayModeTest.INDEX_WIDTH :
                return Integer.toString(dm.getWidth());
            case DisplayModeTest.INDEX_HEIGHT :
                return Integer.toString(dm.getHeight());
            case DisplayModeTest.INDEX_BITDEPTH : {
                int bitDepth = dm.getBitDepth();
                String ret;
                if (bitDepth == DisplayMode.BIT_DEPTH_MULTI) {
                    ret = "Multi";
                } else {
                    ret = Integer.toString(bitDepth);
                }
                return ret;
            }
            case DisplayModeTest.INDEX_REFRESHRATE : {
                int refreshRate = dm.getRefreshRate();
                String ret;
                if (refreshRate == DisplayMode.REFRESH_RATE_UNKNOWN) {
                    ret = "Unknown";
                } else {
                    ret = Integer.toString(refreshRate);
                }
                return ret;
            }
        }
        throw new ArrayIndexOutOfBoundsException("Invalid column value");
    }

}

public class DisplayModeTest extends JFrame implements ActionListener,
    ListSelectionListener {

    private boolean waiting = false;
    private GraphicsDevice device;
    private DisplayMode originalDM;
    private JButton exit = new JButton("Exit");
    private JButton changeDM = new JButton("Set Display");
    private JLabel currentDM = new JLabel();
    private JTable dmList = new JTable();
    private JScrollPane dmPane = new JScrollPane(dmList);
    private boolean isFullScreen = false;

    public static final int INDEX_WIDTH = 0;
    public static final int INDEX_HEIGHT = 1;
    public static final int INDEX_BITDEPTH = 2;
    public static final int INDEX_REFRESHRATE = 3;

    public static final int[] COLUMN_WIDTHS = new int[] {
        100, 100, 100, 100
    };
    public static final String[] COLUMN_NAMES = new String[] {
        "Width", "Height", "Bit Depth", "Refresh Rate"
    };

    public DisplayModeTest(GraphicsDevice device) {
        super(device.getDefaultConfiguration());
        this.device = device;
        setTitle("Display Mode Test");
        originalDM = device.getDisplayMode();
        setDMLabel(originalDM);
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        // Make sure a DM is always selected in the list
        exit.addActionListener(this);
        changeDM.addActionListener(this);
        changeDM.setEnabled(device.isDisplayChangeSupported());
    }

    public void actionPerformed(ActionEvent ev) {
        Object source = ev.getSource();
        if (source == exit) {
            device.setDisplayMode(originalDM);
            System.exit(0);
        } else { // if (source == changeDM)
            int index = dmList.getSelectionModel().getAnchorSelectionIndex();
            if (index >= 0) {
                DisplayModeModel model = (DisplayModeModel)dmList.getModel();
                DisplayMode dm = model.getDisplayMode(index);
                device.setDisplayMode(dm);
                setDMLabel(dm);
                setSize(new Dimension(dm.getWidth(), dm.getHeight()));
                validate();
            }
        }
    }

    public void valueChanged(ListSelectionEvent ev) {
        changeDM.setEnabled(device.isDisplayChangeSupported());
    }

    private void initComponents(Container c) {
        setContentPane(c);
        c.setLayout(new BorderLayout());
        // Current DM
        JPanel currentPanel = new JPanel(new FlowLayout(FlowLayout.CENTER));
        c.add(currentPanel, BorderLayout.NORTH);
        JLabel current = new JLabel("Current Display Mode : ");
        currentPanel.add(current);
        currentPanel.add(currentDM);
        // Display Modes
        JPanel modesPanel = new JPanel(new GridLayout(1, 2));
        c.add(modesPanel, BorderLayout.CENTER);
        // List of display modes
        for (int i = 0; i < COLUMN_WIDTHS.length; i++) {
            TableColumn col = new TableColumn(i, COLUMN_WIDTHS[i]);
            col.setIdentifier(COLUMN_NAMES[i]);
            col.setHeaderValue(COLUMN_NAMES[i]);
            dmList.addColumn(col);
        }
        dmList.getSelectionModel().setSelectionMode(
            ListSelectionModel.SINGLE_SELECTION);
        dmList.getSelectionModel().addListSelectionListener(this);
        modesPanel.add(dmPane);
        // Controls
        JPanel controlsPanelA = new JPanel(new BorderLayout());
        modesPanel.add(controlsPanelA);
        JPanel controlsPanelB = new JPanel(new GridLayout(2, 1));
        controlsPanelA.add(controlsPanelB, BorderLayout.NORTH);
        // Exit
        JPanel exitPanel = new JPanel(new FlowLayout(FlowLayout.CENTER));
        controlsPanelB.add(exitPanel);
        exitPanel.add(exit);
        // Change DM
        JPanel changeDMPanel = new JPanel(new FlowLayout(FlowLayout.CENTER));
        controlsPanelB.add(changeDMPanel);
        changeDMPanel.add(changeDM);
        controlsPanelA.add(new JPanel(), BorderLayout.CENTER);
    }

    public void setVisible(boolean isVis) {
        super.setVisible(isVis);
        if (isVis) {
            dmList.setModel(new DisplayModeModel(device.getDisplayModes()));
        }
    }

    public void setDMLabel(DisplayMode newMode) {
        int bitDepth = newMode.getBitDepth();
        int refreshRate = newMode.getRefreshRate();
        String bd, rr;
        if (bitDepth == DisplayMode.BIT_DEPTH_MULTI) {
            bd = "Multi";
        } else {
            bd = Integer.toString(bitDepth);
        }
        if (refreshRate == DisplayMode.REFRESH_RATE_UNKNOWN) {
            rr = "Unknown";
        } else {
            rr = Integer.toString(refreshRate);
        }
        currentDM.setText(
            COLUMN_NAMES[INDEX_WIDTH] + ": " + newMode.getWidth() + " "
            + COLUMN_NAMES[INDEX_HEIGHT] + ": " + newMode.getHeight() + " "
            + COLUMN_NAMES[INDEX_BITDEPTH] + ": " + bd + " "
            + COLUMN_NAMES[INDEX_REFRESHRATE] + ": " + rr
            );
    }

    public void begin() {
        isFullScreen = device.isFullScreenSupported();
        setUndecorated(isFullScreen);
        setResizable(!isFullScreen);
        if (isFullScreen) {
            // Full-screen mode
            device.setFullScreenWindow(this);
            validate();
        } else {
            // Windowed mode
            pack();
            setVisible(true);
        }
    }

    public static void main(String[] args) {
        GraphicsEnvironment env = GraphicsEnvironment.
            getLocalGraphicsEnvironment();
        GraphicsDevice[] devices = env.getScreenDevices();
        // REMIND : Multi-monitor full-screen mode not yet supported
        for (int i = 0; i < 1 /* devices.length */; i++) {
            DisplayModeTest test = new DisplayModeTest(devices[i]);
            test.initComponents(test.getContentPane());
            test.begin();
        }
    }
}

MultiBufferTest enters full-screen mode and uses multi-buffering through an active render loop.

import java.awt.*;
import java.awt.image.BufferStrategy;

public class MultiBufferTest {
    
    private static Color[] COLORS = new Color[] {
        Color.red, Color.blue, Color.green, Color.white, Color.black,
        Color.yellow, Color.gray, Color.cyan, Color.pink, Color.lightGray,
        Color.magenta, Color.orange, Color.darkGray };
    private static DisplayMode[] BEST_DISPLAY_MODES = new DisplayMode[] {
        new DisplayMode(640, 480, 32, 0),
        new DisplayMode(640, 480, 16, 0),
        new DisplayMode(640, 480, 8, 0)
    };
    
    Frame mainFrame;
    
    public MultiBufferTest(int numBuffers, GraphicsDevice device) {
        try {
            GraphicsConfiguration gc = device.getDefaultConfiguration();
            mainFrame = new Frame(gc);
            mainFrame.setUndecorated(true);
            mainFrame.setIgnoreRepaint(true);
            device.setFullScreenWindow(mainFrame);
            if (device.isDisplayChangeSupported()) {
                chooseBestDisplayMode(device);
            }
            Rectangle bounds = mainFrame.getBounds();
            mainFrame.createBufferStrategy(numBuffers);
            BufferStrategy bufferStrategy = mainFrame.getBufferStrategy();
            for (float lag = 2000.0f; lag > 0.00000006f; lag = lag / 1.33f) {
                for (int i = 0; i < numBuffers; i++) {
                    Graphics g = bufferStrategy.getDrawGraphics();
                    if (!bufferStrategy.contentsLost()) {
                        g.setColor(COLORS[i]);
                        g.fillRect(0,0,bounds.width, bounds.height);
                        bufferStrategy.show();
                        g.dispose();
                    }
                    try {
                        Thread.sleep((int)lag);
                    } catch (InterruptedException e) {}
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            device.setFullScreenWindow(null);
        }
    }
    
    private static DisplayMode getBestDisplayMode(GraphicsDevice device) {
        for (int x = 0; x < BEST_DISPLAY_MODES.length; x++) {
            DisplayMode[] modes = device.getDisplayModes();
            for (int i = 0; i < modes.length; i++) {
                if (modes[i].getWidth() == BEST_DISPLAY_MODES[x].getWidth()
                   && modes[i].getHeight() == BEST_DISPLAY_MODES[x].getHeight()
                   && modes[i].getBitDepth() == BEST_DISPLAY_MODES[x].getBitDepth()
                   ) {
                    return BEST_DISPLAY_MODES[x];
                }
            }
        }
        return null;
    }
    
    public static void chooseBestDisplayMode(GraphicsDevice device) {
        DisplayMode best = getBestDisplayMode(device);
        if (best != null) {
            device.setDisplayMode(best);
        }
    }
    
    public static void main(String[] args) {
        try {
            int numBuffers = 2;
            if (args != null && args.length > 0) {
                numBuffers = Integer.parseInt(args[0]);
                if (numBuffers < 2 || numBuffers > COLORS.length) {
                    System.err.println("Must specify between 2 and "
                        + COLORS.length + " buffers");
                    System.exit(1);
                }
            }
            GraphicsEnvironment env = GraphicsEnvironment.
                getLocalGraphicsEnvironment();
            GraphicsDevice device = env.getDefaultScreenDevice();
            MultiBufferTest test = new MultiBufferTest(numBuffers, device);
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.exit(0);
    }
}

«« Previous
Next »»