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?
(评论者的加分问题:你在其他操作系统下也会遇到同样的效果吗?)
问:为什么会发生这种影响?
答案很简单:你必须调用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加速.
也可以看看: