So I was using double buffering by manually drawing everything to offscreen image and then drawing that image on screen. Now I deceided to use swing in my game and convert rendering code to use buffer strategy. For activly rendering swing I used stuff described here.
After putting it all together I noticed drop of fps on my slower computer from 55 to 44. I was very dissapointed becouse I was actually expecting fps increase as I heard that double buffering uses all avaliable speed up methodes. So to check if it was from converting to buffer stragegy I created a small benchmark similar to my game rendering engine.
For test I drawed 100 ovals in a loop, 50 rectangles and 50 20x20 png images with alpha channel. Resoults are good, without png images BS rendering goes to 220 fps, and BI rendering only 90. With png images BS drops to 80, but it’s still a lot better then 60 BI render gets. So conclusion of this small benchmark is this: BS rendering is noticablely faster then BI rendering, and extreamly faster when dealing with java shapes.
This is my first benchmark test so don’t take it too seriously and if you see any bugs or improper measuring please tell. Why fps droped down in my game… I don’t know, probably becouse synchronizing for being able to render swing components. This is what I’ll test next.
Here is the code, 2 classes, switch rendering mode by setting “BS” or “BI” in VitkroijePanel.RENDER_METHOD :
Viktorije:
package viktorije;
import java.awt.Dimension;
import java.awt.Toolkit;
import javax.swing.JFrame;
public class Viktorije extends JFrame {
public final static int DEFAULT_FPS = 60;
public static int res_height;
public static int res_width;
///////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////
// MAIN
public static void main(String[] args) {
int fps = DEFAULT_FPS;
long period = (long) 1000.0/fps;
System.out.println("fps: " + fps + "; period: " + period + " ms");
new Viktorije("Viktorije", period*1000000L); // ms --> nanosecs
}
// CONSTRUCTOR
public Viktorije(String title, long period) {
super(title);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setIgnoreRepaint(true);
setUndecorated(false);
Dimension screen_dim = Toolkit.getDefaultToolkit().getScreenSize();
res_width = (int)screen_dim.getWidth();
res_height = (int)screen_dim.getHeight();
setBounds(0,0,res_width,res_height);
setResizable(false);
setVisible(true); // must be called before createBufferStrategy() or exception is thrown, don't know why
createBufferStrategy(2);
ViktorijePanel viktorije_panel = new ViktorijePanel(this, period);
setContentPane(viktorije_panel);
viktorije_panel.startAnim(); // Kova: starts game loop thread
}
}
ViktorijePanel:
package viktorije;
import java.awt.*;
import java.awt.image.*;
import java.net.URL;
import javax.imageio.ImageIO;
import javax.swing.JPanel;
public class ViktorijePanel extends JPanel implements Runnable {
private Thread animator;
private volatile boolean running = false;
private long used_time = 0;
private int[] fps_store = new int[50];
private int fps = 0;
private int j = 0;
private static final String RENDER_METHOD = "BI"; // Kova: if not BS it will assume BI
private Graphics dbg;
private Image dbImage;
private GraphicsDevice device;
private DisplayMode display_mode;
private BufferStrategy buffer_strategy;
private Viktorije viktorije;
// BufferedImage ball = loadImage("images/lopta_1.png");
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
// CONSTRUCTOR
public ViktorijePanel(Viktorije viktorije, long period) {
this.viktorije = viktorije;
this.buffer_strategy = viktorije.getBufferStrategy();
setSize(1024,768);
setIgnoreRepaint(true);
setOpaque(true);
setLayout(null);
} // end: constructor
public void paint(Graphics g) {
System.out.println("ViktorijePanel.paint(): this should rarely (best never) be called");
}
// Kova: create and start main game thread
public void startAnim() {
if (animator == null || !running) {
animator = new Thread(this, "Viktorije Animator");
animator.start();
}
} // kraj: startAnim()
// Kova: start main game thread
public void run() {
running = true;
GraphicsEnvironment environment = GraphicsEnvironment.getLocalGraphicsEnvironment();
device = environment.getDefaultScreenDevice();
display_mode = new DisplayMode(1024,768,32,DisplayMode.REFRESH_RATE_UNKNOWN);
System.out.println("device.isFullScreenSupported() == " + device.isFullScreenSupported());
while(running) {
long start_time = System.nanoTime();
if (RENDER_METHOD.equals("BI")) {
gameRenderBI();
paintScreenBI();
} else {
gameRenderBS();
paintScreenBS();
}
sleep();
used_time = System.nanoTime() - start_time;
fps_store[j] = 1000/(int)(used_time/1000000);
j++;
if (j>49) j=0;
fps = 0;
for (int fps1: fps_store) {
fps += fps1;
}
fps /= 50;
}
} // end: run()
// Kova: only yields
private void sleep() {
// try {
// Thread.sleep(10); // Kova: 100 fps
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
Thread.yield();
}
// Kova: load images for testing if you like...
public static BufferedImage loadImage(String path) {
URL url = null;
try {
url = Viktorije.class.getClassLoader().getResource(path);
return ImageIO.read(url);
} catch (Exception e) {
System.out.println("Error loading image: " + path + " " + url);
System.exit(0);
return null;
}
}
// Kova: render graphics using BufferStrategy
private void gameRenderBS() {
Graphics2D g2d = null;
g2d = (Graphics2D)buffer_strategy.getDrawGraphics();
drawMyStuff(g2d);
}
// Kova: paint using BufferStrategy
private void paintScreenBS() {
try {
buffer_strategy.show();
Toolkit.getDefaultToolkit().sync(); // Sync the display on some systems (on Linux, this fixes event queue problems)
}
catch (Exception e) { // quite commonly seen at applet destruction
System.out.println("Graphics error: " + e);
e.printStackTrace();
}
} // end of paintScreen()
// Kova: render graphics using offscreen BufferedImage
private void gameRenderBI() {
if (dbImage == null) {
dbImage = createImage(1024, 768);
if (dbImage == null) {
return;
}
dbg = dbImage.getGraphics();
}
drawMyStuff(dbg);
}
// Kova: paint using .drawImage() using BufferedImage as offscreen buffer
private void paintScreenBI() {
Graphics g;
try {
g = this.getGraphics();
if ((g != null) && (dbImage != null))
g.drawImage(dbImage, 0, 0, null);
Toolkit.getDefaultToolkit().sync();
g.dispose();
}
catch (Exception e) {
System.out.println("Graphics error: " + e);
}
}
public void setFullScreen(boolean fullScreen) throws IllegalArgumentException {
if (isFullScreen() == fullScreen) {
return; // CommanderKeith: we're already in fullscreen, so return.
}
if (fullScreen) {
viktorije.setResizable(false);
device.setFullScreenWindow(viktorije);
if (display_mode != null && device.isDisplayChangeSupported()) {
try {
device.setDisplayMode(display_mode);
System.out.println("display mode set");
} catch (IllegalArgumentException e) {
System.out.println("display mode not supported");
device.setFullScreenWindow(null);
viktorije.setResizable(true);
throw e;
}
} else {
device.setFullScreenWindow(null);
viktorije.setResizable(true);
throw new IllegalArgumentException("device.isDisplayChangeSupported() == false");
}
} else {
device.setFullScreenWindow(null);
viktorije.setResizable(true);
}
viktorije.createBufferStrategy(2);
this.buffer_strategy = viktorije.getBufferStrategy();
}
public boolean isFullScreen() {
if (device.getFullScreenWindow() == null){
return false;
}
return true;
}
// Kova: draw your stuff for testing
private void drawMyStuff(Graphics g) {
g.setFont(new Font("Arial", Font.BOLD, 18));
g.drawString(RENDER_METHOD, 20, 70);
g.setColor(Color.GREEN);
g.fillRect(0, 0, Viktorije.res_width, Viktorije.res_height); // Kova: clear the background
g.setColor(Color.white);
g.fillRect(0, 45, Viktorije.res_width, 6);
for (int i=0; i<50; i++) {
g.drawOval(10*i, 10*i, 50, 50);
g.drawOval(10*i, 768/(i+1), 50, 50);
g.fillRect(10*i, 500, 50, 50);
// g.drawImage(ball, 20*i, 600, null);
}
g.setColor(Color.BLACK);
g.drawString("say: ", 25, 725);
if (used_time > 0) {
g.drawString("FPS: " + fps, 20, 40);
}
}
} // end: ViktorijePanel