Плавное рисование с использованием Java2d без конвейеров Opengl или Direct3d?

Я не могу найти способ получить плавное движение или анимацию чего-либо с помощью Java2d, когда конвейеры opengl и direct3d отключены (путем вызова виртуальной машины с параметрами -Dsun.java2d.d3d=false и -Dsun.java2d.opengl=false)

Быстрый и грязный код ниже демонстрирует мою проблему. Он рисует прямоугольник, который перемещается по экрану. Расположение ящика обновляется примерно 60 раз в секунду, а экран перерисовывается столько раз, сколько возможно. Он использует класс BufferStrategy для реализации двойной буферизации; переворот выполняется в "bs.show();"

Код (нажмите Escape, чтобы выйти):

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();
    }

}

Когда я обычно выполняю этот код с помощью «java FluidMovement», он работает гладко, как шелк (не считая случайных разрывов), потому что jvm использует конвейер direct3d/directdraw. Когда я выполняю этот код с "java -Dsun.java2d.d3d=false -Dsun.java2d.opengl=false FluidMovement", он ужасно прерывистый.

Я не могу предположить, что используется конвейер direct3d или opengl. Конвейеры не работают с 2 из 3 машин, на которых я пробовал; это работало только на машине с выделенной графикой под управлением Windows 7. Могу ли я в любом случае заставить коробку двигаться плавно, или мне следует прибегнуть к использованию какой-либо библиотеки с низкоуровневым доступом, такой как JOGL?

Примечания:

  • Частота кадров не при чем. В обоих случаях (конвейеры включены и отключены) приложение работает со скоростью более 300 кадров в секунду. Я принудительно отключил vsync, когда конвейеры включены.
  • Я пробовал Toolkit.getDefaultToolkit().sync()
  • Я пробовал много разных типов петель, но движение никогда не было по-настоящему плавным. Даже при фиксированной частоте кадров наблюдается такая же изменчивость.
  • Я пробовал запускать фрейм в полноэкранном эксклюзивном режиме.
  • Я пробовал использовать 3 или даже 4 буфера.

person Community    schedule 23.11.2012    source источник


Ответы (2)


Многие вещи бросаются мне в глаза и пугают меня...

  1. Вы не соблюдаете контракт нити/Swing. Все обновления пользовательского интерфейса ДОЛЖНЫ выполняться с помощью потока диспетчеризации событий. Весь долгий и блокирующий код должен выполняться в фоновом потоке.
  2. Ваш «анимационный цикл» поглощает много процессорного времени, ничего не делая. Поток должен спать между циклами (или, по крайней мере, должен рисовать только тогда, когда что-то изменилось), это должно снизить общую нагрузку на систему.

Я попробовал несколько решений.

Хотя у меня не было «значительных» проблем, это действительно простые примеры, но в целом я добился лучшей производительности с параметрами JVM по умолчанию.

Стратегия буферизации

Это в основном то, что у вас было, начните хорошо с EDT и используйте стратегию буфера, которую вы использовали

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();

            }
        });
    }
}

Компоненты Swing с двойной буферизацией

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);
        }
    }
}
person MadProgrammer    schedule 23.11.2012
comment
Благодарю за ваш ответ. Одна из вещей, которые я хочу сделать, это избегать использования свинга. Вот почему я использовал Frame вместо JFrame. У меня сложилось впечатление, что awt является потокобезопасным, но после поиска в Google я понял, что это не так. Причина, по которой я написал таймер таким, какой он есть, заключается в демонстрации. Код, который я написал, был просто использован для демонстрации моей проблемы. Я сделал так, что один и тот же кадр рисуется несколько раз, чтобы понять, что движение не плавное. Я также упоминал об этом в своих заметках о таймере - person ; 23.11.2012
comment
Я также не могу использовать таймер из-за его неточности - person ; 23.11.2012
comment
Одна из вещей, о которых я подумал, это лучший цикл перерисовки, а не перерисовка всего экрана, перерисовывание только тех разделов, которые вы обновили. Я лично не вижу смысла перекрашивать экран, когда он не обновился, это долго и расточительно. Поэтому, если вы спрашиваете, как его улучшить, измените цикл анимации и установите задержку между обновлениями ;) - person MadProgrammer; 23.11.2012
comment
Я также не могу использовать таймер из-за его неточности Вам действительно нужна наносекундная точность? Большинство людей не смогут увидеть разницу между 25 кадрами в секунду и 60 кадрами в секунду (если только у них не очень дрянной монитор). - person MadProgrammer; 23.11.2012
comment
В финальной программе нужно будет перерисовывать весь экран к сожалению. Будет много транспарантов. Я также хотел бы иметь возможность использовать полноэкранный эксклюзивный режим на компьютерах, которые могут его поддерживать, и в любом случае потребуется использовать перелистывание страниц. - person ; 23.11.2012
comment
Также нет гарантии, что таймер может завершить всю задачу до следующего тика, поэтому я хотел бы иметь возможность кодировать специально для этого. - person ; 23.11.2012
comment
Также нет гарантии, что таймер завершит всю задачу до следующего такта Это справедливое замечание. Есть обходные пути, но я думаю, что лучше всего использовать Thread и sleep в течение необходимого времени для поддержания частоты кадров. - person MadProgrammer; 23.11.2012
comment
Я, вероятно, взгляну на JOGL, так как это все равно было рекомендацией для этого. Спасибо за ответ. Начинаю ненавидеть java @_@ - person ; 23.11.2012
comment
Java - это весело, вам просто нужно отпустить ;) - person MadProgrammer; 23.11.2012
comment
Я принял твой ответ. Я попробовал ваш код, но он все еще имеет ту же изменчивость. Я не думаю, что могу полагаться на Java2d. Отпустить? Вместо этого я хочу плакать]: - person ; 23.11.2012
comment
Я не уверен, как JOGL поможет, если аппаратное обеспечение не может обеспечить ускорение, но у меня недостаточно опыта, чтобы предположить обратное. - person MadProgrammer; 23.11.2012
comment
Конвейер, не работающий в java, не означает, что на этом компьютере невозможно аппаратное ускорение. - person ; 25.11.2012

Я решил свои проблемы, перейдя на JOGL. Теперь все хорошо и гладко на машинах, с которыми у меня раньше были трудности.

LWJGL также выглядит многообещающе, он позволяет использовать opengl почти так же, как в C.

Java2D хорош, если работает один из конвейеров, но в остальном он не очень надежен. Мне больше повезло с использованием чистых программных поверхностей в SDL, а не в java2d.

person Community    schedule 23.11.2012