Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java2D: interaction between XWindows events and frame rate

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:

enter image description here

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

like image 218
Thomas Avatar asked Nov 17 '16 17:11

Thomas


1 Answers

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:

  • Java animation stutters when not moving mouse cursor
  • http://www.java-gaming.org/index.php/topic,15000.
  • Drawing in Java using Canvas
  • http://www.oracle.com/technetwork/articles/java/mixing-components-433992.html
like image 152
anttix Avatar answered Nov 03 '22 16:11

anttix