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

Posted

技术标签:

【中文标题】Java2D:XWindows 事件和帧率之间的交互【英文标题】:Java2D: interaction between XWindows events and frame rate 【发布时间】:2017-04-01 09:26:02 【问题描述】:

我在 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;
            
        
    

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

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

为什么会出现这种效果?我怎样才能达到实际 60 fps?

(评论者的额外问题:您在其他操作系统下是否也遇到相同的效果?)

【问题讨论】:

我尝试在 Windows 10 上运行的机器上多次运行您的代码。有时我观察到闪烁变慢了。你有没有遇到过这个问题?您是否尝试过保护您的代码免受帧丢失?? @sbaitmangalkar 感谢您试用代码。帧丢失是我目前没有考虑的一个单独问题。但要明确一点:当您运行程序时,您不会在将鼠标光标移到窗口上时立即看到动画加速? 缓冲区策略中的缓冲区大多是VolatileImage,这可能会根据您所说的操作系统因素丢失内容,从而导致被提及的行为。因此,通过限制丢失缓冲区的循环条件可能会解决您的问题。同样,我不确定这是否真的是代码行为背后的原因。 在我的测试运行中,内容从未丢失,并且所描述的行为仍然可见。 在 OSX El Capitan 中工作。可能是因为它不受 X11 命令缓冲区批处理的影响。 【参考方案1】:

:为什么会出现这种效果?

简答:必须在BufferStrategy#show 之后调用canvas.getToolkit().sync()Toolkit.getDefaultToolkit().sync()

长答案:如果没有明确的 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 加速。

另见:

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

【讨论】:

以上是关于Java2D:XWindows 事件和帧率之间的交互的主要内容,如果未能解决你的问题,请参考以下文章

Directshow:如何更改视频的大小和帧率?

Sensor曝光和帧率基础知识

Android使用camera2复制内置视频录制质量和帧率

SpriteKit 游戏中的高 CPU 和帧率下降

使用 MediaCodec 和 MediaMuxer 录制视频,但码率和帧率不正确

Unity--时间和帧率