@author Gary Frost
*/
public class Main{
/**
- LifeKernel represents the data parallel algorithm describing by Conway’s game of life.
-
- http://en.wikipedia.org/wiki/Conway’s_Game_of_Life
-
- We examine the state of each pixel and its 8 neighbors and apply the following rules.
-
- if pixel is dead (off) and number of neighbors == 3 {
-
pixel is turned on
- } else if pixel is alive (on) and number of neighbors is neither 2 or 3
-
pixel is turned off
- }
-
- We use an image buffer which is 2widthheight the size of screen and we use fromBase and toBase to track which half of the buffer is being mutated for each pass. We basically
- copy from getGlobalId()+fromBase to getGlobalId()+toBase;
-
-
- Prior to each pass the values of fromBase and toBase are swapped.
-
*/
public static class LifeKernel extends Kernel{
private static final int ALIVE = 0xffffff;
private static final int DEAD = 0;
private final int[] imageData;
private final int width;
private final int height;
private int fromBase;
private int toBase;
public LifeKernel(int _width, int _height, BufferedImage _image) {
imageData = ((DataBufferInt) _image.getRaster().getDataBuffer()).getData();
width = _width;
height = _height;
fromBase = height * width;
toBase = 0;
setExplicit(true); // This gives us a performance boost
/** draw a line across the image **/
for (int i = width * (height / 2) + width / 10; i < width * (height / 2 + 1) - width / 10; i++) {
imageData[i] = LifeKernel.ALIVE;
}
put(imageData); // Because we are using explicit buffer management we must put the imageData array
}
@Override public void run() {
int gid = getGlobalId();
int to = gid + toBase;
int from = gid + fromBase;
int x = gid % width;
int y = gid / width;
if ((x == 0 || x == width - 1 || y == 0 || y == height - 1)) {
// This pixel is on the border of the view, just keep existing value
imageData[to] = imageData[from];
} else {
// Count the number of neighbors. We use (value&1x) to turn pixel value into either 0 or 1
int neighbors = (imageData[from - 1] & 1) + // EAST
(imageData[from + 1] & 1) + // WEST
(imageData[from - width - 1] & 1) + // NORTHEAST
(imageData[from - width] & 1) + // NORTH
(imageData[from - width + 1] & 1) + // NORTHWEST
(imageData[from + width - 1] & 1) + // SOUTHEAST
(imageData[from + width] & 1) + // SOUTH
(imageData[from + width + 1] & 1); // SOUTHWEST
// The game of life logic
if (neighbors == 3 || (neighbors == 2 && imageData[from] == ALIVE)) {
imageData[to] = ALIVE;
} else {
imageData[to] = DEAD;
}
}
}
public void nextGeneration() {
// swap fromBase and toBase
int swap = fromBase;
fromBase = toBase;
toBase = swap;
execute(width * height);
}
}
public static void main(String[] _args) {
JFrame frame = new JFrame(“Game of Life”);
final int width = Integer.getInteger(“width”, 1024 + 512);
final int height = Integer.getInteger(“height”, 768);
// Buffer is twice the size as the screen. We will alternate between mutating data from top to bottom
// and bottom to top in alternate generation passses. The LifeKernel will track which pass is which
final BufferedImage image = new BufferedImage(width, height * 2, BufferedImage.TYPE_INT_RGB);
final LifeKernel lifeKernel = new LifeKernel(width, height, image);
// Create a component for viewing the offsecreen image
@SuppressWarnings(“serial”) JComponent viewer = new JComponent(){
@Override public void paintComponent(Graphics g) {
if (lifeKernel.isExplicit()) {
lifeKernel.get(lifeKernel.imageData); // We only pull the imageData when we intend to use it.
}
// We copy one half of the offscreen buffer to the viewer, we copy the half that we just mutated.
if (lifeKernel.fromBase == 0) {
g.drawImage(image, 0, 0, width, height, 0, 0, width, height, this);
} else {
g.drawImage(image, 0, 0, width, height, 0, height, width, 2 * height, this);
}
}
};
// Set the default size and add to the frames content pane
viewer.setPreferredSize(new Dimension(width, height));
frame.getContentPane().add(viewer);
// Swing housekeeping
frame.pack();
frame.setVisible(true);
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
long start = System.currentTimeMillis();
long generations = 0;
while (true) {
lifeKernel.nextGeneration(); // Work is performed here
viewer.repaint(); // Request a repaint of the viewer (causes paintComponent(Graphics) to be called later not synchronous
generations++;
long now = System.currentTimeMillis();
if (now - start > 1000) {
frame.setTitle(lifeKernel.getExecutionMode() + " generations per second: " + (generations * 1000.0) / (now - start));
start = now;
generations = 0;
}
}
}
}