GameOfLife – Source code
O hai! Last time I’ve posted I promised you some code so here it is.
GameOfLifeGPU is a simple project to implement Conway’s Game Of Life algorithm on a GPU. I’m using a 400×300 grid with edge wrapping, loading premade patterns from 400×300 content files and a simple mechanism for switching between those premade patterns. I’m also using a QuadDrawer class which you can find somewhere on Ziggyware.
GameOfLife.cs:
using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using System.IO;
namespace GameOfLifeGPU
{
/// <summary>
/// This is the main type for your game
/// </summary>
public class GameOfLife : Microsoft.Xna.Framework.Game
{
GraphicsDeviceManager graphics;
SpriteBatch spriteBatch;
private QuadRenderer quadDrawer;
private int width = 400;
private int height = 300;
private RenderTarget2D gridRT;
private Texture2D texture;
private Effect clearEffect;
private Effect updateEffect;
private string currentTexture = "gliders";
private KeyboardState keyState;
private KeyboardState oldKeyState;
public GameOfLife()
{
graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = "Content";
quadDrawer = new QuadRenderer(this);
Components.Add(quadDrawer);
}
/// <summary>
/// Allows the game to perform any initialization it needs to before starting to run.
/// This is where it can query for any required services and load any non-graphic
/// related content. Calling base.Initialize will enumerate through any components
/// and initialize them as well.
/// </summary>
protected override void Initialize()
{
graphics.PreferredBackBufferWidth = 1024;
graphics.PreferredBackBufferHeight = 786;
graphics.PreferMultiSampling = false;
graphics.ApplyChanges();
IsFixedTimeStep = true;
IsMouseVisible = true;
TargetElapsedTime = new TimeSpan(0, 0, 0, 0, 10);
base.Initialize();
}
/// <summary>
/// LoadContent will be called once per game and is the place to load
/// all of your content.
/// </summary>
protected override void LoadContent()
{
// Create a new SpriteBatch, which can be used to draw textures.
spriteBatch = new SpriteBatch(GraphicsDevice);
clearEffect = Content.Load<Effect>("Effects\\clear");
updateEffect = Content.Load<Effect>("Effects\\update");
gridRT = new RenderTarget2D(GraphicsDevice, width, height, 1, SurfaceFormat.Color);
texture = Content.Load<Texture2D>("glider");
}
/// <summary>
/// UnloadContent will be called once per game and is the place to unload
/// all content.
/// </summary>
protected override void UnloadContent()
{
}
/// <summary>
/// Allows the game to run logic such as updating the world,
/// checking for collisions, gathering input, and playing audio.
/// </summary>
/// <param name="gameTime">Provides a snapshot of timing values.</param>
protected override void Update(GameTime gameTime)
{
oldKeyState = keyState;
keyState = Keyboard.GetState();
if (keyState.IsKeyDown(Keys.Space) && !oldKeyState.IsKeyDown(Keys.Space))
{
string[] textures = Directory.GetFiles(Content.RootDirectory);
for (int j = 0; j < textures.Length; j++)
{
textures[j] = Path.GetFileNameWithoutExtension(textures[j]);
}
int i = Array.IndexOf(textures, currentTexture);
texture = Content.Load<Texture2D>(textures[(i + 1)%textures.Length]);
currentTexture = textures[(i + 1)%textures.Length];
Window.Title = "GameOfLife - " + currentTexture;
}
base.Update(gameTime);
}
/// <summary>
/// This is called when the game should draw itself.
/// </summary>
/// <param name="gameTime">Provides a snapshot of timing values.</param>
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.SetRenderTarget(0, gridRT);
updateEffect.Begin();
updateEffect.Parameters["Texture"].SetValue(texture);
updateEffect.Parameters["GridSize"].SetValue(new Vector2(width, height));
updateEffect.CurrentTechnique.Passes[0].Begin();
quadDrawer.RenderFullscreen();
updateEffect.CurrentTechnique.Passes[0].End();
updateEffect.End();
GraphicsDevice.SetRenderTarget(0, null);
texture = gridRT.GetTexture();
GraphicsDevice.Clear(Color.Black);
spriteBatch.Begin();
spriteBatch.Draw(texture,
new Rectangle(0, 0, GraphicsDevice.PresentationParameters.BackBufferWidth, GraphicsDevice.PresentationParameters.BackBufferHeight),
Color.White);
spriteBatch.End();
base.Draw(gameTime);
}
}
}
Just simple pattern switching code in the Update region and the draw mechanism in the Draw region.
Content\Effects\clear.fx:
struct VertexShaderInput
{
float4 Position : POSITION0;
};
struct VertexShaderOutput
{
float4 Position : POSITION0;
};
VertexShaderOutput VertexShaderFunction(VertexShaderInput input)
{
VertexShaderOutput output;
output.Position = input.Position;
return output;
}
float4 PixelShaderFunction(VertexShaderOutput input) : COLOR0
{
return float4(0, 0, 0, 1);
}
technique Technique1
{
pass Pass1
{
VertexShader = compile vs_1_1 VertexShaderFunction();
PixelShader = compile ps_1_1 PixelShaderFunction();
}
}
Content\Effects\update.fx:
texture Texture;
sampler2D TextureSampler = sampler_state
{
Texture = <Texture>;
ADDRESSU = WRAP;
ADDRESSV = WRAP;
MAGFILTER = POINT;
MINFILTER = POINT;
MIPFILTER = POINT;
};
float2 GridSize;
struct VertexShaderInput
{
float4 Position : POSITION0;
float2 Texcoord : TEXCOORD0;
};
struct VertexShaderOutput
{
float4 Position : POSITION0;
float2 Texcoord : TEXCOORD0;
};
VertexShaderOutput VertexShaderFunction(VertexShaderInput input)
{
VertexShaderOutput output;
output.Position = input.Position;
output.Texcoord = input.Texcoord;
return output;
}
float4 PixelShaderFunction(VertexShaderOutput input) : COLOR0
{
float4 cell = float4(0, 0, 0, 1);
float4 alive = float4(1, 1, 1, 1);
float4 dead = float4(0, 0, 0, 1);
float2 pixel = 1.0f / GridSize;
float n = 0.0f;
// count neighbours
n += tex2D(TextureSampler, float2(input.Texcoord.x - pixel.x, input.Texcoord.y - pixel.y)).r;
n += tex2D(TextureSampler, float2(input.Texcoord.x - pixel.x, input.Texcoord.y)).r;
n += tex2D(TextureSampler, float2(input.Texcoord.x - pixel.x, input.Texcoord.y + pixel.y)).r;
n += tex2D(TextureSampler, float2(input.Texcoord.x, input.Texcoord.y - pixel.y)).r;
n += tex2D(TextureSampler, float2(input.Texcoord.x, input.Texcoord.y + pixel.y)).r;
n += tex2D(TextureSampler, float2(input.Texcoord.x + pixel.x, input.Texcoord.y - pixel.y)).r;
n += tex2D(TextureSampler, float2(input.Texcoord.x + pixel.x, input.Texcoord.y)).r;
n += tex2D(TextureSampler, float2(input.Texcoord.x + pixel.x, input.Texcoord.y + pixel.y)).r;
if (tex2D(TextureSampler, input.Texcoord).r == 1.0f)
{
if (n == 2.0f || n == 3.0f)
cell = alive;
else
cell = dead;
}
else
{
if (n == 3.0f)
cell = alive;
else
cell = dead;
}
return cell;
}
technique Technique1
{
pass Pass1
{
// TODO: set renderstates here.
VertexShader = compile vs_2_0 VertexShaderFunction();
PixelShader = compile ps_2_0 PixelShaderFunction();
}
}
The algorithm kicks in the update.fx. Space cycles the premade patterns and you can make your own patterns by inserting your own 400×300 images. Check out the premade patterns. Wrapping edges were so easy to do. Easy like writing ADDRESSU = WRAP; ADDRESSV = WRAP;. In the CPU version they are a bit more complicated. So here you go. Simple and easy. Here’s the complete project in a zip file (73kb), which you can download from MU or RS:
http://www.megaupload.com/?d=PMV3OPJF
http://rapidshare.com/files/290162624/GameOfLifeGPU.zip
EDIT: I’ve corrected a typo in the LoadContent method. The starting texture name shouldn’t be “gliders”, but “glider”. Please correct this in your downloaded project file also.