2

I'm trying to program a game in java, which basically constitutes mostly drawing a lot of image assets onto the screen.

That works great in basis, I have 70 frames per second on my main screen. The problem is that when I drag my gamewindow onto the other screen, suddenly it experiences screen tearing and severe framerate issues, only displaying 15-25 frames per second.

I've narrowed it down to 1 screen (Samsung TV) using properly configurated sun.java2d.d3d.D3DGraphicsConfig, while the other screen (laptop screen https://www.acer.com/ac/de/DE/content/series/nitro5) defaults to sun.awt.Win32GraphicsConfig.

I tried solutions from Java2D Performance Issues and FPS drops after loading images in java and changed all my images to properly configurated images with this code:

                final BufferedImage image = ImageIO.read(file);
                if(usedConfig != null) {
                    final BufferedImage compatibleImage = usedConfig.createCompatibleImage(image.getWidth(), image.getHeight(), image.getTransparency());
                    final Graphics2D g = compatibleImage.createGraphics();
                    g.drawImage(image, 0, 0, null);
                }
                currentImages.put(pathname+file.getName(), image);

I'm adjusting my config every frame with:

    GraphicsConfiguration currentConfig = null;
    if(Game.jpanel != null) {
        currentConfig = Game.board.getGraphicsConfiguration();
    }
    if(usedConfig != currentConfig) {
        System.out.println("config mismatch");
        if(usedConfig != null) {
            System.out.println(usedConfig.getClass().getName());
        }
        System.out.println(currentConfig.getClass().getName());
        usedConfig = currentConfig;
        firstCall = true;
    }
    if(firstCall != true) {
        return;
    }
    firstCall = false;
    ...//load and transform each image

The problem is that no matter what I do, it seems that on the slow screen nothing really helps, I'm stuck with at most 25 Frames per second.

I'm still working on this problem as you're reading this, so I will post an answer if I find one.

To simulate the problems you can try the VM option -Dsun.java2d.d3d=false with the following code:

import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.awt.image.ImageObserver;
import java.io.File;
import java.io.IOException;
import java.util.Timer;
import java.util.TimerTask;

import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;

public class SmallestReproducableExample {
    public static int frames = 0;
    public static int fps = 0;
    public static void main(final String[] args) throws IOException {
        final String pathname = "./image.png";
        final File file = new File(pathname);
        final BufferedImage image = ImageIO.read(file);
        final JFrame frame = new JFrame("SmallestReproducableExample");
        frame.setDefaultCloseOperation
        (JFrame.EXIT_ON_CLOSE);

        final TimerTask fpsCounterTask = new TimerTask() {
            @Override
            public void run() {
                fps = frames;
                frames = 0;
            }
        };
        final Timer fpsCounter = new Timer();
        final Timer paintThread = new Timer();
        fpsCounter.schedule(fpsCounterTask, 1000, 1000);
        final JPanel board = new JPanel() {
            @Override
            public void paint(final Graphics g) {
                g.clearRect(-45, -45, 2041, 1041);
                for(int x = -40; x <= 2000; x+=40) {
                    for(int y = -40; y <= 1000; y+=40) {
                        g.drawImage(image, x, y, 40, 40, (ImageObserver)null);
                    }
                }
                for(int x = -41; x <= 2000; x+=40) {
                    for(int y = -41; y <= 1000; y+=40) {
                        g.drawImage(image, x, y, 40, 40, (ImageObserver)null);
                    }
                }
                for(int x = -42; x <= 2000; x+=40) {
                    for(int y = -42; y <= 1000; y+=40) {
                        g.drawImage(image, x, y, 40, 40, (ImageObserver)null);
                    }
                }
                frame.setTitle("SmallestReproducableExample fps: "+fps);
                final TimerTask repaintTask = new TimerTask() { //Task must be new otherwise it throws java.lang.IllegalStateException: Task already scheduled or cancelled
                    @Override
                    public void run() {
                        repaint();
                    }
                };
                ++frames;
                paintThread.schedule(repaintTask, 1);
            }
        };
        board.setSize((1900),(1070));
        board.setDoubleBuffered(true);
        frame.add(board);
        frame.setSize((1900),(1070));
        frame.setVisible(true);
        frame.setExtendedState(frame.getExtendedState() | JFrame.MAXIMIZED_BOTH);
    }
}

you obviously need your own test image with something transparent, as I can't provide one because of licenses.

HopefullyHelpful
  • 1,652
  • 3
  • 21
  • 37
  • 1
    Avoid overriding `paint` in preference to `paintComponent` as a general rule. `JPanel` implements `ImageObsever`, so you can replace `(ImageObserver)null` with `this`. Swing is single thread and NOT thread safe. You should avoid using `java.util.Timer` to reschedule painting and you should NOT do it from within the `paint` method, as painting may occur for any number of reasons, many of which you don't control. A Swing `Timer` might be a better solution or a dedicated `Thread` loop, if you ensure updates to the UI and its state are made within the Event Dispatching Thread – MadProgrammer Mar 31 '21 at 23:58
  • 1
    If you "create" something, you should also "dispose" of it, `final Graphics2D g = compatibleImage.createGraphics();` should have `g.dispose()` as the last call when you're done with the context, to ensure it's releasing any references it's maintaining. – MadProgrammer Mar 31 '21 at 23:59
  • If you need finite control over the painting workflow, I'd consider using a [`BufferStrategy`](https://docs.oracle.com/javase/tutorial/extra/fullscreen/bufferstrategy.html) instead – MadProgrammer Apr 01 '21 at 00:00
  • Thanks for the feedback. Though it doesn't adress the main problem, which seems to be that the graphics cards/system I'm using seems only supports one screen using graphics acceleration with a good pipeline while the other one is defaulting to a win32GraphicsDevice with a slow pipeline. Not sure if it's a system issue or a general java problem. It seems that the primary screen I choose is able to support a fast pipeline, for one screen that fast pipeline is d3d and for the other opengl. Secondary screen is always slow. – HopefullyHelpful Apr 01 '21 at 00:06
  • Sure, but if you isolate the ... incorrect workflow ... you reduce the work required to finding the cause and a possible solution. Having said that. I'm pretty sure you can't "dynamically" switch between graphical pipelines – MadProgrammer Apr 01 '21 at 08:09
  • I think the solution for me probaly is to move to unity or try out some opengl library for java. – HopefullyHelpful Apr 01 '21 at 09:27

1 Answers1

0

Ok, update to the issue, if I enable the VM option -Dsun.java2d.opengl=true and plug out my other screen, then the opengl pipeline is enabled on the slow screen, resulting in high 60-70 fps, which wasn't possible previously.

But now when switching screens the other screen is slow.

I also need a way to enable the opengl pipeline on the fly programmatically to solve the issue completely.

HopefullyHelpful
  • 1,652
  • 3
  • 21
  • 37