奇怪的缓冲区策略问题 - 游戏仅在英特尔 GPU 上运行速度快

2022-09-01 17:51:19

我遇到了一个非常奇怪的问题,我试图寻找答案好几天。我的游戏刚刚有了一个新的粒子系统,但速度太慢,无法玩。不幸的是,BufferedImage转换非常缓慢。爆炸效果包括从.png文件中加载的大约200个白色精灵,随机旋转,缩放和着色,以随机速度移动。

我试图通过三重/双缓冲使性能更好,但遇到了一些问题。

我的第一次尝试是使用游戏所吸引的JPanel。我在JFrame的类(Main)中设置了缓冲区,然后在Game(扩展JPanel)类中完成了绘制,但是,没有图形g = bufferstrategy.getDrawGraphics();。然后,在绘制方法结束时,如果缓冲区没有丢失,我显示了缓冲区。缓冲区总是“丢失”,因为我没有使用它的图形对象进行绘制。但!游戏运行得和地狱一样快!在实际使用中没有缓冲器!但是如何做到呢?

这次尝试最终没有图形错误,并且性能提升巨大 - 但仅在nVidia / AMD卡上。英特尔GPU无法处理这种情况,屏幕闪烁着白色。

因此,我最终正确设置并使用了BufferStrategy。Game 类现在扩展了 Canvas,而不是 JPanel,因为从 JFrame 获取图形,并使用它来绘制 JPanel 最终会产生偏移量,因为它在标题栏下绘制。仍然很快,修复60 FPS。

现在,当我在JFrame(主类)中创建BufferStrategy时,根本没有图片。我通过在游戏类(Canvas)中设置BufferStrategy来纠正这一点。现在的情况是正确的,但游戏本身像蜗牛一样慢。一次爆炸将FPS降至约10,但仅在nVidia / AMD上。讽刺。即使是旧的英特尔GPU也以60 FPS处理它,我设法在5-6岁的集成英特尔GPU上以60 FPS的速度移动10000个粒子。当发生爆炸时,我的卡会撞击到100%的负载。

这是我的主要代码(整个代码不清楚,很长):

public class Game extends Canvas {
 -snip-
 public void tick() {
    BufferStrategy bf = getBufferStrategy();
    Graphics g = null;
    try {
        g = bf.getDrawGraphics();
        paint(g);
    } finally {
        g.dispose();
    }
    if (!bf.contentsLost()) {
        bf.show();
    } else {
        System.err.println("Buffer lost!");
    }
    Toolkit.getDefaultToolkit().sync();
 }
 public void setBuffers() {
    GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
    GraphicsDevice gs = ge.getDefaultScreenDevice();
    GraphicsConfiguration gc = gs.getDefaultConfiguration();

    if (gc.getBufferCapabilities().isMultiBufferAvailable()) {
        createBufferStrategy(3);
        System.out.println("Triple buffering active");
    } else {
        createBufferStrategy(2);
        System.err.println("Triple buffering not supported by the GPU");
        System.out.println("Double buffering active");
    }
    System.out.println("FullScreen required: " + getBufferStrategy().getCapabilities().isFullScreenRequired());
    System.out.println("Page flipping: " + getBufferStrategy().getCapabilities().isPageFlipping());
 }
 public void paint(Graphics g) {
    super.paint(g);
    //set up RenderingHints, draw stuff
 }
 -snip snip-
}

当然,一旦游戏开始,我就调用 setBuffers()。

我希望你能帮助我,这个问题非常重要,因为在我看来,使用Vol volatileImage不会提高性能,因为图像处理需要使用BufferedImage来完成。我敢打赌,我错过了一些微不足道的东西,或者以错误的方式做了一些事情。

以下是我的电脑规格,只是为了表明这不是硬件问题:英特尔酷睿i7-3770k @ 4.3GHz,nVidia GTX 460,12 GB内存

“快速”计算机:英特尔酷睿 2 双核 @ 2.7 GHz,集成英特尔显卡,2 GB 内存

感谢您的帮助和时间!:)

编辑VolatileImage能提供帮助吗?如果我没看错的话,图像处理必须使用BufferedImages来完成,但是绘制它们很慢。


答案 1

以下是一些需要检查的事项:


如果不知道 setBuffers 函数的控制台/错误输出,就很难分辨。英特尔是否使用 createBufferStrategy(2);而 NV 使用 createBufferStrategy(3); ?如果是这样,这可能是使用额外内存的问题的一部分。


你试过java2d System.properties吗?http://docs.oracle.com/javase/1.5.0/docs/guide/2d/flags.html尤其是用于调试的 trace 属性。

仅窗口

System.setProperty("sun.java2d.transaccel", "True");
System.setProperty("sun.java2d.d3d", "True");
System.setProperty("sun.java2d.ddforcevram", "True");

所有平台

System.setProperty("sun.java2d.opengl", "True");

用于调试

System.setProperty("sun.java2d.trace", "timestamp,log,count");
//// -Dsun.java2d.trace=[log[,timestamp]],[count],[out:<filename>],[help],[verbose]

Toolkit.getDefaultToolkit().sync();实际上并不强制监视 VSync,这是由 BufferCapability 完成的(在您的 setBuffers 函数中)。

    BufferStrategy bf = getBufferStrategy();
    if (bf != null) {
        BufferCapabilities caps = bf.getCapabilities();
        try {
            Class ebcClass = Class.forName(
                "sun.java2d.pipe.hw.ExtendedBufferCapabilities");
            Class vstClass = Class.forName(
                "sun.java2d.pipe.hw.ExtendedBufferCapabilities$VSyncType");

            Constructor ebcConstructor = ebcClass.getConstructor(
                new Class[] { BufferCapabilities.class, vstClass });
            Object vSyncType = vstClass.getField("VSYNC_ON").get(null);

            BufferCapabilities newCaps = (BufferCapabilities)ebcConstructor.newInstance(
                new Object[] { caps, vSyncType });

            createBufferStrategy(2, newCaps);

            // TODO: if success, setCanChangeRefreshRate(false) and setRefreshRate(60). 
            // Possibly override refreshRateSync()?
        }
        catch (Throwable t) {
            // Ignore
            t.printStackTrace();
        }
    }

编辑此代码来自 (http://pulpcore.googlecode.com/hg-history/3c4001969922b93048e0a166395115df059a2059/src/pulpcore/platform/applet/BufferStrategySurface.java)

此外,您应该在 Canvas 构造函数运行并将其添加到 JFrame 之后调用 setBuffers。


我“相对确定”你不需要调用super.paint(g);我不知道这是否是导致您的特定问题的原因,但是您应该从绘画功能中删除该行。


虽然你没有显示代码,但你使用200个随机移动的精灵的爆炸机制可能是导致错误的主要原因。找出您希望一次在屏幕上显示的最大爆炸次数,并在手之前生成这些精灵N * 200,将它们放在数组列表中ArrayList(N),然后在代码中循环引用它们。ExplosionClass = explosionList.get(currentExplosionIndex % explosionList.size());


答案 2

检查图片的大小与缓存的比较,并尝试诊断缓存未命中的数量。阅读面向数据的设计,它通常是其中的一部分。

另一种方法是将你进行爆炸的方式更改为一种你不加载200个精灵并将它们分散开来的方式(你是重用它们,还是在每次进行爆炸时加载它们?)。制作动画爆炸需要较少的功率,尽管它可能不那么壮观,你需要做一些动画制作。

Java也不是制作游戏的最佳语言。您正在处理非常高的水平,而JVM会稍微减慢速度。我的朋友在制作小游戏时也有类似的问题,很难制作出一款不滞后的游戏。


推荐