[SOLVED] Java2D drawing speed (need help with performance).

Since it is my first post I would like to say hello everybody :slight_smile:

The goal of my program is to test how many FPS I can get while consnstantly drawing on the screen.
I would really appreciate if you can point any bottlenecks or suggest improvements.

The problem is that on one machine I can get around 2700 FPS, on the other with similiar hardware setup (but different OS) I get like 60-70.

If you want to compile those codes just remember to set image paths.

First my interface and implementation of FPS counting.

public interface FPS {
    int getFPSFrames();
    void setFPSFrames(int value);
}
public class FPSTask extends TimerTask {
    private FPS fps;

    public FPSTask(FPS fps) {
        this.fps = fps;
    }

    public void run() {
        System.out.println("Last: " + System.currentTimeMillis() + " frames: " + fps.getFPSFrames());
        fps.setFPSFrames(0);
    }
}

Bitmap with Screen based on the Catacomb Snatch sources.

public class Bitmap {
    protected int w, h;
    protected int pixels[];

    public Bitmap(int w, int h) {
        this(w, h, true);
    }

    protected Bitmap(int w, int h, boolean createPixels) {
        this.w = w;
        this.h = h;
        if (createPixels) {
            pixels = new int[w * h];
        }
    }

    public void blit(Bitmap bitmap, int x, int y) {
        Rectangle blitArea = new Rectangle(x, y, x + bitmap.w, y + bitmap.h);
        adjustBlitArea(blitArea);
        int blitWidth = blitArea.x2 - blitArea.x1;
        for (int yy = blitArea.y1; yy < blitArea.y2; yy++) {
            int targetPixel = yy * w + blitArea.x1;
            int sourcePixel = (yy - y) * bitmap.w + (blitArea.x1 - x);
            targetPixel -= sourcePixel;
            for (int xx = sourcePixel; xx < sourcePixel + blitWidth; xx++) {
                int col = bitmap.pixels[xx];
                int alpha = (col >> 24) & 0xff;
                if (alpha == 0xff) {
                    pixels[targetPixel + xx] = col;
                } else {
                    int bgCol = pixels[targetPixel + xx];
                    pixels[targetPixel + xx] = blendColors(bgCol, col);
                }
            }
        }
    }

    private Rectangle adjustBlitArea(Rectangle blitArea) {
        if (blitArea.x1 < 0) blitArea.x1 = 0;
        if (blitArea.y1 < 0) blitArea.y1 = 0;
        if (blitArea.x2 > w) blitArea.x2 = w;
        if (blitArea.y2 > h) blitArea.y2 = h;
        return blitArea;
    }

    public int getWidth() {
        return w;
    }

    public int getHeight() {
        return h;
    }

    public static Bitmap load(String filename) {
        try {
            BufferedImage bi = ImageIO.read(new File(filename));
            int biWidth = bi.getWidth();
            int biHeight = bi.getHeight();
            Bitmap bitmap = new Bitmap(biWidth, biHeight);
            bi.getRGB(0, 0, biWidth, biHeight, bitmap.pixels, 0, biWidth);
            return bitmap;
        } catch (IOException e) {
            return null;
        }
    }

    public static int blendColors(int backgroundColor, int colorToBlend) {
        RGB bgRGB = new RGB(backgroundColor);
        RGB pixelRGB = new RGB(colorToBlend);
        int bgAlpha = 256 - pixelRGB.alpha;
        int r = ((pixelRGB.r * pixelRGB.alpha + bgRGB.r * bgAlpha) >> 8) & 0xff0000;
        int g = ((pixelRGB.g * pixelRGB.alpha + bgRGB.g * bgAlpha) >> 8) & 0xff00;
        int b = ((pixelRGB.b * pixelRGB.alpha + bgRGB.b * bgAlpha) >> 8) & 0xff;
        return 0xff000000 | r | g | b;
    }

}
public class Screen extends Bitmap {
    protected final BufferedImage image;

    public Screen(int w, int h) {
        super(w, h, false);
        image = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
        pixels = ((DataBufferInt) image.getRaster().getDataBuffer()).getData();
    }

    public Image getImage() {
        return image;
    }

}

Helper classes used in Bitmap.

public class Rectangle extends java.awt.Rectangle {
    public int x1, y1, x2, y2;

    public Rectangle(int x1, int y1, int x2, int y2) {
        super(x1, y1, x2 - x1, y2 - y1);
        this.x1 = x1;
        this.y1 = y1;
        this.x2 = x2;
        this.y2 = y2;
    }

}
public class RGB {
    public int r, g, b;
    public int alpha;

    public RGB(int color) {
        r = color & 0xff0000;
        g = color & 0xff00;
        b = color & 0xff;
        alpha = color & 0xff000000;
    }
}

And finally whole program (based on ra4king’s game skeleton):

public class TestJFrame extends JFrame implements FPS, Runnable {
    public static final int MAX_OBJECTS = 500;

    private int frames;
    private Screen screen;

    private Bitmap background;
    private Bitmap object;

    private Canvas canvas;

    public TestJFrame() {
        setSize(640, 480);
        setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        setVisible(true);
        setIgnoreRepaint(true);
        screen = new Screen(getWidth(), getHeight());
        canvas = new Canvas();
        canvas.setSize(getWidth(), getHeight());
        add(canvas);
        pack();
        background = Bitmap.load("your_path_to_any_background.png"); //TODO set path, my background was 640x480.
        object = Bitmap.load("your_path_to_any_object"); //TODO set path, my object was 32x32.
    }

    public static void main(String[] args) {
        TestJFrame testJFrame = new TestJFrame();
        Thread t = new Thread(testJFrame);
        t.start();
    }

    public void run() {
        Timer timer = new Timer();
        FPSTask fpsTask = new FPSTask(this);
        timer.schedule(fpsTask, 0, 1000);
        canvas.createBufferStrategy(2);
        canvas.setIgnoreRepaint(true);
        BufferStrategy bs = canvas.getBufferStrategy();
        while (true) {
            do {
                do {
                    Graphics2D g = (Graphics2D) bs.getDrawGraphics();
                    render(g);
                    g.dispose();
                } while (bs.contentsRestored());
                bs.show();
            } while (bs.contentsLost());
            frames++;
        }
    }

    private void render(Graphics g) {
        g.setColor(Color.black);
        draw(MAX_OBJECTS);
        g.drawImage(screen.getImage(), 0, 0, getWidth(), getHeight(), null);
    }

    private void draw(int objects) {
        screen.blit(background, 0, 0);
        for (int i = 0; i < MAX_OBJECTS; i++) {
            screen.blit(object, (int) (Math.random() * 640), (int) (Math.random() * 480));
        }
    }

    public int getFPSFrames() {
        return frames;
    }

    public void setFPSFrames(int value) {
        frames = value;
    }
}

2700 = Windows, 60-70 = Linux perhaps? You may want to try explicitly turning on the OpenGL pipeline to see if that changes anything.

But you have also run into Java2D’s major downfall and one of the primary reasons why people tend to avoid it in favor of LWJGL or JOGL - the API is highly unpredictable and it is across not only platforms, but also hardware and even minor versions of Java :s You can’t even really call it an API as an API is supposed to hide implementation details; with Java2D you must know what is going on under the hood in order to get any kind of performance out of it.

That being said, you can turn on trace logging to see what drawing calls Java2D is doing under the hood; with that logging you can see if it is using the hardware pipeline or the software pipeline.

-Dsun.java2d.trace=log

Vast majority of that code looks like it’s a non-Java2d software renderer though. Only bit of Java2d looks like the buffer strategy. Pure Java software renderers tend to be quite similar performance across VM’s. More likely with those figures is that one of the canvas buffer strategies is vsynced.

[quote]60-70
[/quote]
Forced VSync? How’s the CPU usage?

@gimbal: yes, 2700 on Windows, 60-70 on Linux. Here’s my log:

X11FillRect
X11FillRect
sun.java2d.loops.FillRect::FillRect(AnyColor, SrcNoEa, AnyInt)
sun.java2d.loops.FillRect::FillRect(AnyColor, SrcNoEa, AnyInt)
sun.java2d.loops.FillRect::FillRect(AnyColor, SrcNoEa, AnyInt)
sun.java2d.loops.FillRect::FillRect(AnyColor, SrcNoEa, AnyInt)
sun.java2d.loops.Blit::Blit(IntRgb, SrcNoEa, IntRgb)
X11FillRect

Followed by lots of (I guess this is invoked in the loop):

sun.java2d.loops.TransformHelper::TransformHelper(IntArgb, SrcNoEa, IntArgbPre)

Also I have tried -Dsun.java2d.opengl=true, but got performance loss.

@nsigma:
I swapped the code to use BufferedImage but only lost performance. In my code bitmaps are arrays of int, so drawing is in fact done on arrays and I blit my screen (Screen class) every render on the physical screen. Can that be a bottle neck?

@cylab:
CPU usage: core1: 60-80%, core2: 40-50%, cores3-8 are under 5%
What should I do with VSync? Is there a way to turn it off (would I get screen flickering then)?

Yeah when you only do manual drawing stuff in stead of blitting primitives the performance is likely better when using the software pipeline. (forced) vsync seems like a candidate to explain the results; it may be that it is enforced because OS drawing functions are used (X11FillRect). Not to familiar with Linux / X11, I can only make haphazard guesses there.

I swapped the code to use BufferedImage but only lost performance.
That will probably depend on if the BufferedImage is a “compatible” image or not. Transparency can also be a bottleneck.

Think you missed the point of mine and cylab’s comments - if it’s caused by VSync you don’t have a performance bottleneck across the two OS’s. VSync is good, as it will stop tearing and it’s pointless running faster than the screen can update anyway. If you just want to check the performance of the code across the two OS’s, try taking out the blit to screen and just running the rest of your code - they’ll almost certainly come out about equal performance.

Unless you can take advantage of Java2D hardware acceleration, working directly with arrays of int is often faster than using the Java2D software renderer. It’s the same mechanism I use in the optimized software renderer for Praxis, and I use LWJGL and some code forked from libGDX for the OpenGL renderer (all of which you can read as Java2D is often a PITA! :slight_smile: )

Huh, without rendering on the screen (only blitting on bitmap) I get like 250-260 FPS. I will test my code at Windows machine when I get back home.
Also I have uploaded sources to GitHub: https://github.com/tomaszwojcik/java2d-graphics-engine

Try this on Linux (with Java 7):
-Dsun.java2d.xrender=true

@65K: Now I have 230 FPS (With rendering on screen). So it is a great improvement!

Thanks a lot guys!