Ok i thought id post this one up as a nice complement to the above this version is a slight alteration of one of my older projects which is gpu time based. This example is very similar to the last two and i’ve renamed some things to make it more similar.
There are some differences to the above particle examples.
Were as the original and then the modifyed versions are cpu based and the second example is cpu / gpu based. The following version is fully gpu based the motion is basically algorithmic time driven.
This has its pro’s and con’s
The con being it’s inflexible physics wise which is is a big downside.
The pro being were going to get a big boost in speed from the previous examples much bigger.
So we’ll now draw hundreds of thousands of quads instead of just thousands.

here is the game1 file
using System;
//using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using System.Runtime.InteropServices;
namespace CpuGpuParticlesExC02
{
// the instanceDataType
[StructLayout(LayoutKind.Sequential)]
public struct InstanceData : IVertexType
{
public Vector3 instancePosition;
public float instanceTimeOrId;
public Color instanceColor;
public static readonly VertexDeclaration VertexDeclaration;
static InstanceData()
{
var elements = new VertexElement[]
{
new VertexElement(0, VertexElementFormat.Vector3, VertexElementUsage.Position, 1),
new VertexElement(12, VertexElementFormat.Single, VertexElementUsage.BlendWeight, 0),
new VertexElement(16, VertexElementFormat.Color, VertexElementUsage.Color, 1),
//new VertexElement( offset in bytes, VertexElementFormat.Single, VertexElementUsage. option, shader element usage id number )
};
VertexDeclaration = new VertexDeclaration(elements);
}
VertexDeclaration IVertexType.VertexDeclaration
{
get { return VertexDeclaration; }
}
}
// the vertexDataType
[StructLayout(LayoutKind.Sequential)]
public struct VertexPositionTexture : IVertexType
{
public Vector3 position;
public Vector2 texCoordinate;
public static readonly VertexDeclaration VertexDeclaration;
static VertexPositionTexture()
{
var elements = new VertexElement[]
{
new VertexElement(0, VertexElementFormat.Vector3, VertexElementUsage.Position, 0),
new VertexElement(12, VertexElementFormat.Vector2, VertexElementUsage.TextureCoordinate, 0),
};
VertexDeclaration = new VertexDeclaration(elements);
}
VertexDeclaration IVertexType.VertexDeclaration
{
get { return VertexDeclaration; }
}
}
public class ParticleSystemGpuTimed
{
// Vertex data
VertexBuffer vertexBuffer;
IndexBuffer indexBuffer;
VertexBufferBinding vertexBufferBinding;
// Instance data
InstanceData[] instanceData;
VertexBuffer instanceBuffer;
VertexBufferBinding instanceBufferBinding;
Effect particlesEffect;
public Texture2D ParticleTexture { get; set; }
public Vector2 ParticleSize { get; set; }
public uint InitialInstanceCount { get; set; }
public int MaxVisibleParticles{ get; private set;}
int numInstancesToDraw;
float totalTime = 0;
#region Initilisation
public ParticleSystemGpuTimed(Effect effect)
{
particlesEffect = effect;
}
public void IntializeParticleSystemBuffers(GraphicsDevice graphicsDevice)
{
// set up the indice stuff
int[] indices = new int[6];
// indices to triangle vertices
indices[0] = 1; indices[1] = 0; indices[2] = 2;
indices[3] = 3; indices[4] = 1; indices[5] = 2;
//
indexBuffer = new IndexBuffer(graphicsDevice, typeof(int), 6, BufferUsage.WriteOnly);
indexBuffer.SetData(indices);
// set up the vertex stuff
// Create a single quad centered at the origin
float halfWidth = ParticleSize.X / 2;
float halfHeight = ParticleSize.Y / 2;
float z = .2f;
VertexPositionTexture[] vertices = new VertexPositionTexture[4];
// x,y world positions
vertices[0].position = new Vector3(-halfWidth, -halfHeight, z);
vertices[1].position = new Vector3(halfWidth, -halfHeight, z);
vertices[2].position = new Vector3(-halfWidth, halfHeight, z);
vertices[3].position = new Vector3(halfWidth, halfHeight, z);
// u,v texture coords
vertices[0].texCoordinate = new Vector2(0.0f, 0.0f);
vertices[1].texCoordinate = new Vector2(1.0f, 0.0f);
vertices[2].texCoordinate = new Vector2(0.0f, 1.0f);
vertices[3].texCoordinate = new Vector2(1.0f, 1.0f);
//
vertexBuffer = new VertexBuffer(graphicsDevice, VertexPositionTexture.VertexDeclaration, 4, BufferUsage.WriteOnly);
vertexBuffer.SetData(vertices);
vertexBufferBinding = new VertexBufferBinding(vertexBuffer);
// set up the instance stuff
MaxVisibleParticles = (int)(InitialInstanceCount);
instanceData = new InstanceData[MaxVisibleParticles];
// set particles randomly
Random rnd = new Random();
for (int i = 0; i < MaxVisibleParticles; ++i)
{
// instance data float time
instanceData[i].instanceTimeOrId = -(i + 1) / (float)InitialInstanceCount;
// instance data float position
instanceData[i].instancePosition = new Vector3
(
(rnd.Next(0, 800) - 400),
(rnd.Next(0, 600) - 300),
rnd.Next(1, MaxVisibleParticles + 1) / (float)(MaxVisibleParticles + 1)
);
instanceData[i].instanceColor = new Color(rnd.Next(0, 255), rnd.Next(0, 255), rnd.Next(0, 255), 255);
}
//
instanceBuffer = new VertexBuffer(graphicsDevice, InstanceData.VertexDeclaration, MaxVisibleParticles, BufferUsage.WriteOnly);
instanceBufferBinding = new VertexBufferBinding(instanceBuffer, 0, 1);
instanceBuffer.SetData(instanceData);
graphicsDevice.SetVertexBuffers(vertexBufferBinding, instanceBufferBinding);
graphicsDevice.Indices = indexBuffer;
}
#endregion Initilisation
// We could draw less instances here.
public void UpdateParticleTime(float seconds)
{
// i could use my dynamic dead alive buffer here to keep the sorting smooth.
numInstancesToDraw = MaxVisibleParticles;
totalTime += seconds;
}
public void DrawParticles(Matrix world, Matrix view, Matrix proj, GraphicsDevice graphicsDevice)
{
// Select the technique.
particlesEffect.CurrentTechnique = particlesEffect.Techniques["ParticleDrawingTimed02"];
// Initialise our shader constants
particlesEffect.Parameters["World"].SetValue(world);
particlesEffect.Parameters["View"].SetValue(view);
particlesEffect.Parameters["Projection"].SetValue(proj);
particlesEffect.Parameters["ParticleTexture"].SetValue(ParticleTexture);
particlesEffect.Parameters["TotalTime"].SetValue((float)totalTime);
//// Set buffers to device , for this example ill comment the below out to show once its set it stays set till you unset it.
//graphicsDevice.SetVertexBuffers(vertexBufferBinding, instanceBufferBinding);
//graphicsDevice.Indices = indexBuffer;
// Draw
particlesEffect.CurrentTechnique.Passes[0].Apply();
graphicsDevice.DrawInstancedPrimitives(PrimitiveType.TriangleList, 0, 0, 2, numInstancesToDraw);
}
}
public class Game1 : Game
{
GraphicsDeviceManager graphics;
ParticleSystemGpuTimed particleSystem;
Texture2D particleTexture;
Effect particlesEffect;
Matrix worldMatrix, viewMatrix, projMatrix;
// basically a elaborate timing device.
float cyclePercentage = 0f;
public double simulationSpeedHigherSlower = 1; // you can speed it up or slow it down with this well set it in load.
double cycleTime = 0d;
double last = 0d;
double now = 0d;
double elapsedUpdateTime = 0;
public static float framesIterated = 0;
public static float timeAccumulated = 0;
public static float fps = 0;
string msg =
"Gpu timed particle example." +
"- fps: Please wait a moment " +
" - total particles: "
;
public Game1()
{
graphics = new GraphicsDeviceManager(this);
graphics.GraphicsProfile = GraphicsProfile.HiDef;
graphics.PreferMultiSampling = false;
Window.AllowUserResizing = true;
this.IsFixedTimeStep = false;
graphics.SynchronizeWithVerticalRetrace = false;
graphics.PreferredBackBufferWidth = 1024;
graphics.PreferredBackBufferHeight = 768;
Content.RootDirectory = "Content";
}
protected override void Initialize()
{
base.Initialize();
}
protected override void LoadContent()
{
SetUpViewProjection();
particlesEffect = Content.Load<Effect>("InstancingShader");
particleTexture = Content.Load<Texture2D>("smokeSmall02b");
simulationSpeedHigherSlower = 30d; // 30 seconds for a particle to come round to its start position
particleSystem = new ParticleSystemGpuTimed(particlesEffect)
{
InitialInstanceCount = 500000, // number of particles to start with.
ParticleSize = new Vector2(2.0f, 2.0f),
ParticleTexture = particleTexture,
};
particleSystem.IntializeParticleSystemBuffers(GraphicsDevice);
}
public void SetUpViewProjection()
{
// Setup the worldViewProj matrix
float width = GraphicsDevice.PresentationParameters.BackBufferWidth;
float height = GraphicsDevice.PresentationParameters.BackBufferHeight;
float aspect = width / height;
worldMatrix = Matrix.Identity;
viewMatrix = Matrix.CreateLookAt(new Vector3(0.01f, 0.01f, 5.0f), Vector3.Forward, Vector3.Up);
projMatrix = Matrix.CreateOrthographic(aspect * width, height, 0.1f, 1000);
}
protected override void UnloadContent()
{
Content.Unload();
}
protected override void Update(GameTime gameTime)
{
if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed || Keyboard.GetState().IsKeyDown(Keys.Escape))
Exit();
last = now;
now = gameTime.ElapsedGameTime.TotalSeconds;
elapsedUpdateTime = now - last;
cycleTime += elapsedUpdateTime;
cyclePercentage = (float)(cycleTime / simulationSpeedHigherSlower);
if (cycleTime > simulationSpeedHigherSlower)
{
cycleTime -= simulationSpeedHigherSlower;
}
particleSystem.UpdateParticleTime((float)(cyclePercentage));
base.Update(gameTime);
}
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.Black);
GraphicsDevice.SamplerStates[0] = SamplerState.PointClamp;
GraphicsDevice.DepthStencilState = DepthStencilState.DepthRead;
//GraphicsDevice.BlendState = BlendState.Additive;
GraphicsDevice.BlendState = BlendState.NonPremultiplied;
framesIterated++;
timeAccumulated += (float)gameTime.ElapsedGameTime.TotalSeconds;
if (timeAccumulated > 1.0f)
{
timeAccumulated = 0f;
fps = framesIterated;
framesIterated = 0;
msg =
"Gpu timed particle example. Fps: " + fps + " - total particles: " + particleSystem.MaxVisibleParticles;
;
}
Window.Title = msg;
particleSystem.DrawParticles(worldMatrix, viewMatrix, projMatrix, GraphicsDevice);
base.Draw(gameTime);
}
}
}