I'm experiencing an unexpected interaction between system events and the window refresh rate in simple Java2D applications on Linux/XWindows. It is best demonstrated with the small example below.
This program creates a small window in which a half-circle is displayed at different rotations. The graphics are updated at 60 frames per second in order to produce a flickering display. This is achieved through a BufferStrategy
, namely by calling its show
method.
However, I note that when I (a) move the mouse over the window so that the window receives mouse-over events or (b) hold down a key on the keyboard so that the window receives keyboard events, the flickering increases visibly.
As the rate at which BufferStrategy.show()
is called is not affected by these events, as can be seen by the print-outs on the console (they should constantly be at around 60 fps). However, the faster flickering indicates that the rate at which the display is actually updated, does indeed change.
It looks to me that actual, i.e., visible 60 frames per second are not achieved unless mouse or keyboard events are generated.
public class Test {
// pass the path to 'test.png' as command line parameter
public static void main(String[] args) throws Exception {
BufferedImage image = ImageIO.read(new File(args[0]));
// create window
JFrame frame = new JFrame();
Canvas canvas = new Canvas();
canvas.setPreferredSize(new Dimension(100, 100));
frame.getContentPane().add(canvas);
frame.pack();
frame.setVisible(true);
int fps = 0;
long nsPerFrame = 1000000000 / 60; // 60 = target fps
long showTime = System.nanoTime() + nsPerFrame;
long printTime = System.currentTimeMillis() + 1000;
for (int tick = 0; true; tick++) {
BufferStrategy bs = canvas.getBufferStrategy();
if (bs == null) {
canvas.createBufferStrategy(2);
continue;
}
// draw frame
Graphics g = bs.getDrawGraphics();
int framex = (tick % 4) * 64;
g.drawImage(image, 18, 18, 82, 82, framex, 0, framex+64, 64, null);
g.dispose();
bs.show();
// enforce frame rate
long sleepTime = showTime - System.nanoTime();
if (sleepTime > 0) {
long sleepMillis = sleepTime / 1000000;
int sleepNanos = (int) (sleepTime - (sleepMillis * 1000000));
try {
Thread.sleep(sleepMillis, sleepNanos);
} catch (InterruptedException ie) {
/* ignore */
}
}
showTime += nsPerFrame;
// print frame rate achieved
fps++;
if (System.currentTimeMillis() > printTime) {
System.out.println("fps: " + fps);
fps = 0;
printTime += 1000;
}
}
}
}
A sample image to use with this program (the path to which must be passed as a command line argument) is this:
So my (2-part) question is:
Why is this effect happening? How can I reach actual 60 fps?
(Bonus question for commenters: do you experience the same effect also under other operating systems?)
Q: Why is this effect happening?
Short answer: One must call canvas.getToolkit().sync()
or Toolkit.getDefaultToolkit().sync()
after BufferStrategy#show
Long answer: Without an explicit Toolkit#sync
pixels do not make it to the screen until X11 command buffer is full or another event, such as mouse move, forces a flush.
Quote from http://www.java-gaming.org/index.php/topic,15000.
... even if we (Java2D) issue our rendering commands immediately the video driver may chose not to execute them right away. Classic example is X11 - try timing a loop of Graphics.fillRects - see how many you can issue in a couple of seconds. Without toolkit.sync() (which in case of X11 pipeline does XFlush()) after each call or at the end of the loop what you actually measure is how fast Java2D can call X11 lib's XFillRect method, which just batches up those calls until it's command buffer is full, only then they're sent to the X server for execution.
Q: How can I reach actual 60 fps?
A: Flush the command buffer and consider turning on OpenGL acceleration for Java2D with System.setProperty("sun.java2d.opengl", "true");
in code or -Dsun.java2d.opengl=true
command line option.
See also:
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With