Search code examples
javaswingbufferstrategy

What is the correct way to use createBufferStrategy()?


Even after using Java Swing for over a year, it still seems like magic to me. How do I correctly use a BufferStrategy, in particular, the method createBufferSrategy()?

I would like to have a JFrame and a Canvas that gets added to it and then painted. I would also like to be able to resize (setSize()) the Canvas. Every time I resize the Canvas it seems my BufferStrategy gets trashed or rather, turns useless, since using show() on the BufferStrategy does not actually do anything. Also, createBufferStrategy() has a weird non-deterministic behaviour and I don't know how to synchronize it correctly.

Here's what I mean:

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 has a method makeBufferStrat() and 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();
}
}

I simply call MyFrame's repaint() method from a while(true) loop in the main method. When threshold is small (i.e. 2), bufferStrat.show() in about 70% of all cases doesn't do anything - the JFrame just remains gray upon starting the program. The remaining 30% it paints the rectangle how it's supposed to. If I do threshold = 200;, the painting succeeds close to 100% of the time I execute the program. Javadoc says that createBufferStrategy() may take a while, so I assume that's the issue here. However, how do I synchronize and use it properly? Clearly, I'm doing something wrong here. I can't imagine that's how it's supposed to be used.

Does anyone have a minimal working example?


Solution

  • The way you create the BufferStrategy is "okay", you could have a look at the JavaDocs for BufferStrategy which has a neat little example.

    The way you're using it, is questionable. The main reason for using a BufferStrategy is because you want to take control of the painting process (active painting) away from Swing's painting algorithm (which is passive)

    BUT, you seem to trying to do both, which is why it's causing your issues. Instead, you should have a "main" loop which is responsible for deciding what and when the buffer should paint, for example...

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

    You should also remember, Swing's been using either DirectX or OpenGL pipelines since about 1.4 (or maybe 1.5). The main reasons for using BufferStrategy are more direct access to the hardware (which Swing is pretty close to anyway) AND direct control over the painting process (which is now really the only reason to use it)