我在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?
(评论者的加分问题:您在其他操作系统下也会遇到同样的效果吗?)
答案 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加速。
另见: