Java2D:XWindows 事件和帧率之间的交互
Java2D: interaction between XWindows events and frame rate
我在 Linux/XWindows 上的简单 Java2D 应用程序中遇到系统事件与 window 刷新率之间的意外交互。最好用下面的小例子来证明。
这个程序创建了一个小的 window,其中一个半圆以不同的旋转显示。图形以每秒 60 帧的速度更新,以产生闪烁的显示。这是通过 BufferStrategy
实现的,即调用它的 show
方法。
但是,我注意到当我 (a) 将鼠标移到 window 上以便 window 接收鼠标悬停事件或 (b) 按住键盘上的某个键时window 接收到键盘事件,闪烁明显增加。
因为调用 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?
(评论者的红利问题:你在其他操作系统下也遇到过同样的效果吗?)
问:为什么会出现这种效果?
简答:必须在BufferStrategy#show
之后调用canvas.getToolkit().sync()
或Toolkit.getDefaultToolkit().sync()
长答案:没有明确的 Toolkit#sync
像素不会出现在屏幕上,直到 X11 命令缓冲区已满或其他事件(例如鼠标移动)强制同花顺。
引用自
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.
问:如何才能达到实际的 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
我在 Linux/XWindows 上的简单 Java2D 应用程序中遇到系统事件与 window 刷新率之间的意外交互。最好用下面的小例子来证明。
这个程序创建了一个小的 window,其中一个半圆以不同的旋转显示。图形以每秒 60 帧的速度更新,以产生闪烁的显示。这是通过 BufferStrategy
实现的,即调用它的 show
方法。
但是,我注意到当我 (a) 将鼠标移到 window 上以便 window 接收鼠标悬停事件或 (b) 按住键盘上的某个键时window 接收到键盘事件,闪烁明显增加。
因为调用 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?
(评论者的红利问题:你在其他操作系统下也遇到过同样的效果吗?)
问:为什么会出现这种效果?
简答:必须在BufferStrategy#show
canvas.getToolkit().sync()
或Toolkit.getDefaultToolkit().sync()
长答案:没有明确的 Toolkit#sync
像素不会出现在屏幕上,直到 X11 命令缓冲区已满或其他事件(例如鼠标移动)强制同花顺。
引用自 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.
问:如何才能达到实际的 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