Thursday, October 14, 2010

How to use BufferStrategy in Java

One of the things I didn't know when starting Java programming was the state of the hardware acceleration support. Java does provide hardware acceleration. In earlier version the double buffering was a bit buggy but since jre6 it works very well in swing. It also uses DirectX and Opengl under the hood which you can enable/disable. Hardware accelerated drawing is enabled by default on most systems where it is properly installed. To take advantage of this hardware acceleration you want to use a java.awt.Canvas component. You can let swing do the double buffering which entails simply repainting the screen and not worrying about using the BufferStrategy class. It seems to perform well, so you should test both methods out and see which works best for you. In this snippet I will not mix swing and awt so I use a Frame instead of a JFrame. I noticed from an early access release of jdk7 that if you use a Canvas component inside a JFrame nothing will draw, unlike jre6 and below where it doesn't present an issue. I do not know whether this is intentional to try and keep native resources and lightweight resource from conflicting or some other such implementation detail but it did cause me a headache for a little while.

VSync Improving Visuals or Triple Buffering
This is a minor annoyance when running in windowed mode for a game. Occasionally the display and the drawing of the window will become out of sync and cause some tearing or jittering of the constantly refreshing window. It happens due to not being able to refresh the window contents at the exact same rate the display is actually refreshing the entire screen. This isn't too noticeable with such high refresh rates on monitors, but just in case you need to be able to do this, there is a function. I didn't really know about it till recently, it's in the java.awt.Toolkit called sync(). You can also create a buffer strategy with 3 buffers, or triple buffering, which is known to almost eliminate the need for the sync so that your program isn't in the middle of drawing to the buffer while the display is drawing from the buffer.
EDIT: Use java.awt.Toolkit called sync() to update the display buffer (whatever that really is) to be the most current drawing surface in the buffer strategy. Should help ensure the user sees the latest and greatest from the application.

Other Useful Options
A number of different options are also available as System Properties for java2d. For instance the trace property which tells you how long and specifically which drawing functions are called. This will let you know whether the hardware acceleration drawing layer in Java is being called at all.
java -Dsun.java2d.trace=log
Setting System Properties in the code
A nice thing to be able to do is to use a configuration file loaded by your program to save/load these types of graphics options or other kinds of options that your program may allow the user to change but are used by the Java API before the application starts. I like to use static initialization blocks for this. So I will have something like the following in my main class.
// performance settings.

static {
    System.setProperty("sun.java2d.transaccel", "True");
    // System.setProperty("sun.java2d.trace", "timestamp,log,count");
    // System.setProperty("sun.java2d.opengl", "True");
    System.setProperty("sun.java2d.d3d", "True");
    System.setProperty("sun.java2d.ddforcevram", "True");

You can set it up however you like and even get complex with it, loading the settings from a config file like I mentioned above, or popup a nice little window with selectable options for d3d versus opengl. The trace line specifies the format of all output from java2d as described in the system properties link.

Using BufferStrategy
Use the createBufferStrategy(int numBuffers) in the canvas component only after the frame has been created and displayed. You can control how many buffers to use for rendering, the most I'd suggest would be 3 any more than that is overkill, imho. Creation of the strategy may cause a java.lang.IllegalStateException to be thrown if the canvas does not have a parent who is being displayed. Meaning you need to add the Canvas component to a Container and make sure that container is visible.
// create a strategy that uses two buffers, or is double buffered.

  // get a reference to the strategy object, for use in our render method
  // this isn't necessary but it eliminates a call during rendering.
  strategy = this.getBufferStrategy();

To use the BufferStrategy for rendering you want to use the strategy's getDrawGraphics and show methods. The getDrawGraphics creates a Graphics2D object that will draw to one of the buffers, if you are using 2 or more buffers this will be an offscreen buffer, allowing you to draw everything completely before displaying it which eliminates flickering and allows you to clear the buffer without the user noticing, and being able to redraw everything. Clearing and redrawing is essential for animation as you want to redraw the entire screen when objects move or change.

Example drawing code from the snippet:
// the back buffer graphics object
  Graphics2D bkG = (Graphics2D) strategy.getDrawGraphics();  
  // clear the backbuffer, this could be substituted for a background
  // image or a tiled background.
  bkG.fillRect(0, 0, getWidth(), getHeight());

  // TODO: Draw your game world, or scene or anything else here.

  // Rectangle2D is a shape subclass, and the graphics object can render
  // it, makes things a little easier.

  // properly dispose of the backbuffer graphics object. Release resources
  // and cleanup.

  // flip/draw the backbuffer to the canvas component.;

  // synchronize with the display refresh rate.

Precision and high frame rates
To make a good game you really do not need more than 60 frames per second. There is a lot of discussion about the subject and many say that the human eye will always be able to see screen refreshes. I think that it has nothing to do with the frame rate as much as with the movement and update timing. As long as the animation in your game is smooth without noticeable and often glitches then it will still be professional. You can always try and aim for a higher frame rate I think I have seen suggestions about around 100fps and I don't know why it's that number, jumping from either 30 to 60 and then all the way to 100. That's 1 frame every 10 milliseconds. While you can do a lot in 10 milliseconds there is a lot you can't do, so find what works best for your game and try and keep the amount objects moved every frame to change at a steady rate relative to their speed. So if an object is moving really fast across the screen small fluctuations in the rate of change in position every frame will cause jittery and jerky movement.

For this snippet I will not show the particulars of creating smooth movement that is for later snippets, where I will go through different kinds of movement. So at long last here it is:


Anonymous said...

didnt know you could fix the refresh issue, my programs have been plagued with screen tearing, cheers

Anonymous said...

I just tried to use BufferStrategy and Toolkit.sync() however framerates are in the many thousands instead of my monitor's 60Hz. if sync() does what it sounds like it should do it should automatically limit my framerate to my monitor's (you artificially cap framerate with the Timer). Mine runs in windowed mode, and BufferStrategy reports false for isFullScreenRequired() and true of isPageFlipping()... seems like a broken promise to me!

MorbidMorvick said...

Good catch indeed it is misleading because I too mistook the Toolkit.sync() function for a vertical sync feature. It is not and in no way does it force a wait condition on any thread that calls it though I'd have to see the underlining implementation to be sure of all that it is doing but as you have also noticed it does not impede the framerate.

What it does do is make sure that the display buffer whereever it is is update to date with whats in the currently showing buffer in the buffer strategy. Thus to help avoid tearing or delays in viewing.

Though tearing isn't much of an issue anymore and only happens when the display is in the middle of a draw to the screen and the buffer changes at the same time. Thus why vsync was used in earlier days with crt monitors. I have never noticed tearing on lcd monitors not saying it doesn't happen though.

isFullRequired being false and isPageFlipping true means in windowed mode you are able to use multi display buffers which should reduce the chances of tearing.

Some resources on vsync

Are you experiencing tearing or hoping that this sync function would eliminate a potential issue on some machines?

Anonymous said...

Excellent explanation... except, I am still getting a java.lang.IllegalStateException. I don't understand how to fix this based on your explanation :( .

MorbidMorvick said...

Let me try to expand on the explanation of why you get an IllegalStateException when creating the BufferStrategy. The canvas has to have access to native resources in order to create the strategy. To do that it has to be added to a Frame and that frame has to have isDisplayable equal to true. I've found that if you try to create the strategy in the constructor it will fail because you need to create the canvas, add it to a frame, make sure the frame created it's native resources before you can create the buffers since their linked to native resources which make sense since you want GPU resources to be used.

So depending on where and when you are trying to create the strategy you could get the exception.

One final note that I know I need to mention in more detail somewhere is that in the example I have opengl turned on by default for cross-platform reasons. I know there are issues with some NVIDIA cards on Windows in Java but I don't remember it causing an IllegalStateException to be thrown. With this snippet I have seen exceptions thrown intermittently upon closing the application.

P.S. If you are still having trouble getting it to work just provide a little bit more detail whether the snippet here works, stack trace, any changes or things you are trying to do, I could probably get it to work if given a failing example.

Del Berry said...

Posted a long thank you, had to sign into google. Lost post....not typing all that again...anyway thank you.

Del Berry said...
This comment has been removed by the author.

Post a Comment