级别: 初级 John Zukowski (jaz@zukowski.net), 总裁, JZ Ventures, Inc
2003 年 7 月 06 日 游戏开发人员和计算机速度发烧友这类人会喜爱 Merlin 新的全屏幕独占模式(Fullscreen Exclusive Mode,FEM)API,通过它可以在完全控制图形显示的情况下直接对显示内存进行写操作。在本篇最新的“Merlin 的魔力”专栏文章中,Java 编程专家 John Zukowski 将介绍这个新的 Merlin 功能部件的强大功能。请在附带的
论坛中与作者和其他读者分享您关于本文的心得。(您也可以单击文章顶部或底部的“讨论”来访问该论坛)。
您是否喜欢让程序搞一些恶作剧,让别人感到不舒服?如果您回答“是”,那么这个月的技巧文章一定对您的胃口。
使用 J2SE 1.4,您的 Java 程序现在可以更改视频方式并获得对屏幕的绝对控制。
您不必让别人随心所欲地玩电脑;您差不多可以拥有整个控制权。
感谢新的全屏幕独占模式(FEM)API 为我们提供了这个无与伦比的强大功能。
即使您回答“不”,不想以惹恼他人来取乐,您也将发现 FEM API 提供了许多帮助。通过直接对显存进行写操作,FEM API 提供了对显示的完全控制 ― 这对于游戏开发来说十分理想,
虽然还有许多其它应用。
例如,一些程序只有用特定大小的屏幕看上去才更好,并且才能更好地工作。
请继续读下去,以发掘您内心有关控制方面的奇思怪想。
更改显示方式
让我们先从研究 FEM API 的
java.awt.DislayMode 类开始,该类包装了特定显示方式的屏幕大小和刷新频率。受支持的方式特定于系统的硬件支持。
要找出特定系统的受支持方式,请查看
GraphicsEnvironment 。通过该环境,您可以获得缺省屏幕设备
GraphicDevice ,通过该屏幕设备可以获得显示方式,如清单 1 所示:
清单 1. 查找显示方式
GraphicsEnvironment graphicsEnvironment =
GraphicsEnvironment.getLocalGraphicsEnvironment();
GraphicsDevice graphicsDevice =
graphicsEnvironment.getDefaultScreenDevice();
DisplayMode displayModes[] =
graphicsDevice.getDisplayModes();
|
还可以使用
getDisplayMode() 方法获得当前的显示模式,如清单 2 所示:
清单 2. 获得当前的显示方式
DisplayMode originalDisplayMode =
graphicsDevice.getDisplayMode();
|
更改方式经证实相对容易些,但必须先利用
GraphicsDevice 的
isDisplayChangeSupported() 方法询问所涉及的图形设备是否支持更改。
一旦知道了这一点,要更改方式,使用
setDisplayMode() 方法,以传入新方式。
显示方式更改一般在
try /
finally 块中发生,
以便于 finally 块将代码复位成初始方式。虽然该过程不是绝对必需的,但它可以确保在程序完成时有一个安全的方式。
清单 3 显示了用于更改显示方式的典型模式:
清单 3. 更改方式
GraphicsDevice graphicsDevice = ...
DisplayMode originalDisplayMode = graphicsDevice.getDisplayMode();
DisplayMode newDisplayMode = ...
try {
if (graphicsDevice.isDisplayChangeSupported()) {
graphicsDevice.setDisplayMode(newDisplayMode);
}
} finally {
graphicsDevice.setDisplayMode(originalDisplayMode);
}
|
使用全屏幕窗口
使用 FEM API,
进入全屏幕窗口是轻而易举的:只要将 window 传递给
GraphicsDevice 的
setFullScreenWindow() 方法,如清单 4 所示。当您想要返回非全屏幕方式时,将
null 传递给该方法。当然,先通过使用
isFullScreenSupported() 方法来检查
GraphicsDevice 是否支持全屏幕方式。
清单 4. 进入全屏幕方式
GraphicsDevice graphicsDevice = ...
Window window = ...
try {
if (graphicsDevice.isFullScreenSupported()) {
graphicsDevice.setFullScreenWindow(window);
}
} finally {
graphicsDevice.setFullScreenWindow(null);
}
|
为了说明目前为止您所学的所有内容,清单 5 包含了一个完整示例。
清单 5 中的代码获得显示方式,随机选择一个方式进行更改,然后显示一个带有文本“Hello, World!”的全屏幕窗口。
该示例显示新显示方式的特征,这样您可以看到特定的屏幕大小、刷新频率和色深(bit depth)。
清单 5. 方式更改示例
import java.awt.*;
import javax.swing.*;
import java.util.Random;
public class DisplayModes {
public static void main(String args[]) {
GraphicsEnvironment graphicsEnvironment =
GraphicsEnvironment.getLocalGraphicsEnvironment();
GraphicsDevice graphicsDevice =
graphicsEnvironment.getDefaultScreenDevice();
DisplayMode displayModes[] = graphicsDevice.getDisplayModes();
DisplayMode originalDisplayMode = graphicsDevice.getDisplayMode();
JWindow window = new JWindow() {
public void paint(Graphics g) {
g.setColor(Color.blue);
g.drawString("Hello, World!", 50, 50);
}
};
try {
if (graphicsDevice.isFullScreenSupported()) {
graphicsDevice.setFullScreenWindow(window);
}
Random random = new Random();
int mode = random.nextInt(displayModes.length);
DisplayMode displayMode = displayModes[mode];
System.out.println(displayMode.getWidth() + "x" +
displayMode.getHeight() + " \t" + displayMode.getRefreshRate() +
" / " + displayMode.getBitDepth());
if (graphicsDevice.isDisplayChangeSupported()) {
graphicsDevice.setDisplayMode(displayMode);
}
Thread.sleep(1000);
} catch (InterruptedException e) {
} finally {
graphicsDevice.setDisplayMode(originalDisplayMode);
graphicsDevice.setFullScreenWindow(null);
}
System.exit(0);
}
} |

 |

|
全屏幕呈现
请注意:在清单 5 中,用于全屏幕的窗口包括了十分有用的
paint() 方法。然而,因为窗口处于全屏幕方式,所以
paint()
方法所需的开销、该方法如何处理剪裁以及其它与显示处理相关的行为都是不必要的。
实际上,已经证明了标准的被动呈现开销太大,它减慢了全屏幕应用程序的速度。
例如,您不必处理如重叠窗口或调整窗口大小之类的任务。
而是可以采取更主动的方法,创建一个处理呈现窗口本身的紧凑循环(tight loop)。
如果您熟悉
双缓冲,那么您知道它在内存中管理两个
Image 对象,
并根据哪个对象拥有
当前的显示信息来在这两个对象之间进行交换。在一个
Image 显示的同时,
您绘制另一个
Image 并在各个“场景”之间交换
Image 对象。
显卡利用了类似的概念,但不是使用实际的 Java
Image 对象,而是交换内存页。
当交换内存页时显示就发生更改,所以您不需要将
Image 对象从程序内存复制到显示内存;
您只要更改视频指针,显示就会发生更改。
尽管现在双缓冲概念仍然存在,但已不是对程序空间中的
Image 进行写操作,而是直接绘制到显示内存空间。
BufferStrategy 类隐藏了使用的是上述两种双缓冲方法中的哪一种这一事实,
并且它允许您利用系统所提供的任何基于硬件的缓冲。要创建
BufferStrategy ,
使用
createBufferStrategy() 方法告诉系统您所期望的缓冲区数目,
并使用
getDrawGraphics() 方法在缓冲区之间进行交换,该方法返回下一个要使用的缓冲区。
从概念上讲仅此而已,但如清单 6 所示,工作代码需要花更多的精力:
清单 6. 使用 BufferStrategy
JFrame frame = ...
frame.createBufferStrategy(2); // Number of buffers to have
BufferStrategy bufferStrategy = frame.getBufferStrategy();
while (!done()) { // Some condition to end
Graphics g = null;
try {
g = bufferStrategy.getDrawGraphics();
drawNextScene(g); // Method to draw to
} finally {
// Free resources
if (g != null) {
g.dispose();
}
}
bufferStrategy.show();
}
|
使用
BufferStrategy 时,您不能假设绘制到缓冲区的最后一项仍有效;
必须使用
contentsLost() 方法进行询问。如果丢失该项,则必须重新绘制整个缓冲区。否则,
您只需绘制最后一次使用后发生的更改的项。
除了
BufferStrategy 的缓冲区支持外,
BufferCapabilities
类允许您使用
getCapabilities() 方法来发现某个策略支持哪些能力,如全屏幕方式。
工作示例
将所有代码段放在一起可产生清单 7 中的示例程序。
由于我的美术技能有限,所以不要指望有什么复杂的花样。然而,您将发现一个使用
BufferStrategy 和全屏幕绘制方式的完整工作示例。示例程序通过记住什么东西没有绘制到当前缓冲区来有意
不让缓冲区同步 ― 以便让您更清楚地了解同时工作的多个缓冲区。
程序的确随意地将 100 个矩形绘制到屏幕,两次绘制之间有 1/10 秒的延迟。
清单 7. 工作示例
import java.awt.*;
import java.awt.image.*;
import javax.swing.*;
import java.util.Random;
public class MultipleBuffers {
public static void main(String args[]) {
GraphicsEnvironment graphicsEnvironment =
GraphicsEnvironment.getLocalGraphicsEnvironment();
GraphicsDevice graphicsDevice =
graphicsEnvironment.getDefaultScreenDevice();
DisplayMode displayModes[] = graphicsDevice.getDisplayModes();
DisplayMode originalDisplayMode = graphicsDevice.getDisplayMode();
JFrame frame = new JFrame();
frame.setUndecorated(true);
frame.setIgnoreRepaint(true);
try {
if (graphicsDevice.isFullScreenSupported()) {
graphicsDevice.setFullScreenWindow(frame);
}
Random random = new Random();
int mode = random.nextInt(displayModes.length);
DisplayMode displayMode = displayModes[mode];
System.out.println(displayMode.getWidth() + "x" +
displayMode.getHeight() + " \t" + displayMode.getRefreshRate() +
" / " + displayMode.getBitDepth());
if (graphicsDevice.isDisplayChangeSupported()) {
graphicsDevice.setDisplayMode(displayMode);
}
frame.createBufferStrategy(2);
BufferStrategy bufferStrategy = frame.getBufferStrategy();
int width = frame.getWidth();
int height = frame.getHeight();
for (int i=0; i<100; i++) {
Graphics g = bufferStrategy.getDrawGraphics();
g.setColor(new Color(random.nextInt()));
g.fillRect(random.nextInt(width),
random.nextInt(height), 100, 100);
bufferStrategy.show();
g.dispose();
Thread.sleep(100);
}
} catch (InterruptedException e) {
} finally {
graphicsDevice.setDisplayMode(originalDisplayMode);
graphicsDevice.setFullScreenWindow(null);
}
System.exit(0);
}
}
|
示例程序将受益于一些增强功能:如使缓冲区保持同步,或者检查当内容丢失时是否必须重新绘制整个缓冲区。
前一个任务只需记住最后绘制的矩形(和颜色),而后者要求全都记住。
管中窥豹,可见一斑
有了新的全屏幕独占模式 API,Java 开发就能成为游戏开发的主流选项。
该 API 取代了使用特定于平台的 API(如 DirectX 或 OpenGL)的需求,
而是依赖于跨所有支持 Java 平台的标准 API。这远远超出那些热衷于编程的专家的想象。
参考资料
关于作者  | |  | John Zukowski 在 JZ Ventures, Inc. 从事战略性 Java 咨询工作,在许多由 jGuru 社区推动的 Java FAQ 上,他经常充当指点迷津的高手。他最新的著作包括 Apress 的 Learn Java with JBuilder 6 和 Sybex 的 Mastering Java 2: J2SE 1.4。可以通过
jaz@zukowski.netJohn 联系。
|
对本文的评价
|