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

Tho*_*mas 12 java x11 frame-rate game-development graphics2d

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

该程序创建一个小窗口,其中以不同的旋转显示半圆.图形以每秒60帧的速度更新,以产生闪烁的显示.这是通过a 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;
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

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

在此输入图像描述

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

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

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

ant*_*tix 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?

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

也可以看看: