jdmonin / JSettlers2

Java Settlers project home, downloads, and GPLv3 source code. To download the latest version as a JAR, see https://github.com/jdmonin/JSettlers2/releases/latest .
http://nand.net/jsettlers/
GNU General Public License v3.0
157 stars 63 forks source link

Using BufferedImage and VolatileImage increases performance #84

Closed tiehfood closed 3 years ago

tiehfood commented 3 years ago

I use my own hex tile set with JSettlers2. As the images have a higher resolution than the default ones, I experienced a very high CPU usage while playing a game. To optimize performance I fist changed all of the GIF files to PNGs, which performed much better on my machine. Then I changed all the image processing and handling from Image to BufferedImages. For drawBoard and drawBoardEmpty I used VolatileImages instead. With this changes, the game uses drastically less CPU with my custom tile set.

jdmonin commented 3 years ago

Hello,

Thank you for your interesting observations. What OS and java version are you using?

I haven't worked with VolatileImage. Would you be interested in sharing your code? I'd like to compare it to the current version.

Many thanks, -Jeremy

tiehfood commented 3 years ago

All in SOCBoardPanel

Starting with loadHexesAndImages

newHexes[0] = ImageIO.read(clazz.getResource(imageDir + "waterHex.png"));  
newHexes[1] = ImageIO.read(clazz.getResource(imageDir + "clayHex.png"));
newHexes[2] = ImageIO.read(clazz.getResource(imageDir + "oreHex.png"));
newHexes[3] = ImageIO.read(clazz.getResource(imageDir + "sheepHex.png"));
newHexes[4] = ImageIO.read(clazz.getResource(imageDir + "wheatHex.png"));
newHexes[5] = ImageIO.read(clazz.getResource(imageDir + "woodHex.png"));
newHexes[6] = ImageIO.read(clazz.getResource(imageDir + "desertHex.png"));

getScaledImageUp

public static final BufferedImage getScaledImageUp(final BufferedImage src, final int w, final int h)
{
    BufferedImage buff = gfxConfig.createCompatibleImage(w, h, Transparency.BITMASK);
    Graphics2D g = bufi.createGraphics();
    g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
    g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
    g.drawImage(src, 0, 0, w, h, null);
    g.dispose();
    return bufi;
}

drawBoard

VolatileImage ebb = emptyBoardBuffer;

if (scaledMissedImage || ebb == null)
{
    if (ebb == null)
    {
        ebb = gfxConfig.createCompatibleVolatileImage(scaledPanelW, scaledPanelH);
        emptyBoardBuffer = ebb;
    }

    drawnEmptyAt = System.currentTimeMillis();
    scaledMissedImage = false;
    drawBoardEmpty(ebb.createGraphics());

    if (scaledMissedImage && (scaledAt != 0) && (RESCALE_MAX_RETRY_MS < (drawnEmptyAt - scaledAt)))
        scaledMissedImage = false;
}

g.setPaintMode();
g.drawImage(ebb, 0, 0, this);
...

paintComponent

VolatileImage ibuf = buffer;
try
{
    if (ibuf == null)
    {
        ibuf = gfxConfig.createCompatibleVolatileImage(scaledPanelW, scaledPanelH);
        buffer = ibuf;
    }
    try
    {
        drawBoard(ibuf.createGraphics());
    } catch (ConcurrentModificationException cme) {
        repaint();
        return;
    }
...

of corse you have to change all your variables from type Image to BufferedImage

tiehfood commented 3 years ago

P.S. I'm using it on MacOS (java RE 1.8) and Windows (JDK 1.8/11)

jdmonin commented 3 years ago

Thank you, I will take a look soon.

tiehfood commented 3 years ago

A little cosmetic tuning: I created a static variable icon for SOCPlayerClient with the official JSettlers icon image and then called on each new frame setIconImage . This way I have the nice official icon (i.e. on windows) and not the ugly default java icon. Don't know if it's the best way with the static variable in SOCPlayerClient, but it works 😊

jdmonin commented 3 years ago

Thanks @tiehfood , I've written some optimization code based on the docs and our discussion here. Tested on java 8 and newer on various platforms, including with stand-in big PNG images.

When you have time, could you please check out my feat/boardpanel-perf-84 branch to see if that works for you? If it looks good, I'll merge to master.

Thank you!

tiehfood commented 3 years ago

I only did a short test but as far as I could tell it looks very good. CPU usage drops to about 1/3 or even 1/4 in the new version while moving the mouse/resizing the board :)

tiehfood commented 3 years ago

You could also implement the BufferedImages on the player icons. Not that big impact on performance but would be consistent across image handling

jdmonin commented 3 years ago

This is a great improvement, thank you again :)

I'll consider this done, and I'll update the todo list with your other suggestion.