dev in the making

game development, maya and code by brainzizi

Posts Tagged ‘gpu

GameOfLife – Source code

leave a comment »

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.

Written by brainzizizi

10.08.2009 at 07:14

Posted in xna

Tagged with , ,