6

While testing some real-time simulation code which uses a Swingworker I noticed my GUI always seems to run at 30 fps, no more, no less. I update the GUI everytime the user interacts with the application (like a mouse move) or when the process() method of the Swingworker is called. The Swingworker doesn't do anything right now, it just grabs the mouse location from the GUI and sends it back as a clone through the publish() and process() methods (I'm just doing this to see what I can and can't do when communicating between threads because multithreading is still fairly new to me). I don't have any timers anywhere, the process() method of the Swingworker calls repaint() on the GUI, so I was wondering what causes the GUI to update at 30 fps? Is there maybe like a vsync active in the GUI by default or is it some behaviour of the process() method in the Swingworker? And finally: is there a way to get higher framerates?

Here's an sscce which exhibits this behaviour:

public class SimGameTest implements Runnable {
    public static void main(String[] args) {
        SwingUtilities.invokeLater(new SimGameTest());
    }

    @Override
    public void run() {
        MainWindow mainWindow = new MainWindow(new Game());

        mainWindow.setLocationRelativeTo(null);
        mainWindow.setVisible(true);
    }
}

public class MainWindow extends JFrame {
    private Game        game;
    private GamePanel   gamePanel;

    public MainWindow(Game game) {
        this.game = game;

        createAndShowGUI();

        startGame();
    }

    private void startGame() {
        GameSim gameSim = new GameSim(game, gamePanel);
        gameSim.execute();
    }

    private void createAndShowGUI() {
        setTitle("Sim game test");
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setResizable(false);

        JPanel contentPane = new JPanel(new GridBagLayout());

        gamePanel = new GamePanel(game);
        contentPane.add(gamePanel);

        add(contentPane);
        pack();
    }
}

public class Game {
    public Point            mouseLocation       = new Point();
    public Point            clonedMouseLocation = new Point();
}

public class GameSim extends SwingWorker<Point, Point> {
    private Game                game;
    private GamePanel           gamePanel;

    public GameSim(Game game, GamePanel gamePanel) {
        this.game = game;
        this.gamePanel = gamePanel;
    }

    @Override
    protected Point doInBackground() throws Exception {
        while (true) {
            publish((Point) game.mouseLocation.clone());
        }
    }

    @Override
    protected void process(List<Point> pointList) {
        game.clonedMouseLocation = pointList.get(pointList.size() - 1);
        gamePanel.repaint();
    }
}

public class GamePanel extends JPanel {
    private Game                game;
    private long                lastTime;

    public GamePanel(Game game) {
        this.game = game;
        setPreferredSize(new Dimension(512, 512));
    addMouseMotionListener(new GamePanelListener(););
    }

    @Override
    public void paintComponent(Graphics g) {
        // draw background
        g.setColor(new Color(0, 0, 32));
        g.fillRect(0, 0, getWidth(), getHeight());

        g.setColor(new Color(192, 192, 255));
        g.drawString(game.clonedMouseLocation.x + ", " + game.clonedMouseLocation.y, 10, 502);

        long now = System.nanoTime();
        long timePassed = now - lastTime;
        lastTime = now;
        double fps = ((double) 1000000000 / timePassed);
        g.drawString("fps: " + fps, 10, 482);
    }

    private class GamePanelListener extends MouseInputAdapter {
        @Override
        public void mouseMoved(MouseEvent e) {
            if (contains(e.getPoint())) {
                game.mouseLocation = e.getPoint();
            }
        }
    }
}

I created another version where I just count the number of times the GUI was repainted and show the count on screen, and it seems to be increasing at a rate of 30 per second, so I guess the fps calculation isn't the problem.

Braiam
  • 1
  • 11
  • 47
  • 78
Erik Stens
  • 1,779
  • 6
  • 25
  • 40
  • 2
    Please edit your question to include an [sscce](http://sscce.org/) that exhibits the rate you describe. – trashgod Aug 07 '12 at 00:26
  • 2
    How are you getting the frame rate? – James Black Aug 07 '12 at 00:35
  • I average about 24fps. I think you will find it depends on the graphics pipeline implementation (DX or GL), the hardware, the OS and the over all load on the system. I also got it to drop down to 5fps when I placed the system under load. – MadProgrammer Aug 07 '12 at 01:32
  • I can get it down under heavy load as well, but not up, so I'm wondering what is causing this limit, if it's some part of Java or the OS itself. And can I get around it using a different technique? – Erik Stens Aug 07 '12 at 01:42
  • Switching to active rendering should improve fps, [Passive vs. Active Rendering](http://docs.oracle.com/javase/tutorial/extra/fullscreen/rendering.html). – tenorsax Aug 07 '12 at 01:55

1 Answers1

11

It turns out SwingWorker posts instances of Runnable to the EventQueue using a javax.swing.Timer with exactly that DELAY.

private static class DoSubmitAccumulativeRunnable 
      extends AccumulativeRunnable<Runnable> implements ActionListener {
    private final static int DELAY = (int) (1000 / 30);
    ...
}

You can get a higher frame rate, but not with javax.swing.SwingWorker. You can build SwingWorker from source, but eventually you saturate the thread with instances of Runnable. One alternative is to run the model flat out and sample it periodically, as shown here.

trashgod
  • 203,806
  • 29
  • 246
  • 1,045
  • I did _not_ know about `javax.swing.SwingWorker` using `javax.swing.Timer`. – trashgod Aug 07 '12 at 02:04
  • I discovered that yesterday while answering [this question](http://stackoverflow.com/questions/11825281/java-swing-running-on-edt) – Robin Aug 07 '12 at 05:43