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

Я не могу найти способ получить плавное движение или анимацию чего-либо, используя Java2d, когда конвейеры opengl и direct3d отключены (вызывая vm с -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 буфера.

2 ответа

Решение

Ряд вещей выскакивают на меня и пугают меня...

  1. Вы не соблюдаете нить / свинг контракт. Все обновления пользовательского интерфейса ДОЛЖНЫ выполняться с помощью потока диспетчеризации событий. Весь долго работающий и блокирующий код должен выполняться в фоновом потоке.
  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();

            }
        });
    }
}

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

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

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

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

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

Другие вопросы по тегам