4

I can't figure out a way to get smooth movement or animation of anything using Java2d when the opengl and direct3d pipelines are disabled (by invoking the vm with -Dsun.java2d.d3d=false and -Dsun.java2d.opengl=false)

The quick and dirty code below demonstrates my problem. It draws a box that moves across the screen. The box location is updated about 60 times per second and the screen is redrawn as many times as possible. It uses the BufferStrategy class to implement double buffering; the flip is done at "bs.show();"

Code(press escape to quit):

import java.awt.Color;
import java.awt.Frame;
import java.awt.Graphics;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.Rectangle;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.image.BufferStrategy;

public class FluidMovement {
    private static volatile boolean running = true;
    private static final int WIDTH = 500;
    private static final int HEIGHT = 350;

    public static void main(String[] args) {
        GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
        GraphicsDevice gd = ge.getDefaultScreenDevice();
        GraphicsConfiguration gc = gd.getDefaultConfiguration();

        Frame frame = new Frame(gc);
        frame.setIgnoreRepaint(true);
        frame.setUndecorated(true);
        frame.addKeyListener(new KeyAdapter() {
            @Override public void keyPressed(KeyEvent e) {
                if (e.getKeyCode() == KeyEvent.VK_ESCAPE) {
                    running = false;
                }
            }
        });
        frame.setSize(WIDTH, HEIGHT);
        frame.setVisible(true);
        frame.createBufferStrategy(2);
        BufferStrategy bs = frame.getBufferStrategy();
        long nextTick = System.nanoTime();
        class Rect {
            int dx = 2, dy = 1, x = 0, y = 0;
        }
        Rect rect = new Rect();

        Graphics g;
        while (running) {

            if (System.nanoTime() > nextTick) {
                rect.x = (rect.x + rect.dx) % WIDTH;
                rect.y = (rect.y + rect.dy) % HEIGHT;
                nextTick += 1000000000 / 60;
            }

            g = bs.getDrawGraphics();
            g.setColor(Color.BLACK);
            g.fillRect(0, 0, WIDTH, HEIGHT);
            g.setColor(Color.WHITE);
            g.fillRect(rect.x, rect.y, 10, 10);
            g.dispose();
            bs.show();
        }
        bs.dispose();
        frame.dispose();
    }

}

When I execute this code normally with "java FluidMovement", it runs smooth as silk (besides the occasional tearing) because the jvm makes use of the direct3d/directdraw pipeline. When i execute this code with "java -Dsun.java2d.d3d=false -Dsun.java2d.opengl=false FluidMovement" it is terribly choppy.

I can't make the assumption that the direct3d or opengl pipeline is used. The pipelines don't work with 2 of the 3 machines I have tried it on; it only worked on a machine with dedicated graphics running Windows 7. Is there anyway I can make the box move smoothly or should i resort to using some kind of library with low level access like JOGL?

Notes:

  • Frame rate is not the issue. In both cases (pipelines enabled and disabled), the application runs well over 300 fps. I forced vsync off when the pipelines are enabled.
  • I've tried Toolkit.getDefaultToolkit().sync()
  • I've tried many different types of loops, but the movement is never truly smooth. Even with a fixed framerate the same choppiness is exhibited.
  • I've tried running the frame in full-screen exclusive mode.
  • I've tried using 3 or even 4 buffers.

2 Answers2

3

A number of things jump out at me and scare me...

  1. You're not honoring the thread/Swing contract. All updates to the UI MUST be made from with the Event Dispatching Thread. All long running and blocking code should be executed in a background thread.
  2. Your "animation loop" is sucking up a lot of CPU time doing nothing. The thread should be sleeping between cycles (or at the very least, should only paint when something has changed), this should reduce the over all load on the system.

I tried a few solutions.

While I didn't have "significant" issues, these are really simple examples, I did generally get better performance with the default JVM options.

Buffering strategy

This is basically what you had, begin nice to the EDT and using the buffer strategy your were using

public class SimpleAnimationTest {

    private boolean running = true;
    private Rectangle box = new Rectangle(0, 90, 10, 10);
    private int dx = 4;
    protected static final int WIDTH = 200;
    protected static final int HEIGHT = 200;

    public static void main(String[] args) {
        new SimpleAnimationTest();
    }

    public SimpleAnimationTest() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                }

                JFrame frame = new JFrame();

                frame.setIgnoreRepaint(true);

                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.setLayout(new BorderLayout());
                frame.setSize(WIDTH, HEIGHT);
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);

                frame.createBufferStrategy(2);
                final BufferStrategy bs = frame.getBufferStrategy();

                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        long tock = 1000 / 60;
                        while (running) {

                            box.x += dx;
                            if (box.x + box.width > WIDTH) {
                                box.x = WIDTH - box.width;
                                dx *= -1;
                            } else if (box.x < 0) {
                                box.x = 0;
                                dx *= -1;
                            }

                            Graphics2D g = (Graphics2D) bs.getDrawGraphics();
                            g.setColor(Color.BLACK);
                            g.fillRect(0, 0, WIDTH, HEIGHT);
                            g.setColor(Color.WHITE);
                            g.fill(box);
                            g.dispose();
                            bs.show();
                            try {
                                Thread.sleep(tock);
                            } catch (InterruptedException ex) {
                            }
                        }
                        bs.dispose();

                    }
                }).start();

            }
        });
    }
}

Double Buffered Swing Components

public class SimpleAnimationTest {

    private Rectangle box = new Rectangle(0, 90, 10, 10);
    private int dx = 4;

    public static void main(String[] args) {
        new SimpleAnimationTest();
    }

    public SimpleAnimationTest() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                }

                JFrame frame = new JFrame();
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.setLayout(new BorderLayout());
                frame.add(new SimplePane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class SimplePane extends JPanel {

        public SimplePane() {

            setDoubleBuffered(true);

            Timer timer = new Timer(1000 / 300, new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    box.x += dx;
                    if (box.x + box.width > getWidth()) {
                        box.x = getWidth() - box.width;
                        dx *= -1;
                    } else if (box.x < 0) {
                        box.x = 0;
                        dx *= -1;
                    }
                    repaint();
                }
            });
            timer.setRepeats(true);
            timer.setCoalesce(true);
            timer.start();
        }

        @Override
        protected void paintComponent(Graphics g) {
            Graphics2D g2d = (Graphics2D) g.create();
            super.paintComponent(g2d);
            box.y = (getHeight() - box.height) / 2;
            g2d.setColor(Color.RED);
            g2d.fill(box);
            g2d.dispose();
        }

        @Override
        public Dimension getPreferredSize() {
            return new Dimension(200, 200);
        }
    }
}
MadProgrammer
  • 343,457
  • 22
  • 230
  • 366
  • Thank you for your response. One of the things I want to do is avoid using swing. This is why I used Frame instead of JFrame. I was under the impression that awt was thread safe but after googling that I realized it isn't. The reason I wrote the timer the way it is is for purposes of demonstration. The code I wrote was just used to demonstrate my problem. I made it so that the same frame is drawn multiple times to get my point across that movement is not smooth. I also mentioned this in my notes about the timer –  Nov 23 '12 at 03:34
  • I am also unable to use a timer because of its inaccuracy –  Nov 23 '12 at 03:41
  • One of things I was thinking about mention was a better repaint cycle, rather then repainting the entire screen, only repaint those sections you have updated. I don't see the point, personally, in repainting the screen when it hasn't updated, it's time consuming and wasteful. So if you're asking how to improve it, change the animation loop and put in a delay between updates ;) – MadProgrammer Nov 23 '12 at 03:42
  • *"I am also unable to use a timer because of its inaccuracy*" Do you really need nano-second precision? Most people won't be able to see the difference between 25fps and 60fps (unless they have a really crappy monitor) – MadProgrammer Nov 23 '12 at 03:43
  • The final program will need to redraw the entire screen sadly. There will be many transparencies. I'd also like to be able to use full screen exclusive mode on computers that can support it and will need to use page-flipping regardless. –  Nov 23 '12 at 03:53
  • There is also no guarantee that a timer can finish the entire task before the next tick, so I'd like to be able to code specifically for that. –  Nov 23 '12 at 03:55
  • *"There is also no guarantee that a timer can finish the entire task before the next tick*" That's a fair point. There are work arounds, but I think you best bet is to use a `Thread` and `sleep` for the required amount of time to maintain the frame rate – MadProgrammer Nov 23 '12 at 03:59
  • I'll probably take a look at JOGL since that was the recommendation for this anyways. Thank you for the response. Starting to really hate java @_@ –  Nov 23 '12 at 04:07
  • Java's fun, you just need to let go ;) – MadProgrammer Nov 23 '12 at 04:40
  • I've accepted your answer. I did try out your code but it still has that similar choppiness. I don't think I can rely on Java2d. Let go? I wanna cry instead ]: –  Nov 23 '12 at 04:47
  • I'm not sure how JOGL is going to help, if the hardware is unable to provide acceleration - but I don't enough experience to suggest otherwise – MadProgrammer Nov 23 '12 at 04:51
  • A pipeline not working in java doesn't mean some kind of hardware acceleration is impossible on that computer. –  Nov 24 '12 at 22:19
1

I've solved my problems by switching to JOGL. Everything is nice and smooth now on the machines that I had difficulties with earlier.

LWJGL also looks promising, it lets you use opengl in almost in an identical way that you would in C.

Java2D is great if one of the pipelines work but otherwise it isn't very reliable. I've had better luck using pure software surfaces in SDL over java2d.