Search code examples
javaswingjframebufferstrategy

Swing requires createBufferStrategy(2) to properly paint


I have some grids that is painted to screen one by one. I use arrow keys to move grids around as a group. Swing is said to be doubleBuffered by default so I believe frame.createBufferStrategy(2) is a bad practice but the problem is when I don't use manual double buffering, the grids are misaligned and some holes are appearing between them. Using manual double buffering fixes it.

I'm also experiencing some graphical problems(such as a dialog's buttons not displaying properly) in the actual program(not in SSCCE) so I thought it might be caused by the incorrect implementation of the double buffering.

Here is the SSCCE of the program, that causes grids to misalign when not manually double buffered:

package SSCCE;

import java.awt.Color;
import java.awt.FlowLayout;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.LayoutManager;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.image.BufferStrategy;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;

public class Main {
boolean manuallyDoubleBuffered = false; //change this

static Main main;

public final JFrame frame = new JFrame();
public final Keys keys = new Keys();
private JPanel panel;
private BufferStrategy bufferStrategy;

 

public static void main(String[] args) {
    main = new Main();
    main.initiate();
    // --START LOOP--
    Thread loop = new Thread(main.new Looper());
    loop.start();
}

public void initiate() {
    frameInit();
    keys.start();
}

private void frameInit() {
    frame.setSize(1200, 750);
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    frame.setVisible(true);
    setUpGUI();
    if (manuallyDoubleBuffered)
        frame.createBufferStrategy(2); // manual double buffering
    bufferStrategy = frame.getBufferStrategy();
}

private void setUpGUI() {
    panel = new JPanel() {
        @Override
        protected void paintComponent(Graphics g) {
            Graphics2D g2d = (Graphics2D) g;
            Main.main.rendering(g2d);
            super.paintComponent(g);
        }
    };

    LayoutManager layout = new FlowLayout();
    frame.getContentPane().setBackground(Color.black);
    panel.setLayout(layout);
    panel.setOpaque(false);//
    JButton but1 = new JButton("but1");
    panel.add(but1);
    frame.add(panel);
}

class Looper implements Runnable {
    @Override
    public void run() {
        Main.main.gameLoop();
    }
}

private void gameLoop() {
    // variables are declared at start
    while (true) {

        if (manuallyDoubleBuffered)
            paint(); // MANUAL double buffering
        else
            frame.repaint();// no manual double buffering

        update();

        try {
            Thread.sleep(1000 / 60);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}// loop end

private void update() {
    move();

}

private void rendering(Graphics2D g2d) {
    // // testing
    paintGrids(g2d);
}

private void move() {
    x += sx;
    y += sy;
}

int sx = 0; //speedX
int sy = 0; //speedY

//top left corner of the grid
int x = 0; 
int y = 0;

private void paintGrids(Graphics2D g) {
    for (int i = 0; i < 100; i++) {
        for (int t = 0; t < 100; t++) {
            g.setColor(Color.GRAY);
            g.fillRect(i * 50 + x, t * 50 + y, 50, 50);
            g.setColor(Color.BLACK);
            g.drawString(i + "," + t, i * 50 + x, t * 50 + y + 10);
        }
    }

}

public void paint() {
    // uses double buffering system.
    do {
        do {
            Graphics2D g2d = (Graphics2D) bufferStrategy.getDrawGraphics();
            g2d.fillRect(0, 0, frame.getWidth(), frame.getHeight());

            try {
                frame.paint(g2d);
            } catch (NullPointerException e) {
                e.printStackTrace();
            }
            g2d.dispose();
        } while (bufferStrategy.contentsRestored());
        bufferStrategy.show();
    } while (bufferStrategy.contentsLost());
}
}

class Keys implements KeyListener {// Trimmed down to shorten SSCCE
private final int leftKey = 37; // left b.
private final int rightKey = 39; // Right b.
private final int upKey = 38;// up k.
private final int downKey = 40;// down k.

public void start() {

    Main.main.frame.addKeyListener(this);
    Main.main.frame.setFocusable(true);
}

private void left() {
    Main.main.sx -= 10;
}

private void right() {
    Main.main.sx += 10;
}

private void up() {
    Main.main.sy -= 10;
}

private void down() {
    Main.main.sy += 10;
}

@Override
public void keyPressed(KeyEvent e) {
    // TODO Auto-generated method stub
    System.out.println(e.getKeyCode());
    switch (e.getKeyCode()) {
    case leftKey:
        left();
        break;
    case rightKey:
        right();
        break;
    case downKey:
        down();
        break;
    case upKey:
        up();
        break;
    }
}

@Override
public void keyReleased(KeyEvent arg0) {
    // TODO Auto-generated method stub

}

@Override
public void keyTyped(KeyEvent arg0) {
    // TODO Auto-generated method stub

}

}// END OF THE KEYS CLASS

Oracle tutorials of swing does not explain the usage with a game loop. What is the best way to do it? Am I doing anything wrong?

In case the visual error is not reproduced on other computers, I'm uploading a screenshot: Those black lines are not supposed to happen. Black lines are caused by the misalinging of the rectangles. They don't exist when manual double buffering is set to true.

Thanks in advance.

Edit: I've forgot to mention that the black lines occur when grids are moving.

I' have also found out, manual double buffering drastically reduces performance.

Edit 2 : I've fixed the problem and posted it as an answer but feel free to comment on my code. Main class(except the gameLoop) is similar to the actual main class I use in my program.


Solution

  • I've found the problem and writing here in case something like that ever happens to anyone else.

    The problem was caused due to program being multi-threaded. Top left coordinates of the grids(x and y) were updated by the other thread in the middle of the paintGrids() method. Manual double buffering was slowing the program down (by hundreds of times) and that was allowing the paintGrids method to finish painting before x and y was updated by the keys.

    To fix it I've added the following to the start of the paintGrids method:

    int x = this.x;
    int y = this.y;