Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Strange BufferStrategy issue - Game runs fast only on Intel GPUs

I ran into a very strange problem, I tried searching for an answer for days and days. My game just got a new particle system, but was too slow to be playable. Unfortunately, BufferedImage transformations are very slow. The explosion effect consists of about 200 white sprites loaded from a .png file, rotated, scaled and coloured randomly, moving with a random speed.

I tried to make the performance better with triple / double buffering, and ran into some problems.

My first try was with the JPanel the game was drawn on. I set up the buffers in the JFrame's class (Main), then done the drawing in the Game (extends JPanel) class, BUT, without Graphics g = bufferstrategy.getDrawGraphics();. Then, at the end of the drawing method, i showed the buffer IF it wasn't lost. The buffer was always "lost", as I didn't do the drawing with it's Graphics object. But! The game run as fast as hell! With no buffer in practical use! But how?

This try ended up with no graphical errors, and with a HUGE performance boost - but only on nVidia / AMD cards. Intel GPUs couldn't handle this, the screen was flashing white.

So, i ended up setting and using the BufferStrategy correctly. The Game class now extends Canvas, not JPanel, as getting the Graphics from a JFrame, and using it to draw on a JPanel ends up in an offset, as it is drawing under the title bar. Still fast, fix 60 FPS.

Now, when i created the BufferStrategy in the JFrame (Main class), there was no picture at all. I corrected this by setting up the BufferStrategy in the Game class (Canvas). The picture is correct now, but the game itself is slow like a snail. One explosion tears the FPS down to ~10, but only on nVidia / AMD. Ironic. Even old Intel GPUs handle it with 60 FPS, i managed to get 10000 particles in motion at 60 FPS on a 5-6 year old integrated Intel GPU. My card bumps up to 100% load when an explosion occurs.

Here is my major code (the whole code is unclear and long) :

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-
}

Of course, I call setBuffers() as soon as the Game is started.

I hope you can help me, this problem is very important, as using VolatileImage in my opinion won't give a performance boost, as image manipulating needs to be done using BufferedImage. I bet I'm missing something trivial, or doing something the wrong way.

Here are my computer specs, just to show it's not a hardware problem: Intel Core i7-3770k @ 4.3GHz, nVidia GTX 460, 12 GB ram

The "fast" computer: Intel Core 2 Duo @ 2.7 GHz, Integrated Intel Graphics, 2 GB ram

Thank you for your help and time! :)

EDIT Could VolatileImage help? If I know right, image manipulation must be done using BufferedImages, but drawing them is sluggish.

like image 715
Simon Tamás Avatar asked Jan 25 '13 19:01

Simon Tamás


2 Answers

Here's a few things to check:


Without knowing the console/error output of your setBuffers function it's difficult to tell. Is Intel using createBufferStrategy(2); while NV uses createBufferStrategy(3); ? if so that could be part of the issue with it using extra memory.


Have you tried the java2d System.properties yet? http://docs.oracle.com/javase/1.5.0/docs/guide/2d/flags.html Especially the trace property for debugging.

Windows only

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

All platforms

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

For debugging

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

Toolkit.getDefaultToolkit().sync(); doesn't actually force monitor VSync, that's done by BufferCapabilities (in your setBuffers function).

    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();
        }
    }

EDIT this code was from (http://pulpcore.googlecode.com/hg-history/3c4001969922b93048e0a166395115df059a2059/src/pulpcore/platform/applet/BufferStrategySurface.java)

Also, you should be calling setBuffers AFTER the Canvas constructor has run and it's been added to the JFrame.


I'm "relatively sure" you don't need to call super.paint(g); I don't know if its what's causing your specific issue, but you should remove the line from your paint function.


Though you don't show the code, your explosion mechanism using 200 randomly moving sprites could be a major cause for errors. Figure out the maximum number of explosions you want to have on the screen at one time and generate those sprites N * 200 before hand, put them in an array list ArrayList(N) and then in your code reference them cyclically. ExplosionClass = explosionList.get(currentExplosionIndex % explosionList.size());

like image 194
Louis Ricci Avatar answered Nov 14 '22 23:11

Louis Ricci


Check the size of the pictures compared to the caches, and try to diagnose the number of cache misses. Read up on data oriented design, it's usually a part of that.

An alternative is to change the way you do the explosions to a way where you don't load in 200 sprites and spread them out (do you reuse them, or do you load them every time you do an explosion?). Making an animated explosion takes less power, though it might not be as spectacular, and you would need to do some animating then.

Java also isn't the most optimal language for making games. You're working very high level, and the JVM slows things down a bit. My friends had similar problems when making small games, it was difficult to make a game that didn't lag tons.

like image 23
Wertilq Avatar answered Nov 14 '22 21:11

Wertilq