Java2D:XWindows事件与帧速率之间的交互

时间:2016-11-17 17:43:10

标签: java x11 graphics2d frame-rate game-development

我在Linux / XWindows上的简单Java2D应用程序中遇到系统事件和窗口刷新率之间的意外交互。最好用下面的小例子来证明。

该程序创建一个小窗口,其中以不同的旋转显示半圆。图形以每秒60帧的速度更新,以产生闪烁的显示。这是通过BufferStrategy实现的,即通过调用其show方法。

但是,我注意到当我(a)将鼠标移到窗口上以便窗口接收鼠标悬停事件或(b)按住键盘上的键以使窗口接收键盘事件时,闪烁增加可见。

由于调用BufferStrategy.show()的速率不受这些事件的影响,控制台上的打印输出可以看出(它们应该始终保持在60 fps左右)。但是,更快的闪烁表示显示实际更新的速率确实发生了变化。

在我看来,除非生成鼠标或键盘事件,否则无法实现实际,即每秒可见60帧。

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;
            }
        }
    }
}

与此程序一起使用的示例图像(必须作为命令行参数传递的路径)是:

enter image description here

所以我的(两部分)问题是:

为什么会发生这种影响?如何才能达到实际 60 fps?

(评论者的加分问题:您在其他操作系统下也会遇到同样的效果吗?)

1 个答案:

答案 0 :(得分:6)

:为什么会发生这种影响?

简短回答:必须在canvas.getToolkit().sync()

之后致电Toolkit.getDefaultToolkit().sync()BufferStrategy#show

长答案:如果没有明确的Toolkit#sync像素,则在X11命令缓冲区已满或其他事件(例如鼠标移动)强制刷新之前,不会将其显示在屏幕上。

引用来自 http://www.java-gaming.org/index.php/topic,15000

  

...即使我们(Java2D)立即发出我们的渲染命令视频   司机可能会选择不立即执行它们。经典的例子是X11 - 尝试计算Graphics.fillRects的循环 - 看看你可以发出多少   在几秒钟内。没有toolkit.sync()(在X11管道的情况下执行XFlush())   在每次调用之后或在循环结束时,您实际测量的是Java2D的速度   调用X11 lib的XFillRect方法,它只是批量调用这些调用直到它的命令   缓冲区已满,只有这样它们才会被发送到X服务器执行。

:如何达到实际的60 fps?

A :刷新命令缓冲区并考虑在代码或System.setProperty("sun.java2d.opengl", "true");命令行选项中使用-Dsun.java2d.opengl=true打开Java2D的OpenGL加速。

另见: