How to particle system. A link to MrGrak's small github cpu particle example.

Just wanted to post up a link to this for reference anyone trying to understand how to do particles cpu side.
Or the basic idea for iterating a array of structs quickly for a lot of little game objects.

This is basically the original code if you would like to see it progress.

using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System.Diagnostics;
using Microsoft.Xna.Framework.Content;

namespace MrGraksAosExampleOriginal
{
    public static class Data
    {
        public static GraphicsDeviceManager GDM;
        public static ContentManager CM;
        public static SpriteBatch SB;
        public static Game1Original GAME;
    }

    public struct Physics
    {
        public float X, Y; //current position
        public float accX, accY; //accelerations
        public float preX, preY; //last position
    }

    public enum ParticleID : byte { Inactive, Fire, Rain }

    public struct Particle
    {   //uses physics struct as component
        public Physics physics;
        public Color color;
        public float alpha;
        public ParticleID Id;
        public short age;
    }

    public static class ParticleSystem
    {   //max size depends on the platform, 30k is baseline/lowend
        public const int size = 30000;
        public static int active = 0;
        public static Particle[] data = new Particle[size];

        static Random Rand = new Random();
        public static Texture2D texture;
        public static Rectangle drawRec = new Rectangle(0, 0, 3, 3);
        public static float gravity = 0.1f;
        public static float windCounter = 0;

        public static void Reset()
        {   //acts as a constructor as well
            if (texture == null)
            {   //set up a general texture we can draw dots with, if required
                texture = new Texture2D(Data.GDM.GraphicsDevice, 1, 1);
                texture.SetData<Color>(new Color[] { Color.White });
            }
            //reset particles to inactive state
            for (int i = 0; i < size; i++)
            { data[i].Id = ParticleID.Inactive; }
            active = 0; //reset active count too
        }

        public static void Spawn(
            ParticleID ID,          //type of particle to spawn
            float X, float Y,       //spawn x, y position 
            float accX, float accY  //initial x, y acceleration
            )
        {   //create a stack instance to work on
            Particle P = new Particle();
            //setup P based on parameters
            P.physics.X = X; P.physics.preX = X;
            P.physics.Y = Y; P.physics.preY = Y;
            P.physics.accX = accX;
            P.physics.accY = accY;
            //set defaults for particle
            P.alpha = 1.0f;
            P.age = 0;
            P.color = Color.White;
            //pass the ID
            P.Id = ID;
            //setup particle based on ID
            if (P.Id == ParticleID.Fire)
            {   //fire is red, for instance
                P.color = Color.Red;
            }
            else if (P.Id == ParticleID.Rain)
            {   //rain is blue, this could be an animated sprite tho
                P.color = Color.Blue;
            }
            //find an inactive particle to copy data to
            int s = size;
            for (int i = 0; i < s; i++)
            {   //read each struct once
                if (data[i].Id == ParticleID.Inactive)
                {   //write local to heap once
                    data[i] = P;
                    active++;
                    return;
                }   //if all are active, 
            }       //fail silently/ignore
        }

        static int fireEndMarker = 0;
        static int rainEndInactiveIndexMarker = 0; // this is the dead index.

        public static void Update()
        {   //step 1 - update/animate all the particles
            //step 2 - randomly spawn fire and rain particles

            //increase wind counter
            windCounter += 0.015f;
            //get left or right horizontal value for wind
            float wind = (float)Math.Sin(windCounter) * 0.03f;

            //store locals for this heap data
            int width = Data.GDM.PreferredBackBufferWidth;
            int height = Data.GDM.PreferredBackBufferHeight;

            //step 1 - update all active particles
            int s = size;
            for (int i = 0; i < s; i++)
            {   //particle active, age + check id / behavior
                if (data[i].Id > 0)
                {   //create a local copy
                    Particle P = data[i];

                    //age active particles
                    P.age++;
                    //affect particles based on id
                    if (P.Id == ParticleID.Fire)
                    {   //fire rises, gravity does not affect it
                        P.physics.accY -= gravity;
                        P.physics.accY -= 0.05f;
                        //has longer life than rain
                        if (P.age > 300)
                        {
                            P.Id = ParticleID.Inactive;
                        }
                    }
                    else if (P.Id == ParticleID.Rain)
                    {   //rain falls (at different speeds)
                        P.physics.accY = Rand.Next(0, 100) * 0.001f;
                        //has shorter life than fire
                        if (P.age > 200)
                        {
                            P.Id = ParticleID.Inactive;
                        }
                        //use behavior field here for additional states
                    }

                    //add gravity to push down
                    P.physics.accY += gravity;
                    //add wind to push left/right
                    P.physics.accX += wind;

                    //calculate velocity using current and previous pos
                    float velocityX = P.physics.X - P.physics.preX;
                    float velocityY = P.physics.Y - P.physics.preY;

                    //store previous positions (the current positions)
                    P.physics.preX = P.physics.X;
                    P.physics.preY = P.physics.Y;

                    //set next positions using current + velocity + acceleration
                    P.physics.X = P.physics.X + velocityX + P.physics.accX;
                    P.physics.Y = P.physics.Y + velocityY + P.physics.accY;

                    //clear accelerations
                    P.physics.accX = 0; P.physics.accY = 0;

                    //cull particle if it goes off screen
                    if (P.physics.X < 0 || P.physics.X > width)
                    { P.Id = ParticleID.Inactive; }
                    else if (P.physics.Y < 0 || P.physics.Y > height)
                    { P.Id = ParticleID.Inactive; }

                    //decrement active value upon particle expiration
                    if (P.Id == ParticleID.Inactive) { active--; }

                    //write local to heap
                    data[i] = P;
                }
            }

            for (int i = 0; i < 100; i++)
            {   //step 2 - randomly spawn rain + fire
                Spawn(ParticleID.Rain, Rand.Next(0, width), 0, 0, 0);
                Spawn(ParticleID.Fire, Rand.Next(0, width), height, 0, 0);
            }
        }

        public static void Draw()
        {   //open spritebatch and draw active particles
            Data.SB.Begin(SpriteSortMode.Deferred,
                BlendState.AlphaBlend, SamplerState.PointClamp);

            int s = size;
            for (int i = 0; i < s; i++)
            {   //if particle is active, draw it
                if (data[i].Id > 0)
                {   //create vector2 that draw wants
                    Vector2 pos = new Vector2(data[i].physics.X, data[i].physics.Y);
                    //draw each particle as a sprite
                    Data.SB.Draw(texture,
                        pos,
                        drawRec,
                        data[i].color * data[i].alpha,
                        0,
                        Vector2.Zero,
                        1.0f, //scale
                        SpriteEffects.None,
                        i * 0.00001f);
                }   //layer particles based on index, as simple example
            }

            Data.SB.End();
        }
    }

    public class Game1Original : Game
    {
        public static Stopwatch timer = new Stopwatch();
        public static long ticks = 0;

        public static float frames = 0;
        public static float secondTimer = 0;
        public static float fps = 0;

        public Game1Original()
        {
            //set data game refs
            Data.GDM = new GraphicsDeviceManager(this);
            Data.GDM.GraphicsProfile = GraphicsProfile.HiDef;
            Data.CM = Content;
            Data.GAME = this;
            Content.RootDirectory = "Content";
            IsMouseVisible = true;
        }

        protected override void Initialize() { base.Initialize(); }

        protected override void LoadContent()
        {
            Data.SB = new SpriteBatch(GraphicsDevice);
            ParticleSystem.Reset(); //acts as constructor
        }

        protected override void UnloadContent() { } //lol

        protected override void Update(GameTime gameTime)
        {
            timer.Restart();
            base.Update(gameTime);
            ParticleSystem.Update();
        }

        protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(Color.CornflowerBlue);
            base.Draw(gameTime);
            ParticleSystem.Draw();
            timer.Stop();
            ticks = timer.ElapsedTicks;

            frames++;
            secondTimer += (float)timer.Elapsed.TotalSeconds;
            if (secondTimer > 1.0f)
            {
                secondTimer = 0f;
                fps = frames;
                frames = 0;
            }
            Data.GAME.Window.Title = "AoS Particle System Example by @_mrgrak"
                //+ "- ticks per frame:" + ticks +
                + "- fps:" + fps +
                " - total particles: " + ParticleSystem.active +
                "/" + ParticleSystem.size;
        }
    }
}

Each time you need a new particle you scan through the array of 30000 looking for an empty one.

If 1000 particles die at once that can cause a fair amount of frame fluctations.

Perhaps your better off keeping a seperate int list with indexes of what indexes are available in the array. When a particle dies, add the index to the end of that second list. When you need a new position grab the last index from that second list and remove it from that second list.

Removing and adding the last item of the list can ve quite cheap.

edit

Also i think the drawing loop could be improved. Your scanning through all 30000 items in the array checking each particle. If your world had 50 particles active thats an expensive loop.

That’s mrgraks he wrote that up for a guy on discord who wanted to do it on the cpu.

.
.
I personally would do it on the gpu as instancing but the guy he did it for wanted it on the cpu.
Anyways in either case cpu or gpu.

I don’t really sort at all when i do this sort of thing i swap and use a index to track a dead marker on the array as if it were a stackpointer.
aka … I keep a dead alive index that points at the position were the first dead object is at and just treat the whole array like a stack. I swap a dead item with the last alive item (which is just before the dead index) then decrement the marker. So i move the last alive thing to the dead items position and then decrement the index when i do it. (You don’t actually have to move the item that died just replace its values with the item before the dead marker and decrement the dead marker).

That’s my standard way of handling a item stack were things can die so that it sorts itself in place.
aka there is no sort only swaps on death and a incremented dead index on a creation.
So when new items are created they are created at the dead index position then the dead index increments.

The trade off here is that adding creating on the fly has a little overhead however it eliminates any sorting which is in this case and most cases for a game a translation to a stable cost linear to the item count aka smooth.
You would have to have the requirement to add and delete a lot of items each frame for the sort to be worth being the first class priority over in place swaping in my opinion.

However since i didn’t want to or have time to write up a example myself and someone else did i figured id post this link feel free to leave a comment on his issue page grak is a pretty good fellow im sure any critisim or advice will be welcomed by him.

1 Like

I use the very same solution in my ECS implementation: arrays of struct, delete = move last item in array on top of deleted entry. Particles are also entities, like everything else and simulating 100k particles is no problem at all.

Once you go in the millions, you would have to move the simulation to the gpu entirely of course. The cpu-gpu memory communication is the bottleneck.

I’m curious to know how effective ECS is in a managed language like c#, compared to a native language like C++?

Hey, thanks for checking the AoS guide out. The goal of the guide was to get devs started with AoS. Actually, in the guide I mention that this is just a starting point, and there’s tons of optimizations to add to it, based on your needs. You can store the last active index, for example and only loop up to that value, for update and draw. You can swap particles in spawn, etc… there’s a ton of optimizations to be done, if you want to. Anyway, the goal was just to introduce some concepts. Thanks for reading! :+1:

I made a alteration to his class to show the difference in avoiding the iteration to search thru items for a id.
By using in place index swapping instead of searching or building a new list of inactive items as i described previously. Since he has two groups of items in the array two markers are needed and sometimes two swaps need to occur neverless the fps increase is about 2 to 3 times on both my pc’s.

My older laptop gets a even larger increase a little over 3x.

The code changes are below.

Edited: to fix a bug and the draw loop to only draw alive items.

using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System.Diagnostics;
using Microsoft.Xna.Framework.Content;

// i changed the namespace in my test you might have to change it for yours.
namespace MrGraksAosExample
{
    public static class Data
    {
        public static GraphicsDeviceManager GDM;
        public static ContentManager CM;
        public static SpriteBatch SB;
        public static Game1 GAME;
    }

    public struct Physics
    {
        public float X, Y; //current position
        public float accX, accY; //accelerations
        public float preX, preY; //last position
    }

    public enum ParticleID : byte { Inactive, Fire, Rain }

    public struct Particle
    {   //uses physics struct as component
        public Physics physics;
        public Color color;
        public float alpha;
        public ParticleID Id;
        public short age;
    }

    public static class ParticleSystem
    {   //max size depends on the platform, 30k is baseline/lowend
        public const int size = 30000;
        //public static int active = 0;  // replaced by rainEndInactiveIndexMarker
        public static Particle[] data = new Particle[size];

        static Random Rand = new Random();
        public static Texture2D texture;
        public static Rectangle drawRec = new Rectangle(0, 0, 3, 3);
        public static float gravity = 0.1f;
        public static float windCounter = 0;

        #region  modifications to treat the array as a stack and avoid searchs for inactive indexs.

        // well will add end markers or dead indexs to the array to treat the array as if it were a stack of dead and alive items.
        public static int fireEndMarker = 0;
        public static int rainEndInactiveIndexMarker = 0; // this is the dead index.

        // i've added the following two functions to forego having to search the list for id items.
        // instead the idea here is to keep the array in order on creation or destruction of a item.
        // since there are two groups of items we have a little extra then normal to keep things in order.
        // so we keep two dead markers 
        public static void ActivateParticle(ParticleID ID, Particle P)
        {
            if (ID == ParticleID.Fire)
            {  // this requires a copy and a assignment;
                data[rainEndInactiveIndexMarker] = data[fireEndMarker];
                data[fireEndMarker] = P;
                fireEndMarker++;
                rainEndInactiveIndexMarker++;
            }
            if (ID == ParticleID.Rain)
            {
                data[rainEndInactiveIndexMarker] = P;
                rainEndInactiveIndexMarker++;
            }
        }
        public static void DeActivateParticle(ParticleID ID, int index)
        {
            if (ID == ParticleID.Fire)
            {  // this requires two copys.
                data[index] = data[fireEndMarker - 1];
                data[fireEndMarker - 1] = data[rainEndInactiveIndexMarker - 1];
                fireEndMarker--;
                rainEndInactiveIndexMarker--;
            }
            if (ID == ParticleID.Rain)
            {
                if (index < rainEndInactiveIndexMarker - 1)
                {
                    data[index] = data[rainEndInactiveIndexMarker - 1];
                    rainEndInactiveIndexMarker--;
                }
                else
                {
                    rainEndInactiveIndexMarker--;
                }
            }
        }

        #endregion

        public static void Reset()
        {   //acts as a constructor as well
            if (texture == null)
            {   //set up a general texture we can draw dots with, if required
                texture = new Texture2D(Data.GDM.GraphicsDevice, 1, 1);
                texture.SetData<Color>(new Color[] { Color.White });
            }
            //reset particles to inactive state
            for (int i = 0; i < size; i++)
            { data[i].Id = ParticleID.Inactive; }

            //reset active count too
            fireEndMarker = 0;
            rainEndInactiveIndexMarker = 0; 
        }

        public static void Spawn(
            ParticleID ID,          //type of particle to spawn
            float X, float Y,       //spawn x, y position 
            float accX, float accY  //initial x, y acceleration
            )
        {   //create a stack instance to work on
            Particle P = new Particle();
            //setup P based on parameters
            P.physics.X = X; P.physics.preX = X;
            P.physics.Y = Y; P.physics.preY = Y;
            P.physics.accX = accX;
            P.physics.accY = accY;
            //set defaults for particle
            P.alpha = 1.0f;
            P.age = 0;
            P.color = Color.White;
            //pass the ID
            P.Id = ID;
            //setup particle based on ID
            if (P.Id == ParticleID.Fire)
            {   //fire is red, for instance
                P.color = Color.Red;
            }
            else if (P.Id == ParticleID.Rain)
            {   //rain is blue, this could be an animated sprite tho
                P.color = Color.Blue;
            }

            ActivateParticle(P.Id, P);

        }

        public static void Update()
        {   //step 1 - update/animate all the particles
            //step 2 - randomly spawn fire and rain particles

            //increase wind counter
            windCounter += 0.015f;
            //get left or right horizontal value for wind
            float wind = (float)Math.Sin(windCounter) * 0.03f;

            //store locals for this heap data
            int width = Data.GDM.PreferredBackBufferWidth;
            int height = Data.GDM.PreferredBackBufferHeight;

            for (int i = 0; i < rainEndInactiveIndexMarker; i++)
            {   //particle active, age + check id / behavior
                //create a local copy
                Particle P = data[i];

                bool isAlive = true;

                //age active particles
                P.age++;
                //affect particles based on id
                if (P.Id == ParticleID.Fire)
                {   //fire rises, gravity does not affect it
                    P.physics.accY -= gravity;
                    P.physics.accY -= 0.05f;
                    //has longer life than rain
                    if (P.age > 300)
                    {
                        //P.Id = ParticleID.Inactive;
                        DeActivateParticle(P.Id, i);
                        isAlive = false;
                    }
                    else
                    {
                        //use behavior field here for additional states
                    }
                }
                else if (P.Id == ParticleID.Rain)
                {   //rain falls (at different speeds)
                    P.physics.accY = Rand.Next(0, 100) * 0.001f;
                    //has shorter life than fire
                    if (P.age > 200)
                    {
                        //P.Id = ParticleID.Inactive;
                        DeActivateParticle(P.Id, i);
                        isAlive = false;
                    }
                    else
                    {
                        //use behavior field here for additional states
                    }
                }

                if (isAlive)
                {
                    //add gravity to push down
                    P.physics.accY += gravity;
                    //add wind to push left/right
                    P.physics.accX += wind;

                    //calculate velocity using current and previous pos
                    float velocityX = P.physics.X - P.physics.preX;
                    float velocityY = P.physics.Y - P.physics.preY;

                    //store previous positions (the current positions)
                    P.physics.preX = P.physics.X;
                    P.physics.preY = P.physics.Y;

                    //set next positions using current + velocity + acceleration
                    P.physics.X = P.physics.X + velocityX + P.physics.accX;
                    P.physics.Y = P.physics.Y + velocityY + P.physics.accY;

                    //clear accelerations
                    P.physics.accX = 0; P.physics.accY = 0;

                    //write local to heap
                    data[i] = P;

                    // cull particle if it goes off screen.
                    if (P.physics.X < 0 || P.physics.X > width || P.physics.Y < 0 || P.physics.Y > height) // shorten since these are inclusive conditions.
                        DeActivateParticle(P.Id, i);
                }
            }

            // create particles and disallow creation from exceeding buffer limit.
            int numberOfParticlesToSpawn = 100;
            if (rainEndInactiveIndexMarker + numberOfParticlesToSpawn < size)
            {
                for (int i = 0; i < numberOfParticlesToSpawn; i++)
                {   //step 2 - randomly spawn rain + fire
                    Spawn(ParticleID.Rain, Rand.Next(0, width), 0, 0, 0);
                    Spawn(ParticleID.Fire, Rand.Next(0, width), height, 0, 0);
                }
            }
        }

        public static void Draw()
        {   //open spritebatch and draw active particles
            Data.SB.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp);

            int s = size;
            for (int i = 0; i < rainEndInactiveIndexMarker; i++)
            {   //if particle is active, draw it
                if (data[i].Id > 0)
                {   //create vector2 that draw wants
                    Vector2 pos = new Vector2(data[i].physics.X, data[i].physics.Y);
                    //draw each particle as a sprite
                    Data.SB.Draw(texture,
                        pos,
                        drawRec,
                        data[i].color * data[i].alpha,
                        0,
                        Vector2.Zero,
                        1.0f, //scale
                        SpriteEffects.None,
                        i * 0.00001f);
                }   //layer particles based on index, as simple example
            }

            Data.SB.End();
        }
    }

    public class Game1 : Game
    {
        public static Stopwatch timer = new Stopwatch();

        // changed to show fps as ticks aren't intuitive
        public static float framesIterated = 0;
        public static float timeAccumulated = 0;
        public static float fps = 0;

        public Game1()
        {
            //set data game refs
            Data.GDM = new GraphicsDeviceManager(this);
            Data.GDM.GraphicsProfile = GraphicsProfile.HiDef;
            Data.CM = Content;
            Data.GAME = this;
            Content.RootDirectory = "Content";
            IsMouseVisible = true;
        }

        protected override void Initialize() { base.Initialize(); }

        protected override void LoadContent()
        {
            Data.SB = new SpriteBatch(GraphicsDevice);
            ParticleSystem.Reset(); //acts as constructor
        }

        protected override void UnloadContent() { } //lol

        protected override void Update(GameTime gameTime)
        {
            timer.Restart();
            base.Update(gameTime);
            ParticleSystem.Update();
        }

        protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(Color.CornflowerBlue);
            base.Draw(gameTime);
            ParticleSystem.Draw();
            timer.Stop();

            framesIterated++;
            timeAccumulated += (float)timer.Elapsed.TotalSeconds;
            if(timeAccumulated > 1.0f)
            {
                timeAccumulated = 0f;
                fps = framesIterated;
                framesIterated = 0;
            }
            Data.GAME.Window.Title = 
                "AoS Particle System Example by @_mrgrak" +
                "- fps: " + fps +
                " - total particles: " + ParticleSystem.rainEndInactiveIndexMarker +
                " / " + ParticleSystem.size +
                " markers fire: " + ParticleSystem.fireEndMarker + " rain: "+ ParticleSystem.rainEndInactiveIndexMarker
                ;
        }
    }
}

Ok so i did another one this time i made a few changes.
First i removed the double in place check for a single one.

Then i instanced the whole drawing part of the code to bypass spritebatch so now this is a mixed cpu physics gpu drawing particle system.

I stuck the quads on the gpu and just worked on the smaller instance structs then send them up to the gpu and let it do the drawing a lot of this is the same and recognizable from the previous examples.

Its pretty much the same as the last two. It’s again just faster, im getting a bit tired so im just going to post it as is.

This version bumps the speed up again from the last one by about 100 to 150 %.

Edited to bring the projection matrix and user resizing in line with the way spritebatch looks as well as add a couple things and notes.

using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Content;

// you will probably need to change the namespace in your own test run.
namespace CpuGpuParticles
{

    // structs
    //_________________________________________________________________________

    // this was the data struct i changed the name less typing.
    public static class Gv
    {
        public static Game1 game;
        public static ContentManager content;
        public static SpriteBatch spriteBatch;
        public static SpriteFont currentFont;
        public static GraphicsDeviceManager gdm;
        public static GraphicsDevice device { get { return gdm.GraphicsDevice; } }
        public static int ViewportWidth { get { return gdm.GraphicsDevice.Viewport.Width; } }
        public static int ViewportHeight { get { return gdm.GraphicsDevice.Viewport.Height; } }
        public static int BackBufferWidth { get { return gdm.GraphicsDevice.PresentationParameters.BackBufferWidth; } }
        public static int BackBufferHeight { get { return gdm.GraphicsDevice.PresentationParameters.BackBufferHeight; } }
        public static Action RegisterOnResizeCallback;
        public static void TriggerOnScreenResize(Object sender, EventArgs e){    Gv.RegisterOnResizeCallback();  }
    }

    public struct Physics
    {
        public float X, Y; //current position
        public float accX, accY; //accelerations
        public float preX, preY; //last position
    }

    public struct ParticleID
    {  // don't like enums even if it means a extra dot accessor ill take it.
        public byte value;
        public static ParticleID None { get { var t = new ParticleID(); t.value = 0; return t; } }
        public static ParticleID Fire { get { var t = new ParticleID(); t.value = 1; return t; } }
        public static ParticleID Rain { get { var t = new ParticleID(); t.value = 2; return t; } }
    }

    public struct Particle
    {   //uses physics struct as component
        public Physics physics;
        public ParticleID id;
        public short age;
    }

    // the instanceing and vertex structures.

    public struct InstanceData : IVertexType
    {
        public Vector3 InstancePosition;
        public Color InstanceColor;
        public static readonly VertexDeclaration VertexDeclaration;
        VertexDeclaration IVertexType.VertexDeclaration { get { return VertexDeclaration; } }
        static InstanceData()
        {
            var elements = new VertexElement[]
                {
                    new VertexElement(0, VertexElementFormat.Vector3, VertexElementUsage.Position, 1), // Note the 1 not a 0 used by the VertexPositionTexture UsageIndex.
                    new VertexElement(12, VertexElementFormat.Color, VertexElementUsage.Color, 1)
                };
            VertexDeclaration = new VertexDeclaration(elements);
        }
    }

    public struct VertexPositionTexture : IVertexType
    {
        public Vector3 Position;
        public Vector2 TextureCoordinate;
        public static readonly VertexDeclaration VertexDeclaration;
        VertexDeclaration IVertexType.VertexDeclaration { get { return 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);
        }
    }




    // ParticleSystem
    //_________________________________________________________________________



    public static class ParticleSystem
    {
        public static Texture2D Texture { get; set; }
        static Random Rand = new Random();
        public static Rectangle drawRec = new Rectangle(0, 0, 3, 3);
        public static float gravity = 0.1f;
        public static float windCounter = 0;
        public static int inactiveIndexMarker = 0; // this is the dead index.

        // I renamed size to this for sanity size is to vauge to remember. max size depends on the platform, 30k is baseline/lowend
        public static int MaxVisibleParticles { get; private set; }
        // initial size determined by maxvisibleparticles.
        public static Particle[] data;

        // well be adding some things for instancing.

        // Vertex data
        static VertexBuffer vertexBuffer;
        static IndexBuffer indexBuffer;
        static VertexBufferBinding vertexBufferBinding;

        // Instance data
        static InstanceData[] instanceData;
        static VertexBuffer instanceBuffer;
        static VertexBufferBinding instanceBufferBinding;

        // we need a effect and some matrices.
        public static Effect effect;
        static Matrix world, view, projection;

        static Vector2 pixelCoeff;
        public static Vector2 ParticleSize { get; set; }
        static double elapsedUpdateTime = 0;

        /// <summary>
        /// Acts as a initializer or lazy constructor.
        /// Prime the particle system this is typically called in the game1 load content method.
        /// We can pass a texture to be used for the particleTexture, for now though if we just pass null to the method it will create a dot texture and use that.
        /// </summary>
        public static void LoadUpTheParticleSystem(GraphicsDeviceManager graphics, Microsoft.Xna.Framework.Content.ContentManager Content, Texture2D particleTexture, int numberOfParticles, Vector2 defaultParticleSize)
        {
            var graphicsDevice = graphics.GraphicsDevice;
            var viewport = graphicsDevice.Viewport;

            MaxVisibleParticles = numberOfParticles;
            data = new Particle[MaxVisibleParticles];
            // one half a pixel on the shader in relation to -1 +1 gpu clip space, 
            // handy for later when you want a function to define a particles size in screen pixels.
            pixelCoeff = Vector2.One / viewport.Bounds.Size.ToVector2();

            // the particle size determines the size of the texture drawn.
            ParticleSize = defaultParticleSize;

            // we set up our camera the world view projection matrices here.
            // i need to line this up better with spritebatch in total to be honest so the back buffer changes properly but eh.
            SetUpWorldViewProjection();

            // we get a dot texture when particle texture is null.
            Texture = EnsureTextureValidOrMakeDefault(graphicsDevice, particleTexture);

            // load the effect shader.
            effect = Content.Load<Effect>("InstancingShader");
            effect.CurrentTechnique = effect.Techniques["ParticleInstancing"];

            // initialize the vertex and index buffers and initalize the default instances.
            IntializeParticleSystemBuffers(graphicsDevice);

            // ensure the marker is zero.
            inactiveIndexMarker = 0;

            // this is a callback on a user resize 
            Gv.RegisterOnResizeCallback += OnResize;
        }

        public static void Unload()
        {
            Texture.Dispose();
        }

        public static void OnResize()
        {
            SetUpWorldViewProjection();
        }

        public static void Draw(GameTime gameTime)
        {
            // these really only need to be called when something changes, the view changes alot typically in 3d every frame.
            effect.CurrentTechnique = effect.Techniques["ParticleInstancing"];
            effect.Parameters["World"].SetValue(world);
            effect.Parameters["View"].SetValue(view);
            effect.Parameters["Projection"].SetValue(projection);
            effect.Parameters["ParticleTexture"].SetValue(Texture);

            // set the altered instance buffer data to the device.
            instanceBuffer.SetData(instanceData);

            // Set the shader technique pass and then Draw
            effect.CurrentTechnique.Passes[0].Apply();
            Gv.device.DrawInstancedPrimitives(PrimitiveType.TriangleList, 0, 0, 2, inactiveIndexMarker);
        }

        public static void Update(GameTime gameTime)
        {
            elapsedUpdateTime = gameTime.ElapsedGameTime.TotalSeconds;

            int width = Gv.ViewportWidth;
            int height = Gv.ViewportHeight;

            windCounter += 0.015f;
            float wind = (float)Math.Sin(windCounter) * 0.03f;

            // create particles and disallow creation from exceeding buffer limit.
            int numberOfParticlesToSpawn = 100;
            if (inactiveIndexMarker + (numberOfParticlesToSpawn * 2) < MaxVisibleParticles)
            {
                for (int i = 0; i < numberOfParticlesToSpawn; i++)
                {
                    Spawn(ParticleID.Rain, Rand.Next(0, width), 0, 0, 0);
                    Spawn(ParticleID.Fire, Rand.Next(0, width), height, 0, 0);
                }
            }

            for (int currentIndex = 0; currentIndex < inactiveIndexMarker; currentIndex++)
            {
                Particle P = data[currentIndex];
                P.age--;
                if (P.id.value == ParticleID.None.value)
                {
                    throw new IndexOutOfRangeException("particle ["+currentIndex+"] id is none and less then the  inactiveIndexMarker ("+ inactiveIndexMarker + ").");
                }
                // check for all the conditions that allow us to cull calculations in one place this is a late check but if its offscreen 1 frame who cares.
                if (P.age < 0 || P.physics.X < 0 || P.physics.X > width || P.physics.Y < 0 || P.physics.Y > height)
                {
                    DeActivateParticle(P.id, currentIndex);
                    currentIndex--;
                }
                else
                {
                    if (P.id.value == ParticleID.Fire.value)
                    {   // fire rises, gravity does not affect it
                        P.physics.accY -= gravity;
                        P.physics.accY -= 0.05f;
                    }
                    if (P.id.value == ParticleID.Rain.value)
                    {   // rain falls (at different speeds)
                        P.physics.accY = Rand.Next(0, 100) * 0.001f;
                    }

                    P.physics.accY += gravity;
                    P.physics.accX += wind;

                    // calculate velocity using current and previous pos
                    float velocityX = P.physics.X - P.physics.preX;
                    float velocityY = P.physics.Y - P.physics.preY;

                    // store previous positions (the current positions)
                    P.physics.preX = P.physics.X;
                    P.physics.preY = P.physics.Y;

                    // set next positions using current + velocity + acceleration
                    P.physics.X = P.physics.X + velocityX + P.physics.accX;
                    P.physics.Y = P.physics.Y + velocityY + P.physics.accY;

                    // lear accelerations
                    P.physics.accX = 0; P.physics.accY = 0;

                    // write local to heap
                    data[currentIndex] = P;

                    // copy to instance buffer.
                    instanceData[currentIndex].InstancePosition = new Vector3(P.physics.X, P.physics.Y, currentIndex * 0.00001f + 1.0f);
                }
            }
        }

        public static void Spawn( ParticleID ID,  float X, float Y,  float accX, float accY )
        {
            Particle P = new Particle();
            P.physics.X = X;
            P.physics.Y = Y;
            P.physics.preX = X;
            P.physics.preY = Y;
            P.physics.accX = accX;
            P.physics.accY = accY;
            P.id = ID;
            //setup particle based on ID
            if (P.id.value == ParticleID.Fire.value)
            {
                //fire is red
                instanceData[inactiveIndexMarker].InstanceColor = Color.Red;
                P.age = 300;
            }
            if (P.id.value == ParticleID.Rain.value)
            {
                //rain is blue
                instanceData[inactiveIndexMarker].InstanceColor = Color.Blue;
                P.age = 200;
            }
            data[inactiveIndexMarker] = P;
            inactiveIndexMarker++;
        }

        public static void DeActivateParticle(ParticleID ID, int index)
        {
            data[index].id.value = ParticleID.None.value;
            if (index < inactiveIndexMarker - 1)
                data[index] = data[inactiveIndexMarker - 1];
            inactiveIndexMarker--;
        }

        private static void IntializeParticleSystemBuffers(GraphicsDevice graphicsDevice)
        {
            // set up the vertex stuff.

            // Create a single quad origin is dead center of the quad it could be top left instead.
            float halfWidth = ParticleSize.X /2 ;
            float halfHeight = ParticleSize.Y /2;
            float Left = -halfWidth; float Right = halfWidth;
            float Top = -halfHeight; float Bottom = halfHeight;

            VertexPositionTexture[] vertices = new VertexPositionTexture[4];
            vertices[0] = new VertexPositionTexture() { Position = new Vector3(Left, Top, 0f), TextureCoordinate = new Vector2(0f, 0f) };
            vertices[1] = new VertexPositionTexture() { Position = new Vector3(Left, Bottom, 0f), TextureCoordinate = new Vector2(0f, 1f) };
            vertices[2] = new VertexPositionTexture() { Position = new Vector3(Right, Bottom, 0f), TextureCoordinate = new Vector2(1f, 1f) };
            vertices[3] = new VertexPositionTexture() { Position = new Vector3(Right, Top, 0f), TextureCoordinate = new Vector2(1f, 0f) };

            // set up the indice stuff.

            int[] indices = new int[6];
            if (graphicsDevice.RasterizerState == RasterizerState.CullClockwise)
            {
                indices[0] = 0; indices[1] = 1; indices[2] = 2;
                indices[3] = 2; indices[4] = 3; indices[5] = 0;
            }
            else
            {
                indices[0] = 0; indices[1] = 2; indices[2] = 1;
                indices[3] = 2; indices[4] = 0; indices[5] = 3;
            }

            // set up the instance stuff

            instanceData = new InstanceData[MaxVisibleParticles];
            // set particles randomly
            Random rnd = new Random();
            for (int i = 0; i < MaxVisibleParticles; ++i)
            {
                instanceData[i].InstanceColor = Color.White;
                instanceData[i].InstancePosition = new Vector3
                    (
                    (rnd.Next(0, Gv.ViewportWidth) - Gv.ViewportWidth /2),
                    (rnd.Next(0, Gv.ViewportHeight) - Gv.ViewportHeight /2),
                    rnd.Next(1, MaxVisibleParticles + 1) / (float)(MaxVisibleParticles + 1)
                    );
            }

            // create buffers and set the data to them.
            indexBuffer = new IndexBuffer(graphicsDevice, typeof(int), 6, BufferUsage.WriteOnly);
            indexBuffer.SetData(indices);
            vertexBuffer = new VertexBuffer(graphicsDevice, VertexPositionTexture.VertexDeclaration, 4, BufferUsage.WriteOnly);
            vertexBuffer.SetData(vertices);
            instanceBuffer = new VertexBuffer(graphicsDevice, InstanceData.VertexDeclaration, MaxVisibleParticles, BufferUsage.WriteOnly);
            instanceBuffer.SetData(instanceData);

            // create the bindings.
            vertexBufferBinding = new VertexBufferBinding(vertexBuffer);
            instanceBufferBinding = new VertexBufferBinding(instanceBuffer, 0, 1);

            // set buffer bindings to the device
            graphicsDevice.SetVertexBuffers(vertexBufferBinding, instanceBufferBinding);
            graphicsDevice.Indices = indexBuffer;
        }

        public static Texture2D EnsureTextureValidOrMakeDefault(GraphicsDevice graphicsDevice, Texture2D texture)
        {
            Texture2D t;
            if (texture == null)
            {
                t = new Texture2D(graphicsDevice, 1, 1);
                t.SetData<Color>(new Color[] { Color.White });
            }
            else
                t = texture;
            return t;
        }

        public static void SetUpWorldViewProjection()
        {
            // when this is true the layer depth or z vertice coordinates are positive for forward depth or world position, its opposite when false.
            bool useSpriteBatchProjection = true;
            // Setup the worldViewProj matrix
            float aspect = Gv.ViewportWidth / Gv.ViewportHeight;
            var viewport = Gv.device.Viewport;

            world = Matrix.Identity;
            if (useSpriteBatchProjection)
            {
                // the below is equivillent to view = Matrix.Identity;
                view = Matrix.CreateLookAt(new Vector3(0, 0, 0), Vector3.Forward, Vector3.Up);
                projection = Matrix.CreateOrthographicOffCenter(0, viewport.Width, viewport.Height, 0, 0, -10);
            }
            else
            {
                // the below is equivalent to view = Matrix.Identity;
                view = Matrix.CreateLookAt(new Vector3(0, 0, 0), Vector3.Forward, Vector3.Up);
                projection = Matrix.CreateOrthographicOffCenter(0, viewport.Width, viewport.Height, 0, 0, 10);
            }
        }

    }

    //_________________________________________________________________________
    // Game
    //_________________________________________________________________________


    public class Game1 : Game
    {
        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;
        double elapsedUpdateTime = 0;
        Point minimizedWindowSize = new Point(1024, 768);
        public static float framesIterated = 0;
        public static float timeAccumulated = 0;
        public static float fps = 0;

        string msg =
                "AoS Particle System Example by @_mrgrak" +
                "- fps: Please wait a moment " +
                " - total particles: " + ParticleSystem.inactiveIndexMarker +
                " / " + ParticleSystem.MaxVisibleParticles +
                " inactive index marker: "
                ;

        public Game1()
        {
            graphics = new GraphicsDeviceManager(this);
            graphics.GraphicsProfile = GraphicsProfile.HiDef;
            graphics.PreferMultiSampling = false;
            graphics.HardwareModeSwitch = true;
            Window.AllowUserResizing = true;
            this.IsMouseVisible = true;
            this.IsFixedTimeStep = false;
            graphics.SynchronizeWithVerticalRetrace = false;
            //graphics.PreferredBackBufferWidth = minimizedWindowSize.X;
            //graphics.PreferredBackBufferHeight = minimizedWindowSize.Y;
            //graphics.ApplyChanges();
            Content.RootDirectory = "Content";
        }

        protected override void Initialize()
        {
            minimizedWindowSize = new Point(graphics.PreferredBackBufferWidth, graphics.PreferredBackBufferHeight);
            base.Initialize();
        }

        protected override void LoadContent()
        {
            Gv.spriteBatch = spriteBatch = new SpriteBatch(GraphicsDevice);
            Gv.gdm = graphics;
            Gv.content = Content;
            Gv.game = this;
            ParticleSystem.LoadUpTheParticleSystem(Gv.gdm, Content, null, 30000, new Vector2(3.0f, 3.0f));
            Window.ClientSizeChanged += Gv.TriggerOnScreenResize;
            // this is just so classes from anywere can register or self register to the window resize event.
            Gv.RegisterOnResizeCallback += OnResize;
        }

        protected override void UnloadContent()
        {
            ParticleSystem.Unload();
            Content.Unload();
        }

        public static void OnResize()
        {
        }

        public void UpdateUserFullScreenCheck()
        {
            if (Keyboard.GetState().IsKeyDown(Keys.F11))
            {
                Gv.gdm.IsFullScreen = true;
                Gv.gdm.ApplyChanges();
            }
            if (Keyboard.GetState().IsKeyDown(Keys.F12))
            {
                graphics.IsFullScreen = false;
                Gv.gdm.PreferredBackBufferWidth = minimizedWindowSize.X;
                Gv.gdm.PreferredBackBufferHeight = minimizedWindowSize.Y;
                Gv.gdm.ApplyChanges();
            }
        }

        protected override void Update(GameTime gameTime)
        {
            elapsedUpdateTime = gameTime.ElapsedGameTime.TotalSeconds;
            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed || Keyboard.GetState().IsKeyDown(Keys.Escape))
                Exit();

            UpdateUserFullScreenCheck();

            ParticleSystem.Update(gameTime);

            base.Update(gameTime);
        }

        protected override void Draw(GameTime gameTime)
        {
            Gv.device.Clear(Color.CornflowerBlue);

            ParticleSystem.Draw(gameTime);

            framesIterated++;
            timeAccumulated += (float)gameTime.ElapsedGameTime.TotalSeconds;
            if (timeAccumulated > 1.0f)
            {
                timeAccumulated = 0f;
                fps = framesIterated;
                framesIterated = 0;
                msg =
                                "AoS Particle System by @_mrgrak modified by will. " +
                                "  fps: " + fps +
                                "  total particles: " + ParticleSystem.inactiveIndexMarker +
                                " / " + ParticleSystem.MaxVisibleParticles +
                                "  inactive index marker: " 
                                ;
            }
            Gv.game.Window.Title = msg + ParticleSystem.inactiveIndexMarker;

            base.Draw(gameTime);
        }
    }
}
//Ah almost forgot you'll need the shader too.

//_________________________________________________________________
//_______________________ InstancingShader ___________________________
//_________________________________________________________________


// the shader model well use for dx or gl
#if OPENGL
#define SV_POSITION POSITION
#define VS_SHADERMODEL vs_3_0
#define PS_SHADERMODEL ps_3_0
#else
#define VS_SHADERMODEL vs_4_0
#define PS_SHADERMODEL ps_4_0
#endif



//______________________________________
// shader constants.
//______________________________________


//static const float PI = 3.14159;
//static const float PI2 = 6.28318;
//static const float EIGHT_PI = 25.13274;



//______________________________________
// shader variables.
// we set these up in game1.
//______________________________________


matrix World , View , Projection;



//______________________________________
// the shader textures and samplers.
// we set the texture to use thru game1
//______________________________________


Texture2D ParticleTexture;
sampler2D TexSampler = sampler_state
{
    Texture = <ParticleTexture>;
	//AddressU = Wrap;//AddressV = Wrap;//MinFilter = Anisotropic;//MagFilter = Anisotropic;//MipFilter = Point;
};




//______________________________________
// the shader structs well be defining.
// these match the vertex definitions in game1.
//______________________________________


struct VSInstanceInputSimple
{
    float3 InstancePosition : POSITION1; // the number used must match the vertex declaration.
    float4 InstanceColor : COLOR1;
};

struct VSVertexInputSimple
{
    float4 Position : POSITION0; 
    float2 TexCoord : TEXCOORD0;
};

struct VSOutputSimple
{
    float4 Position : SV_POSITION;
    float2 TexCoord : TEXCOORD0;
    float4 IdColor : COLOR0;
};




//______________________________________
// the vertex shaders.
//______________________________________


VSOutputSimple VertexShader01(in VSVertexInputSimple vertexInput, VSInstanceInputSimple instanceInput)
{
    VSOutputSimple output;
    float4x4 vp = mul(View, Projection);
    float4x4 wvp = mul(World, vp);
    float4 posVert = mul(vertexInput.Position, wvp);
    float4 posInst = mul(instanceInput.InstancePosition.xyz, wvp);
    output.Position = posVert + posInst;
    output.TexCoord = vertexInput.TexCoord;
    output.IdColor = instanceInput.InstanceColor;
    return output;
}



//______________________________________
// the pixel shaders.
//______________________________________


float4 PixelShader01(VSOutputSimple input) : COLOR0
{
      float4 col = tex2D(TexSampler, input.TexCoord);
      return float4(col.rgb * input.IdColor.rgb, col.a);
}



//______________________________________
// the techniques.
// we set this from game1.
//______________________________________


technique ParticleInstancing
{
    pass
    {
        VertexShader = compile VS_SHADERMODEL
        VertexShader01();
        PixelShader = compile PS_SHADERMODEL
        PixelShader01();
    }
};

I fixed the above one up a little with a correct projection matrix.
The next example will be a bit different.

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.

gpuTimedParticleExample

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);
        }
    }
}

the updated shader.

//_________________________________________________________________
//_______________________ InstancingShader ___________________________
//_________________________________________________________________


// the shader model well use for dx or gl
#if OPENGL
#define SV_POSITION POSITION
#define VS_SHADERMODEL vs_3_0
#define PS_SHADERMODEL ps_3_0
#else
#define VS_SHADERMODEL vs_4_0
#define PS_SHADERMODEL ps_4_0
#endif



//______________________________________
// shader constants.
//______________________________________


static const float PI = 3.14159;
static const float PI2 = 6.28318;
static const float EIGHT_PI = 25.13274;



//______________________________________
// shader variables.
// we set these up in game1.
//______________________________________


matrix World, View, Projection;

float TotalTime;


//______________________________________
// the shader textures and samplers.
// we set the texture to use thru game1
//______________________________________


Texture2D ParticleTexture;
sampler2D TexSampler = sampler_state
{
	Texture = <ParticleTexture>;
	//AddressU = Wrap;//AddressV = Wrap;//MinFilter = Anisotropic;//MagFilter = Anisotropic;//MipFilter = Point;
};




//______________________________________
// the shader structs well be defining.
// these match the vertex definitions in game1.
//______________________________________


struct VSInstanceInputSimple
{
	float3 InstancePosition : POSITION1; // the number used must match the vertex declaration.
	float4 InstanceColor : COLOR1;
};

struct VSVertexInputSimple
{
	float4 Position : POSITION0;
	float2 TexCoord : TEXCOORD0;
};

struct VSOutputSimple
{
	float4 Position : SV_POSITION;
	float2 TexCoord : TEXCOORD0;
	float4 IdColor : COLOR0;
};




//______________________________________
// the vertex shaders.
//______________________________________


VSOutputSimple VertexShader01(in VSVertexInputSimple vertexInput, VSInstanceInputSimple instanceInput)
{
	VSOutputSimple output;
    
    float4x4 vp = mul(View, Projection);
    float4x4 wvp = mul(World, vp);
    float4 posVert = mul(vertexInput.Position, wvp);
    float4 posInst = mul(instanceInput.InstancePosition.xyz, wvp);
    float4 pos = (posVert + posInst);
    output.Position = pos;
    output.IdColor = instanceInput.InstanceColor;
	output.TexCoord = vertexInput.TexCoord;

	return output;
}

VSOutputSimple VertexShader02(in VSVertexInputSimple vertexInput, VSInstanceInputSimple instanceInput)
{
    VSOutputSimple output;
    
    float4x4 vp = mul(View, Projection);
    float4x4 wvp = mul(World, vp);
    float4 vert = vertexInput.Position;
    vert.xy *= (instanceInput.InstanceColor.a + 0.2f) * 50.0f; //10.0f; scalar
    float4 posVert = mul(vert, wvp);
    float4 posInst = mul(instanceInput.InstancePosition.xyz, wvp);
    float4 pos = (posVert + posInst);
    output.Position = pos;
    output.IdColor = instanceInput.InstanceColor;
    output.TexCoord = vertexInput.TexCoord;

    return output;
}



//______________________________________
// the pixel shaders.
//______________________________________


float4 PixelShader01(VSOutputSimple input) : COLOR0
{
    float4 col = tex2D(TexSampler, input.TexCoord);
    float alpha = col.a * (1.0f - input.IdColor.a);
    float3 blendedColor = col.rgb * input.IdColor.rgb * (alpha * 1.4f);
    return float4(blendedColor, alpha);
}



//______________________________________
// the techniques.
// we set this from game1.
//______________________________________


technique ParticleInstancing
{
	pass
	{
		VertexShader = compile VS_SHADERMODEL
			VertexShader01();
		PixelShader = compile PS_SHADERMODEL
			PixelShader01();
        //AlphaBlendEnable = TRUE;
        //DestBlend = INVSRCALPHA;
        //SrcBlend = SRCALPHA;
    }
};

// in this version the alpha will increase the size of the particle.
technique ParticleInstancing02
{
    pass
    {
        VertexShader = compile VS_SHADERMODEL
			VertexShader02();
        PixelShader = compile PS_SHADERMODEL
			PixelShader01();
    }
};


// These will be the new additions to the shader from the last example.


struct VSInstanceInputTimed02
{
    float3 InstancePosition : POSITION1; // ahh wait waat .... like i must have accounted for this at some point and didn't change the cs file as well ... omg there is a lesson here.
    float InstanceTimeOrId : BLENDWEIGHT0;
    float4 InstanceColor : COLOR1;
};

struct VSVertexInputTimed02
{
    float4 Position : POSITION0; //SV_POSITION;
    float2 TexCoord : TEXCOORD0;
    //uint InstanceId : SV_InstanceId;
};

struct VSOutputTimed02
{
    float4 Position : SV_POSITION;
    float2 TexCoord : TEXCOORD0;
    float4 Color : COLOR0;
};


VSOutputTimed02 MainVSTimed02(in VSVertexInputTimed02 vertexInput, VSInstanceInputTimed02 instanceInput)
{
    VSOutputTimed02 output;
    output.TexCoord = vertexInput.TexCoord;
    float3 InstancePosition = instanceInput.InstancePosition;
    float InstanceTimeOrId = instanceInput.InstanceTimeOrId;
    float movingTime = InstanceTimeOrId * TotalTime + TotalTime;
    float4x4 vp = mul(View, Projection);
    float4x4 wvp = mul(World, vp);
    float4 posVert = mul(vertexInput.Position, wvp);
    float4 posInst = mul(instanceInput.InstancePosition.xyz, wvp);
    float4 pos = (posVert + posInst);
    output.Position = pos;
    // uncomment the below line for super trippy mode lol
    output.Position = pos * sin(movingTime * PI2);
    output.Position.x = pos.x * cos(movingTime * PI2) - pos.y * sin(movingTime * PI2);
    output.Position.y = pos.x * sin(movingTime * PI2) + pos.y * cos(movingTime * PI2);
    // change color
    output.Color = instanceInput.InstanceColor;
    return output;
}

float4 MainPSTimed02(VSOutputTimed02 input) : COLOR0
{
    float4 col = input.Color * tex2D(TexSampler, input.TexCoord);
    //clip(col.a - .05f); // straight clip alpha draws
    return col;
}



technique ParticleDrawingTimed02
{
    pass
    {
        VertexShader = compile VS_SHADERMODEL
        MainVSTimed02();
        PixelShader = compile PS_SHADERMODEL
        MainPSTimed02();
    }
};

The texture i used for the example right click on it and save image.

smokeSmall02b

1 Like