As strange as it sounds, the performance drain really is on this one line of code right here:
g2d.drawImage(background, 0, 0, this);
The reason is that background
is a rather large image, and the g2d
is performing a scale and a rotate every time. Enabling OpenGL via -Dsun.java2d.opengl=True
as suggested here might help, but I haven't been able to enable it on my machine.
Do note as well that it may be worth just using an existing game engine.
That said, the solution to the problem is to take the work out of the paintComponent
method. If you never will view the background from a different angle, just draw it at this tilt to begin with. Otherwise, you need yet another thread. So there would be three threads:
- The GUI thread
- Your game loop thread
- The rendering thread
In a proof-of-concept style, I added another volatile BufferedImage drawnBackground
as a field, and I added this to the end of your Main constructor:
new Thread(() -> {
while(true) {
if (background == null) continue;
// A new BufferedImage to avoid concurrency issues; in this way,
// the reference to this.drawnBackground is always a valid image
// to be drawn. If we reassign this.drawnBackground = drawnBackground
// while paintComponent() is running, that's fine, as it will simply
// draw the old image.
// It's also important to note that assignment to references
// is an atomic operation, but we still have to declare the field
// as volatile
BufferedImage drawnBackground = new BufferedImage(background.getWidth(), background.getHeight(), background.getType());
Graphics2D g2d = (Graphics2D) drawnBackground.getGraphics();
g2d.setColor(Color.WHITE);
g2d.fillRect(0, 0, background.getWidth(), background.getHeight());
float xscale = (float) SCREENWIDTH / WIDTH;
float yscale = (float) SCREENHEIGHT / HEIGHT;
g2d.scale(xscale, yscale);
g2d.rotate(Math.toRadians(45), WIDTH / 2, HEIGHT / 2);
g2d.drawImage(background, 0, 0, this);
this.drawnBackground = drawnBackground;
}
}).start();
The paintComponent
method changes to look like so:
public void paintComponent(Graphics g){
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
AffineTransform plain = g2d.getTransform();
float xscale = (float)SCREENWIDTH/WIDTH;
float yscale = (float)SCREENHEIGHT/HEIGHT;
g2d.drawImage(drawnBackground, 0, 0, this);
g2d.scale(xscale, yscale);
g2d.rotate(Math.toRadians(45), WIDTH/2, HEIGHT/2);
AffineTransform normal = g2d.getTransform();
// g2d.drawImage(background, 0, 0, this);
// rest of code
In this way, we move the expensive operation of rendering the image to another thread. Note that that other thread will still be getting around 30 fps instead of 60 fps, but the game itself will be running at a better framerate. In other words, the background image will be updating at closer to 30 fps, while everything else will be faster.