Java2D如何实现流畅绘图,不使用Opengl或Direct3d Pipelines?

4

当禁用opengl和direct3d管道时(通过使用-Dsun.java2d.d3d=false和-Dsun.java2d.opengl=false调用vm),我无法找到任何使用Java2d进行平滑移动或动画的方法。

以下是简短且不太规范的代码,它演示了我的问题。 它绘制一个沿屏幕移动的方框。 方框位置每秒更新约60次,并尽可能多地重新绘制屏幕。 它使用BufferStrategy类实现双重缓冲; 翻转在“bs.show();”处完成。

代码(按Esc退出):

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管道。在我尝试过的3台计算机中,有2台计算机无法使用此管道;仅在运行Windows 7 的带有独立显卡的计算机上有效。是否有任何方法可以使方块平滑移动,还是应该使用像JOGL这样具有低级访问权限的库?

注:

  • 帧率不是问题。在启用和禁用管道的两种情况下,应用程序都以超过300 fps的速度运行。在启用管道时,我强制关闭了垂直同步。
  • 我已尝试了Toolkit.getDefaultToolkit().sync()
  • 我已尝试了许多不同类型的循环,但移动永远不会真正流畅。即使是固定帧率,也会显示相同的卡顿。
  • 我已尝试在全屏独占模式下运行框架。
  • 我已尝试使用3甚至4个缓冲区。
2个回答

3

有几个问题让我感到担忧...

  1. 你没有遵守线程/Swing协议。所有对UI的更新都必须在事件分发线程中进行。所有长时间运行和阻塞的代码都应该在后台线程中执行。
  2. 你的“动画循环”正在消耗大量CPU时间,什么也没做。线程应该在循环之间休眠(或者至少只有在有变化时才绘制),这应该会减轻系统的总体负载。

我尝试了一些解决方案。

虽然我没有遇到“重大”的问题,这些都是非常简单的例子,但使用默认的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();

            }
        });
    }
}

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
“我也无法使用计时器,因为它不准确。” 你真的需要纳秒级精度吗?大多数人看不出25fps和60fps之间的差异(除非他们有一个非常糟糕的显示器)。 - MadProgrammer
最终程序需要重新绘制整个屏幕,这很遗憾。会有许多透明度。我还希望能够在支持的计算机上使用全屏独占模式,并且无论如何都需要使用页面翻转。 - user1846344
“计时器无法保证在下一次滴答之前完成整个任务”这是一个很好的观点。虽然有一些解决方法,但我认为最好的选择是使用Threadsleep来维持帧率所需的时间。 - MadProgrammer
我不确定JOGL如何帮助,如果硬件无法提供加速的话 - 但是我没有足够的经验来提出其他建议。 - MadProgrammer
显示剩余7条评论

1
我已经通过转换到JOGL解决了我的问题。现在,我之前遇到困难的机器上的一切都很顺畅。
LWJGL看起来也很有前途,它让你以几乎与C中相同的方式使用opengl。
如果其中一个管道起作用,Java2D非常好,否则它不是很可靠。我在SDL中使用纯软件表面比java2d更好运行。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接