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