By popular demand(!) I’ve copied the source code for my test program below.
The test comprises two elements: a background that is drawn by repeating an opaque tile image so that it fills the window, and a translucent sprite image that is drawn multiple times over that background.
This is supposed to be a cut-down version of a game like http://javagamesfactory.org/views/view-game?name=Starbugs.
The tile image is modified once per frame so that the background is animated. I do this by editing the data buffer (Raster) of the BufferedImage object, which is an array of ints. This is messier than calling setRGB() repeatedly, but I’ve found it to be faster. The drawback though is that the image cannot then be accelerated (see Linuxhippy’s reply above). I’m trying to get around this by copying the tile image onto a VolatileImage, and then blitting that repeatedly to the screen.
The main() function sets whether the background is drawn using the VolatileImage or the (unaccelerated) BufferedImage, and also sets some java2d parameters. Trying out different combinations, I get the following frames-per-second results.
- UseVolatileImage=false, no java2d parameters : 40 fps
- UseVolatileImage=true, no java2d parameters : 10 fps
- UseVolatileImage=false, ddforcevram=true, translaccel=true : 37 fps
- UseVolatileImage=true, ddforcevram=true, translaccel=true : 40 fps
- UseVolatileImage=false, opengl=true : 47 fps
- UseVolatileImage=true, opengl=true : 47 fps
As I said before, I’m testing this on a slow computer!
When I’ve tried it on more powerful machines I find that the combination of VolatileImage + ddforcevram gives a much more significant boost to the frame rate. (I hadn’t tried the “sun.java2d.opengl” parameter until now. It works rather well! How long until the OpenGL pipeline is enabled by default?)
Can anyone see anything I should be doing to the code to improve the frame rate? (I’ve tried playing with
Image.setAccelerationPriority() but that hasn’t helped.)
Does anyone see anything wrong with my choice of UseVolatileImage + ddforcevram + translaccel as the default set-up?
Cheers,
Simon
package Untitled;
import java.awt.*;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.geom.*;
import java.awt.image.*;
import java.util.*;
import javax.swing.JFrame;
public class TestWindow extends JFrame implements Runnable {
private static boolean mUseVolatileImage;
public static void main(String args[]) {
mUseVolatileImage = true;
System.setProperty("sun.java2d.ddforcevram","true");
System.setProperty("sun.java2d.translaccel","true");
//System.setProperty("sun.java2d.opengl","true");
System.setProperty("sun.java2d.trace","count");
new TestWindow();
}
private BufferedImage mSprite = null;
private BufferedImage mTile = null;
private VolatileImage mVTile = null;
private Thread mMainLoop = null;
private BufferStrategy mBufferStrategy = null;
private Random mRandom = null;
public TestWindow() {
reportAcceleratedMemory();
Canvas canvas = new Canvas();
Dimension canvasDim = new Dimension(512, 384);
canvas.setSize(canvasDim);
canvas.setPreferredSize(canvasDim);
canvas.setMinimumSize(canvasDim);
canvas.setMaximumSize(canvasDim);
getContentPane().removeAll();
getContentPane().setLayout(new BorderLayout());
getContentPane().add(canvas, BorderLayout.CENTER);
setLocation(64, 64);
setIgnoreRepaint(true);
setResizable(false);
pack();
addWindowListener(
new WindowAdapter() {
public void windowClosing(WindowEvent e) { exit(); }
public void windowDeiconified(WindowEvent e) { start(); }
public void windowIconified(WindowEvent e) { stop(); }
}
);
validate();
setVisible(true);
canvas.createBufferStrategy(2);
mBufferStrategy = canvas.getBufferStrategy();
mRandom = new Random();
GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
GraphicsDevice gd = ge.getDefaultScreenDevice();
GraphicsConfiguration gc = gd.getDefaultConfiguration();
prepareImages(gc);
start();
}
public void start() {
if ( mMainLoop == null ) {
mMainLoop = new Thread(this);
mMainLoop.start();
}
}
public void stop() {
if ( mMainLoop != null ) mMainLoop.interrupt();
mMainLoop = null;
}
public void exit() {
reportAcceleratedMemory();
stop();
System.exit(0);
}
private void reportAcceleratedMemory() {
GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
GraphicsDevice gd = ge.getDefaultScreenDevice();
int am = gd.getAvailableAcceleratedMemory();
System.out.println("accelerated memory: " + (am/1024) + "K");
}
private void prepareImages(GraphicsConfiguration gc) {
// prepare sprite image
mSprite = gc.createCompatibleImage(16, 16, Transparency.TRANSLUCENT);
Graphics2D g2 = mSprite.createGraphics();
g2.setBackground(new Color(0,0,0,0));
g2.clearRect(0, 0, 16, 16);
g2.setColor(Color.MAGENTA);
g2.fillOval(1, 1, 15, 15);
g2.dispose();
// prepare background tile image
mTile = new BufferedImage(64, 64, BufferedImage.TYPE_INT_RGB);
updateTileImage();
if ( mUseVolatileImage ) {
mVTile = gc.createCompatibleVolatileImage(64, 64, Transparency.OPAQUE);
refreshVolatileImage();
}
}
private void updateTileImage() {
WritableRaster raster = mTile.getRaster();
int pixels[] = ((DataBufferInt)(raster.getDataBuffer())).getData();
for ( int i = 0 ; i < 64*64 ; i++ ) pixels[i] = mRandom.nextInt(256);
}
private void refreshVolatileImage() {
Graphics2D g2 = mVTile.createGraphics();
g2.drawImage(mTile, 0, 0, null);
g2.dispose();
}
public void run() {
long prevNanos = System.nanoTime();
int frameCount = 0;
while ( mMainLoop == Thread.currentThread() ) {
drawGameScreen();
long newNanos = System.nanoTime();
frameCount++;
if ( newNanos > prevNanos + 3e9 ) {
System.out.println("fps: " + ((frameCount*1e9f)/(newNanos-prevNanos)));
prevNanos = newNanos;
frameCount = 0;
}
Thread.yield();
}
mMainLoop = null;
}
private void drawGameScreen() {
Graphics2D g2 = (Graphics2D)mBufferStrategy.getDrawGraphics();
// draw background (repeated tiles)
updateTileImage();
if ( mUseVolatileImage ) {
refreshVolatileImage();
for ( int j = 0 ; j < 6 ; j++ ) {
for ( int i = 0 ; i < 8 ; i++ ) {
do {
if ( mVTile.contentsLost() ) refreshVolatileImage();
g2.drawImage(mVTile, 64*i, 64*j, null);
} while ( mVTile.contentsLost() );
}
}
} else {
for ( int j = 0 ; j < 6 ; j++ ) {
for ( int i = 0 ; i < 8 ; i++ ) {
g2.drawImage(mTile, 64*i, 64*j, null);
}
}
}
// draw lots of sprites (translucent images)
for ( int j = 0 ; j < 24 ; j++ ) {
for ( int i = 0 ; i < 32 ; i++ ) {
if ( (i+j)%2 == 0 ) {
g2.drawImage(mSprite, 16*i + mRandom.nextInt(7)-3,
16*j + mRandom.nextInt(7)-3, null);
}
}
}
g2.dispose();
Toolkit.getDefaultToolkit().sync();
if ( !mBufferStrategy.contentsLost() ) mBufferStrategy.show();
}
}