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

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