Friday, June 22, 2007

Double Buffering and Timed Rendering



A Backbuffer

First thing you need before creating a flicker free environment is to have a buffer to draw to. Basically the concept is, draw everything to an offscreen image the same size as the window you are going to be displaying it on. Once the drawing of a single frame is complete then swap or in this case draw the backbuffer onto the screen. And of course you want to do this every 16-33 milliseconds or between (30-60 fps, frames per second) So we need a backbuffer to use for drawing. You want to use a BufferedImage which has a method to get the Graphics object which is what you use to draw to any component or Image in Java.


Timer and TimerTask's

Ok! Now that we have a backbuffer we need a way of timing each frame and rendering (drawing) that frame onto the window. So we need a timer something that will execute after a specified period of milliseconds. There is a Timer class already in the Java API java.util.Timer. This class allows you to schedule TimerTask's which are simply a separate thread with a run method that you need to overload. A typical rendering loop is something like this:
// create the Timer and schedule it with the timer.
TimerTask renderTask = new TimerTask() {
   public void run() {

      // check to see if the backbuffer needs to be
      // initialized. this will help when window is resized.
      if (backbuffer == null wndResized) {
         // create a BufferedImage the size of the JFrame.
         backbuffer = new BufferedImage(getWidth(), getHeight(),
         BufferedImage.TYPE_INT_ARGB);
         wndResized = false;
      }

      // get the Graphics object from the backbuffer.
      Graphics g = backbuffer.getGraphics();

      // TODO: draw things to the screen.
      // example
      g.drawString(DoubleBuffering.DISPLAY_STRING, 100, 100);

      // now we do our page flipping to display the image on the
      // window. So we need the windows Graphics object.
      getGraphics().drawImage(backbuffer, 0, 0, null);

      // after we have drawn what we need on the screen we can clear
      // the backbuffer.
      g.clearRect(0, 0, backbuffer.getWidth(),
      backbuffer.getHeight());
   }
};
Now scheduling the TimerTask is very easy and is done like so:
// start rendering at 60 fps.
timer.schedule(renderTask, 100, 16);
That would be the guts of things you just extends this as you need and make your game.

Resizing Windows

What to do with resizing windows. Whenever a window is resized you need to recreate the backbuffer since the width and height of the image has changed. This really is not a problem if you are in fullscreen mode or you lock the windows resizable property but still in case you really need to do it here is a way to determine if the window has been resized.
// add a listener to detect when the window has been resized by the
// user.
addComponentListener(
new ComponentAdapter() {
public void componentResized(ComponentEvent e) {
wndResized = true; // resize the backbuffer.
}
}
);

That’s about it here is the single file code snippet for you to use:

2 comments:

Anonymous said...

i tried this ex. but, i got a unexpected result, CPU used 100% for this, why? worse than if i use repaint() and no buffer

MorbidMorvick said...

What system and java version are you running. I haven't experienced any problems running it and I am still on a 2.66ghz Pentium 4 win xp and tested with java version 1.5, 1.6, and 1.7. In newer versions of Java swing does buffering by default which works.

The timer in the example should only run every 16 milliseconds (~60 frames per second) you can try doubling it to see if it makes a difference.

See Response for some things that might make a difference.

Post a Comment