createBufferStrategy() 的正确使用方法是什么?

What is the correct way to use createBufferStrategy()?

即使在使用 Java Swing 一年多之后,我仍然觉得它很神奇。如何正确使用 BufferStrategy,特别是方法 createBufferSrategy()?

我想要一个 JFrame 和一个添加到其中然后绘制的 Canvas。我还希望能够调整 (setSize()) Canvas 的大小。每次我调整 Canvas 的大小时,我的 BufferStrategy 似乎都被丢弃了,或者更确切地说,变得毫无用处,因为在 BufferStrategy 上使用 show() 实际上并没有做任何事情。另外,createBufferStrategy() 有一个奇怪的不确定性行为,我不知道如何正确同步它。

我的意思是:

public class MyFrame extends JFrame {
MyCanvas canvas;
int i = 0;

public MyFrame() {
    setUndecorated(false);
    setVisible(true);
    setSize(1100, 800);
    setLocation(100, 100);
    setDefaultCloseOperation(EXIT_ON_CLOSE);
    canvas = new MyCanvas();
    add(canvas);
    canvas.makeBufferStrat();
}

@Override
public void repaint() {
    super.repaint();
    canvas.repaint();
    //the bigger threshold's value, the more likely it is that the BufferStrategy works correctly
    int threshold = 2;
    if (i < threshold) {
        i++;
        canvas.makeBufferStrat();
    }
}
}

MyCanvas 有一个方法 makeBufferStrat()repaint():

public class MyCanvas extends Canvas {

BufferStrategy bufferStrat;
Graphics2D g;

public MyCanvas() {
    setSize(800, 600);
    setVisible(true);
}

public void makeBufferStrat() {
    createBufferStrategy(2);
    //I'm not even sure whether I need to dispose() those two.
    if (g != null) {
        g.dispose();
    }
    if (bufferStrat != null) {
        bufferStrat.dispose();
    }
    bufferStrat = getBufferStrategy();
    g = (Graphics2D) (bufferStrat.getDrawGraphics());
    g.setColor(Color.BLUE);
}

@Override
public void repaint() {
    g.fillRect(0, 0, 100, 100);
    bufferStrat.show();
}
}

我只是在 main 方法的 while(true) 循环中调用 MyFramerepaint() 方法。 当 threshold 很小(即 2)时,bufferStrat.show() 在大约 70% 的情况下不会执行任何操作 - JFrame 在启动程序时只是保持灰色。剩下的 30% 它按照预期的方式绘制矩形。如果我这样做 threshold = 200;,在我执行程序的时候绘画成功率接近 100%。 Javadoc 说 createBufferStrategy() 可能需要一段时间,所以我认为这就是问题所在。但是,如何正确同步和使用它?显然,我在这里做错了什么。我无法想象它应该如何使用。

有人有最小的工作示例吗?

您创建 BufferStrategy 的方式是 "okay",您可以查看 JavaDocs for BufferStrategy,其中有一个简洁的小示例。

您使用它的方式值得怀疑。使用 BufferStrategy 的主要原因是因为您想从 Swing 的绘画算法(被动)中控制绘画过程(主动绘画)

但是,您似乎正在尝试同时执行这两项操作,这就是它导致您出现问题的原因。相反,您应该有一个 "main" 循环,它负责决定缓冲区应该绘制什么以及何时绘制,例如...

import java.awt.Canvas;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.image.BufferStrategy;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JFrame;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

public class Test {

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

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

                TestPane testPane = new TestPane();
                JFrame frame = new JFrame("Testing");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.add(testPane);
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
                // The component needs to be attached to displayed window before
                // the buffer can be created
                testPane.startPainting();
            }
        });
    }

    public class TestPane extends Canvas {

        private AtomicBoolean painting = new AtomicBoolean(true);
        private PaintCycle paintCycle;

        private Rectangle clickBounds;

        public TestPane() {
            addMouseListener(new MouseAdapter() {
                @Override
                public void mouseClicked(MouseEvent e) {
                    if (clickBounds != null && clickBounds.contains(e.getPoint())) {
                        painting.set(false);
                    }
                }
            });
        }

        public void startPainting() {
            if (paintCycle == null) {
                createBufferStrategy(2);
                painting.set(true);
                paintCycle = new PaintCycle();
                Thread t = new Thread(paintCycle);
                t.setDaemon(true);
                t.start();
            }
        }

        public void stopPainting() {
            if (paintCycle != null) {
                painting.set(false);
            }
        }

        @Override
        public Dimension getPreferredSize() {
            return new Dimension(200, 200);
        }

        public class PaintCycle implements Runnable {

            private BufferStrategy strategy;
            private int xDelta = 2;
            private int yDelta = 2;

            @Override
            public void run() {
                System.out.println("Painting has started");

                int x = (int) (Math.random() * (getWidth() - 40));
                int y = (int) (Math.random() * (getHeight() - 40));

                do {
                    xDelta = (int) (Math.random() * 8) - 4;
                } while (xDelta == 0);
                do {
                    yDelta = (int) (Math.random() * 8) - 4;
                } while (yDelta == 0);

                clickBounds = new Rectangle(x, y, 40, 40);
                strategy = getBufferStrategy();
                while (painting.get()) {
                    // Update the state of the model...
                    update();
                    // Paint the state of the model...
                    paint();
                    try {
                        // What ever calculations you want to use to maintain the framerate...
                        Thread.sleep(40);
                    } catch (InterruptedException ex) {
                    }
                }
                System.out.println("Painting has stopped");
            }

            protected void update() {
                int x = clickBounds.x + xDelta;
                int y = clickBounds.y + yDelta;

                if (x + 40 > getWidth()) {
                    x = getWidth() - 40;
                    xDelta *= -1;
                } else if (x < 0) {
                    x = 0;
                    xDelta *= -1;
                }
                if (y + 40 > getHeight()) {
                    y = getHeight() - 40;
                    yDelta *= -1;
                } else if (y < 0) {
                    y = 0;
                    yDelta *= -1;
                }

                clickBounds.setLocation(x, y);
            }

            protected void paint() {
                // Render single frame
                do {
                    // The following loop ensures that the contents of the drawing buffer
                    // are consistent in case the underlying surface was recreated
                    do {
                        // Get a new graphics context every time through the loop
                        // to make sure the strategy is validated
                        Graphics2D graphics = (Graphics2D) strategy.getDrawGraphics();

                        // Render to graphics
                        // ...
                        graphics.setColor(Color.BLUE);
                        graphics.fillRect(0, 0, getWidth(), getHeight());

                        graphics.setColor(Color.RED);
                        graphics.fill(clickBounds);

                        // Dispose the graphics
                        graphics.dispose();

                        // Repeat the rendering if the drawing buffer contents
                        // were restored
                    } while (strategy.contentsRestored());

                    // Display the buffer
                    strategy.show();

                    // Repeat the rendering if the drawing buffer was lost
                } while (strategy.contentsLost());
            }

        }

    }

}

您还应该记住,Swing 从大约 1.4(或可能是 1.5)开始就一直在使用 DirectX 或 OpenGL 管道。使用 BufferStrategy 的主要原因是更直接地访问硬件(无论如何 Swing 非常接近)和直接控制绘画过程(这实际上是现在使用它的唯一原因)