drawImage wierdness

I am trying to control frame rate by measuring the time taken to drawImage, but I’m getting wierd results. In the code below, period = 0 makes the animation free run. For me, each draw/render cycle takes around 13ms and typical output is:

Rendering took 13.254757ms, Drawing finished (T/F) = true, about to sleep for -13ms

If I try to impose a frame rate (e.g. 20ms - period = 20000000 in the code), suddenly the draw/render cycle seems to take a lot less time:

Rendering took 0.139124ms, Drawing finished (T/F) = true, about to sleep for 14ms

It’s pretty obvious the draw/render cycle is not taking this little time because the code runs slower visually. I’ve tried to find out whether the image has finished drawing by interpreting the boolean returned by drawImage and by using ImageObserver. To me, it looks like drawImage has finished by the time it returns (contrary to the JavaDoc which states that it returns immediately?).

Thread.sleep was used for example - I’ve tried all sorts of code in place of it and problem is not there (simple empty loop causes the same effect). Any clues as to what is going on? This is causing a major headache…

Using 1.5.0_02-b09

The image I used was pinched from:

http://users.design.ucla.edu/~badgerow/Photos/SnowSummit/03-5-Landscape.jpg


import java.awt.DisplayMode;
import java.awt.Frame;
import java.awt.Graphics;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.Image;
import java.awt.Rectangle;
import java.awt.image.BufferStrategy;
import java.awt.image.ImageObserver;
import java.io.File;

import javax.imageio.ImageIO;

public class MultiBufferTest {

	private static Image image;

	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) {

		long startTime;
		long endTime;
		long renderTime;
		long period = 0;
		//long period = 20000000; // Nanoseconds
		long sleepTime = 0;
		boolean drawFinished;
		MyObserver imageObserver = new MyObserver();

		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 (int j = 0; j < 100; j++) {
				for (int i = 0; i < numBuffers; i++) {
					Graphics g = bufferStrategy.getDrawGraphics();
					if (!bufferStrategy.contentsLost()) {
						startTime = System.nanoTime();
						drawFinished = g.drawImage(image, 0, 0, bounds.width,
								bounds.height, j, j, bounds.width + j,
								bounds.height + j, imageObserver);
						bufferStrategy.show();
						g.dispose();
						endTime = System.nanoTime();
						renderTime = endTime - startTime;
						sleepTime = period - renderTime;
						sleepTime /= 1000000;
						System.out.println("Rendering took " + renderTime / 1E6
								+ "ms,\tDrawing finished (T/F) = "
								+ drawFinished + ",\tabout to sleep for "
								+ sleepTime + "ms");
					}
					if (sleepTime > 0) {
						try {
							Thread.sleep((int) sleepTime);
						} 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 > 5) {
					System.err.println("Must specify between 2 and 5 buffers");
					System.exit(1);
				}
			}
			GraphicsEnvironment env = GraphicsEnvironment
					.getLocalGraphicsEnvironment();
			GraphicsDevice device = env.getDefaultScreenDevice();		
	                // Any image bigger than 740x580
			image = ImageIO.read(new File("03-5-Landscape.jpg"));			
			MultiBufferTest test = new MultiBufferTest(numBuffers, device);
		} catch (Exception e) {
			e.printStackTrace();
		}
		System.exit(0);
	}
	
	class MyObserver implements ImageObserver {

		public boolean imageUpdate(Image img, int infoflags, int x, int y, int width, int height) {
			System.out.println("AN IMAGE UPDATE OCCURRED");
			return false;
		}
		
	}
}

You might be interested in reading this:

http://today.java.net/pub/a/today/2005/02/15/timing.html

Cheers,
Mikael Grev

Thanks - I’ve read that. The posted code was only meant to illustrate the problem I have of unpredictable drawImage timing - which I still need for what I’m doing: I know that it is a poor example of animation. Any clues as to how I can reliably get drawImage timed???

You might want to do Toolkit.getDefaultToolkit().sync() to make sure we flush the pipeline.

Otherwise rendering commands get queued by the driver/directx or X11, and all you’re measuring is our speed
to issue those drawing requests.

Thanks,
Dmitri

Thanks for the tip. I put in a sync() in several places, but especially after g.drawImage and bufferStrategy.show().

As so often, it seems the end is in sight but not quite: I still get inconsistent results. The code reports that the render operation takes various times, starting with the true value (~13ms) to a much smaller value. I can’t get my head around why a delay in the render loop would cause such behaviour. I could understand it if I missed a screen refresh due to the delay and the time taken, say, doubles. But why the timing shows a reduction I can’t understand…

Is there other way to force a synchronization?

Thanks.

Rendering took 13.343036ms, Drawing finished (T/F) = true, about to sleep for 6ms
Rendering took 11.509005ms, Drawing finished (T/F) = true, about to sleep for 8ms
Rendering took 9.298668ms, Drawing finished (T/F) = true, about to sleep for 10ms
Rendering took 7.047264ms, Drawing finished (T/F) = true, about to sleep for 12ms
Rendering took 4.756471ms, Drawing finished (T/F) = true, about to sleep for 15ms
Rendering took 1.920356ms, Drawing finished (T/F) = true, about to sleep for 18ms
Rendering took 13.048586ms, Drawing finished (T/F) = true, about to sleep for 6ms
Rendering took 11.236065ms, Drawing finished (T/F) = true, about to sleep for 8ms
Rendering took 8.930744ms, Drawing finished (T/F) = true, about to sleep for 11ms
Rendering took 6.631849ms, Drawing finished (T/F) = true, about to sleep for 13ms
Rendering took 4.352788ms, Drawing finished (T/F) = true, about to sleep for 15ms
Rendering took 1.975949ms, Drawing finished (T/F) = true, about to sleep for 18ms