GL instanced shader problem.

Im using vs2017 on win7 i have created the same project for the crossplatform template gl and windows dx

So i have a example Instanced DX shader that i have trimmed down to simplify it so i can understand it better. this works under the Dx 4_0 profile with hi def set under the windows template.

However

The same shader under GL 3_0 on hi-def, while it does show a window and it doesn’t error or crash ect… Nothing is drawn all i get is a black screen.

Im at a loss as to why ?..

Here is the shader followed by the full code you need a texture to run the example.

, the shader ,

#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

static const float PI = 3.14159265359;
static const float EIGHT_PI = 25.1327412287;


float Life;
float TotalTime;
//int MaxNumOfParticles;
//float2 RadiusRange;

matrix WorldViewProjection;

Texture2D ParticleTexture : register(t0);
sampler TexSampler : register(s0);

//__________________________________________________________
//__________________________________________________________

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

struct VSInstanceInputSimple
{
    float3 RandomIntervals : POSITION1;
    float ParticleTime : BLENDWEIGHT0;
};

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

VSOutputSimple MainVSSimple(in VSVertexInputSimple vertexInput, VSInstanceInputSimple instanceInput)
{
    //VSOutput output = (VSOutput)0;
    VSOutputSimple output; //= (VSOutput)0;

    float3 randomIntervals = instanceInput.RandomIntervals;
    float particleTime = instanceInput.ParticleTime;

    float timeFraction = fmod((particleTime + TotalTime) / (float)Life, 1.0f);

    output.Position = vertexInput.Position;
    output.Position.xy += randomIntervals;
    output.Position.y += timeFraction * 100;
    output.Position.z = 0;//vertexInput.InstanceId * 10 / (float)MaxNumOfParticles;
    output.Position = mul(output.Position, WorldViewProjection);

    // Set colour of particle using the other two passed-in random intervals 
    float4 endColor = lerp(float4(0.0, 0.5, 1.0, 0.8), float4(1.0, 1.0, 1.0, 0.8), randomIntervals.y);
    float4 startColor = lerp(float4(1.0, 0.0, 0.0, 0.8), float4(0.3, 0.0, 1.0, 1.0), randomIntervals.z);

    output.Color = lerp(startColor, endColor, timeFraction);
    output.Color *= (-4 * pow(timeFraction - 0.5, 2) + 1);

    output.TexCoord = vertexInput.TexCoord;

    return output;
}

float4 MainPSSimple(VSOutputSimple input) : COLOR0
{
    return input.Color * ParticleTexture.Sample(TexSampler, input.TexCoord);
}

technique ParticleDrawingSimple
{
    pass P0
    {
        VertexShader = compile VS_SHADERMODEL MainVSSimple();
        PixelShader = compile PS_SHADERMODEL MainPSSimple();
    }
};

, the code , All the classes and structs have been placed in one cs file for simplicity…

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 Game1
{
    [StructLayout(LayoutKind.Sequential)]
    public struct InstanceData : IVertexType
    {
        public static readonly VertexDeclaration VertexDeclaration;

        public Vector3 RandomIntervals;
        public float Time;

        VertexDeclaration IVertexType.VertexDeclaration
        {
            get { return VertexDeclaration; }
        }

        static InstanceData()
        {
            var elements = new VertexElement[]
                {
                    new VertexElement(0, VertexElementFormat.Vector3, VertexElementUsage.Position, 0),
                    new VertexElement(12, VertexElementFormat.Single, VertexElementUsage.BlendWeight, 0)
                };
            VertexDeclaration = new VertexDeclaration(elements);
        }
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct VertexData : IVertexType
    {
        public static readonly VertexDeclaration VertexDeclaration;

        public Vector3 Position;
        public Vector2 TexCoords;

        VertexDeclaration IVertexType.VertexDeclaration
        {
            get { return VertexDeclaration; }
        }

        static VertexData()
        {
            var elements = new VertexElement[]
                {
                    new VertexElement(0, VertexElementFormat.Vector3, VertexElementUsage.Position, 0),
                    new VertexElement(12, VertexElementFormat.Vector2, VertexElementUsage.TextureCoordinate, 0),
                };
            VertexDeclaration = new VertexDeclaration(elements);
        }
    }

    public class Game1 : Game
    {
        GraphicsDeviceManager graphics;
        ParticleSystem02 particleSystem;
        Matrix worldViewProj;

        public Game1()
        {
            graphics = new GraphicsDeviceManager(this);
            graphics.GraphicsProfile = GraphicsProfile.HiDef;
            graphics.PreferMultiSampling = false;
            Window.AllowUserResizing = true;
            // Set the default resolution to be 1024x768
            graphics.PreferredBackBufferWidth = 1024;
            graphics.PreferredBackBufferHeight = 768;
            Content.RootDirectory = "Content";
        }

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

            // Setup the worldViewProj matrix
            float width = GraphicsDevice.PresentationParameters.BackBufferWidth;
            float height = GraphicsDevice.PresentationParameters.BackBufferHeight;
            float aspect = width / height;

            // Create our camera that's looking straight at the center
            Matrix viewMatrix = Matrix.CreateLookAt(new Vector3(0.0f, 0.0f, 5.0f), Vector3.Zero, Vector3.Up);

            // Create a projection matrix so that the world dimensions have the same aspect ratio as our window size
            Matrix projMatrix = Matrix.CreateOrthographic(aspect * 300, 300, 0.1f, 10000);

            worldViewProj = viewMatrix * projMatrix;
        }

        protected override void LoadContent()
        {
            Effect particlesEffect = Content.Load<Effect>("ParticleFx02");
            Texture2D particleTexture = Content.Load<Texture2D>("particle");

            particleSystem = new ParticleSystem02(particlesEffect)
            {
                ParticleSize = new Vector2(3.0f, 3.0f),
                ParticleTexture = particleTexture,
                Life = 20.0f,
                EmitRate = 12500,
                Radius = 180.0f,
                RadiusDeviation = new Vector2(40.0f, 100.0f),
            };

            particleSystem.RefreshParticleSystemBuffers(GraphicsDevice);

        }

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

        protected override void Update(GameTime gameTime)
        {
            base.Update(gameTime);

            particleSystem.UpdateParticleTime((float)gameTime.ElapsedGameTime.TotalSeconds);

            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed || Keyboard.GetState().IsKeyDown(Keys.Escape))
                Exit();
        }

        protected override void Draw(GameTime gameTime)
        {
            base.Draw(gameTime);

            GraphicsDevice.Clear(ClearOptions.DepthBuffer | ClearOptions.Target, Color.Black, 1.0f, 0);
            GraphicsDevice.BlendState = BlendState.Additive;
            GraphicsDevice.DepthStencilState = DepthStencilState.Default;

            particleSystem.DrawParticles(ref worldViewProj, GraphicsDevice);
        }
    }
    class ParticleSystem02
    {
        // Vertex data
        VertexBuffer vertexBuffer;
        IndexBuffer indexBuffer;
        VertexBufferBinding vertexBufferBinding;

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

        Effect particlesEffect;

        Vector2 radiusRange;

        float totalTime = 0;

        #region Properties
        public Vector2 ParticleSize { get; set; }
        public Texture2D ParticleTexture { get; set; }
        public float Life { get; set; }
        public uint EmitRate { get; set; }
        public float Radius { get; set; }
        public Vector2 RadiusDeviation { get; set; }

        public int MaxVisibleParticles
        {
            get; private set;
        }

        #endregion Properties

        #region Initilisation
        public ParticleSystem02(Effect effect)
        {
            particlesEffect = effect;
        }

        public void RefreshParticleSystemBuffers(GraphicsDevice graphicsDevice)
        {
            // Create a single quad centered at the origin
            float halfWidth = ParticleSize.X / 2;
            float halfHeight = ParticleSize.Y / 2;

            VertexData[] vertices = new VertexData[4];
            vertices[0].Position = new Vector3(-halfWidth, -halfHeight, 0);
            vertices[1].Position = new Vector3(halfWidth, -halfHeight, 0);
            vertices[2].Position = new Vector3(-halfWidth, halfHeight, 0);
            vertices[3].Position = new Vector3(halfWidth, halfHeight, 0);

            vertices[0].TexCoords = new Vector2(0.0f, 0.0f);
            vertices[1].TexCoords = new Vector2(1.0f, 0.0f);
            vertices[2].TexCoords = new Vector2(0.0f, 1.0f);
            vertices[3].TexCoords = new Vector2(1.0f, 1.0f);

            vertexBuffer = new VertexBuffer(graphicsDevice, VertexData.VertexDeclaration, 4, BufferUsage.WriteOnly);
            vertexBuffer.SetData(vertices);

            vertexBufferBinding = new VertexBufferBinding(vertexBuffer);

            int[] indices = new int[6];
            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);

            MaxVisibleParticles = (int)Math.Max(Math.Ceiling(Life * EmitRate), 1);

            instanceData = new InstanceData[MaxVisibleParticles];
            instanceBuffer = new VertexBuffer(graphicsDevice, InstanceData.VertexDeclaration, MaxVisibleParticles, BufferUsage.WriteOnly);
            instanceBufferBinding = new VertexBufferBinding(instanceBuffer, 0, 1);

            Random rnd = new Random();

            // Initialise our instance buffer
            for (int i = 0; i < MaxVisibleParticles; ++i)
            {
                // instance data float time
                instanceData[i].Time = -(i + 1) / (float)EmitRate;
                // instance data float position
                instanceData[i].RandomIntervals = new Vector3
                    (
                    (rnd.Next(0, 300) - 150),
                    -.001f,
                    rnd.Next(0, MaxVisibleParticles + 1) / (float)MaxVisibleParticles
                    );
            }

            instanceBuffer.SetData(instanceData);

            radiusRange = new Vector2(Radius + RadiusDeviation.X, Radius + RadiusDeviation.Y);
        }

        #endregion Initilisation

        public void UpdateParticleTime(float seconds)
        {
            // Potentially, you could adjust the number of 
            // particles rendered based on the time
            // However, here we're simply using the maximal number
            numInstancesToDraw = MaxVisibleParticles;

            // Update the total running time
            totalTime += seconds;
        }

        public void DrawParticles(ref Matrix worldViewProj, GraphicsDevice graphicsDevice)
        {
            particlesEffect.CurrentTechnique = particlesEffect.Techniques["ParticleDrawingSimple"];
            // Initialise our shader constants
            particlesEffect.Parameters["WorldViewProjection"].SetValue(worldViewProj);
            particlesEffect.Parameters["Life"].SetValue(Life);
            //particlesEffect.Parameters["RadiusRange"].SetValue(radiusRange);
            //particlesEffect.Parameters["MaxNumOfParticles"].SetValue(MaxVisibleParticles);
            particlesEffect.Parameters["TotalTime"].SetValue((float)totalTime);
            particlesEffect.CurrentTechnique.Passes[0].Apply();

            // Set textures and buffers
            graphicsDevice.Textures[0] = ParticleTexture;
            graphicsDevice.SamplerStates[0] = SamplerState.LinearClamp;
            graphicsDevice.SetVertexBuffers(vertexBufferBinding, instanceBufferBinding);
            graphicsDevice.Indices = indexBuffer;

            graphicsDevice.DrawInstancedPrimitives(PrimitiveType.TriangleList, 0, 0, 6, 0, 2, numInstancesToDraw);
        }
    }
}
struct VSVertexInputSimple
{
    float4 Position : POSITION0;
    float2 TexCoord : TEXCOORD0;
    //uint InstanceId : SV_InstanceId;
};


static InstanceData()
        {
            var elements = new VertexElement[]
                {
                    new VertexElement(0, VertexElementFormat.Vector3, VertexElementUsage.Position, 1),//
                    new VertexElement(12, VertexElementFormat.Single, VertexElementUsage.BlendWeight, 0)
                };
            VertexDeclaration = new VertexDeclaration(elements);
        }

.
//6 >> 4???
graphicsDevice.DrawInstancedPrimitives(PrimitiveType.TriangleList, 0, 0, 6, 0, 2, numInstancesToDraw);

This is was a example i found and trimmed down to make it simpler.
Didn’t see that but it’s not related to the actual problem as it still draws on dx.

   //graphicsDevice.DrawInstancedPrimitives(PrimitiveType.TriangleList, 0, 0, 4, 0, 2, numInstancesToDraw);
   graphicsDevice.DrawInstancedPrimitives(PrimitiveType.TriangleList, 0, 0, 2, numInstancesToDraw);

Still a black screen on gl and nothing is drawn.

Is this working for anyone else on the gl crossplatform desktop project template ?

xD
//return input.Color * ParticleTexture.Sample(TexSampler, input.TexCoord);//no idea
return input.Color * tex2D(TexSampler, input.TexCoord);

No joy works on dx but not gl.

float4 MainPSSimple(VSOutputSimple input) : COLOR0
{
    //input.Color * ParticleTexture.Sample(TexSampler, input.TexCoord);
    //input.Color.a = 1;
    //return input.Color;
    // even this wont work so im thinking its the semantics somehow.
    float4 col = float4(1,1,1,1);
    return col;
}

weird xD
test with desktopgl project

I even trimmed this down to practically do nothing.

VSOutputSimple MainVSSimple(in VSVertexInputSimple vertexInput, VSInstanceInputSimple instanceInput)
{
    //VSOutput output = (VSOutput)0;
    VSOutputSimple output;

    float4 col = float4(1, 1, 1, 1);

    output.Color = col;
    output.TexCoord = vertexInput.TexCoord;

    float3 randomIntervals = instanceInput.RandomIntervals;
    float particleTime = instanceInput.ParticleTime;

    float timeFraction = fmod((particleTime + TotalTime) / (float)Life, 1.0f);

    output.Position = vertexInput.Position;
    output.Position.xy += randomIntervals;
    output.Position.y += timeFraction * 100;
    output.Position.z = randomIntervals.z;//vertexInput.InstanceId * 10 / (float)MaxNumOfParticles;
    output.Position = mul(output.Position, WorldViewProjection);

    return output;
}

Everything i do works on the dx (windows) project but not on the gl one (the crossplatform desktop project).

It seems like it’s either the semantics or the instancebuffer isn’t actually performing the draws on gl. Could it have something to do with the position semantics for gl ? I really am bad with understanding how they work.

Maybe its just my card cant do instancing with open gl or something ?
If anyone trys this and this actually works for you on the desktop crossplatform project template please let me know.

This sounds familiar… I’ve also written instancing shader for my game using OpenGL, and whenever I start the game, I get the black screen too… until I move the camera in the game, after which everything renders normally. Never figured out the reason why.

I know, not very useful, but you could try to add a code which allows you to change your View matrix while the game is run just to see whether you get something on screen or not.

cross platform desktop project

Well that’s really weird on GL it runs no errors.
I just don’t get any polygons drawn for the instance draw calls.
What version of monogame are you running ?

im using 3.7.0.828

build 3.7.0.849

@willmotil Did you ever figure out the opengl problem? I am testing your code on OpenGL, and I get a black screen. It seems to work fine on DirectX.

It still seems to not work so i suppose no.

Pumpkin got it to work, i had assumed it was just a problem with my card.
But this card is like brand new dx12 and it’s still not working.

Im trying to simplify it more right now and get it to work.

If you find any clues as to whats going on post back.

@willmotil The version of MG you reported in a previous comment did not have PR #5912 merged yet, while the version pumpkin reported did. I think it fixed the issue you ran into. Make sure to test with a recent develop version of MG.

The version im using now is only a couple weeks old.

Does that version work on gl when you run it ?

Do i need to completely remake the project from scratch maybe ?

@Jjagg I have 2 test apps using @willmotil 's code… one OpenGL and one DirectX. The instancing on DirectX works perfectly. On OpenGL, it’s just a black / blank screen.

I am using a pretty recent build viz. 3.7.0-1465.

I’ll file a bug and attach both sample projects.

Bug filed. Issue is: https://github.com/MonoGame/MonoGame/issues/6280

Note, I have the exact same issue with my own particle system code which I recently updated to use instancing. The instancing works fine on DirectX, but doesn’t on OpenGL.

@YTN

i’ve tested your sample:

    static InstanceData()
    {
        var elements = new VertexElement[]
            {
                new VertexElement(0, VertexElementFormat.Vector3, VertexElementUsage.Position, 1),//0
                new VertexElement(12, VertexElementFormat.Single, VertexElementUsage.BlendWeight, 0)
            };
        VertexDeclaration = new VertexDeclaration(elements);
    } 

and

    float4 MainPSSimple(VSOutputSimple input) : COLOR0
    {
      //return input.Color * ParticleTexture.Sample(TexSampler, input.TexCoord);
      return input.Color * tex2D(TexSampler, input.TexCoord);
    }
1 Like

Ty PumpkinPudding.

Figures it would be a typo to look past over and over.

       VertexElementUsage.Position, 1),

Most ridiculous thing of all is that the 1 was in the shader the entire time. So i must of accounted for it at some point. Sorry man i feel stupid.

Edit:
I altered the code a bit and pumped out 2 million particles no problem, nice.
The altered working gl code is below it’s a little bit simpler then the previous example.

The shader

#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

//_________________________________________________________________
//__________________________ the gl shader_________________________
//_________________________________________________________________

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

//float Life;
float TotalTime;
matrix WorldViewProjection;

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

//__________________________________________________________

struct VSInstanceInputSimple
{
    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;
};

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

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

VSOutputSimple MainVSSimple(in VSVertexInputSimple vertexInput, VSInstanceInputSimple instanceInput)
{
    VSOutputSimple output;
    output.TexCoord = vertexInput.TexCoord;
    float3 InstancePosition = instanceInput.InstancePosition;
    float InstanceTimeOrId = instanceInput.InstanceTimeOrId;
    float movingTime = InstanceTimeOrId * TotalTime + TotalTime;
    float4 posVert = mul(vertexInput.Position, WorldViewProjection);
    float4 posInst = mul(InstancePosition.xyz, WorldViewProjection);
    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
    float4 startColor = lerp(float4(1.0, 0.0, 0.0, 1.0), float4(0.0, 0.0, 1.0, 1.0), InstancePosition.x);
    float4 endColor = lerp(float4(0.5, 0.5, 0.0, 1.0), float4(0.0, 1.0, 1.0, 1.0), InstancePosition.y);
    output.Color = lerp(startColor, endColor, (InstancePosition.z + InstanceTimeOrId));
    return output;
}

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

technique ParticleDrawingSimple
{
    pass
    {
        VertexShader = compile VS_SHADERMODEL MainVSSimple();
        PixelShader = compile PS_SHADERMODEL MainPSSimple();
    }
};

The game1 file

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using System;
using System.Runtime.InteropServices;

namespace GLParticleTestTrimed
{
    // the instanceDataType
    [StructLayout(LayoutKind.Sequential)]
    public struct InstanceData : IVertexType
    {
        public Vector3 instancePosition;
        public float instanceTimeOrId;
        public static readonly VertexDeclaration VertexDeclaration;
        static InstanceData()
        {
            var elements = new VertexElement[]
                {
                    new VertexElement(0, VertexElementFormat.Vector3, VertexElementUsage.Position, 1), // oh 0 so bad bad.
                    new VertexElement(12, VertexElementFormat.Single, VertexElementUsage.BlendWeight, 0)
                    //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 Game1 : Game
    {
        GraphicsDeviceManager graphics;
        ParticleSystem02 particleSystem;
        Matrix viewProjection;
        Texture2D particleTexture;
        Effect particlesEffect;

        // basically a elaborate timing device.
        float cyclePercentage = 0f;
        public double fullCycleTimeInSeconds = 9d; // you can speed it up or slow it down with this.
        double cycleTime = 0d;
        double elapsedUpdateTime = 0;
        double last = 0d;
        double now = 0d;

        public Game1()
        {
            graphics = new GraphicsDeviceManager(this);
            graphics.GraphicsProfile = GraphicsProfile.HiDef;
            graphics.PreferMultiSampling = false;
            Window.AllowUserResizing = true;
            graphics.PreferredBackBufferWidth = 1024;
            graphics.PreferredBackBufferHeight = 768;
            Content.RootDirectory = "Content";
        }

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

        protected override void LoadContent()
        {
            SetUpViewProjection();

            particlesEffect = Content.Load<Effect>("ParticleEffect03GL");
            particleTexture = Content.Load<Texture2D>("particle");

            particleSystem = new ParticleSystem02(particlesEffect)
            {
                ParticleSize = new Vector2(2.0f, 2.0f),
                ParticleTexture = particleTexture,
                InitialInstanceCount = 2000500, // Lets push it up to a Million
                //VisiblityDuration = 5.0f,
            };

            particleSystem.IntializeParticleSystemBuffers(GraphicsDevice);
        }

        public void SetUpViewProjection()
        {
            // Setup the worldViewProj matrix
            float width = GraphicsDevice.PresentationParameters.BackBufferWidth;
            float height = GraphicsDevice.PresentationParameters.BackBufferHeight;
            float aspect = width / height;
            Matrix viewMatrix = Matrix.CreateLookAt(new Vector3(0.01f, 0.01f, 5.0f), Vector3.Forward, Vector3.Up);
            Matrix projMatrix = Matrix.CreateOrthographic(aspect * width, height, 0.1f, 1000);
            viewProjection = viewMatrix * projMatrix;
        }

        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 / fullCycleTimeInSeconds);
            if (cycleTime > fullCycleTimeInSeconds)
            {
                cycleTime -= fullCycleTimeInSeconds;
            }

            particleSystem.UpdateParticleTime((float)(cyclePercentage));

            base.Update(gameTime);
        }

        protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(Color.Black);
            //GraphicsDevice.Clear(ClearOptions.DepthBuffer | ClearOptions.Target, Color.Black, 1.0f, 0);
            GraphicsDevice.BlendState = BlendState.Additive;
            GraphicsDevice.DepthStencilState = DepthStencilState.Default;

            particleSystem.DrawParticles(viewProjection, GraphicsDevice);

            base.Draw(gameTime);
        }
    }

    public class ParticleSystem02
    {
        // Vertex data
        VertexBuffer vertexBuffer;
        IndexBuffer indexBuffer;
        VertexBufferBinding vertexBufferBinding;

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

        int numInstancesToDraw;
        float totalTime = 0;

        Effect particlesEffect;

        public Vector2 ParticleSize { get; set; }
        public Texture2D ParticleTexture { get; set; }
        public uint InitialInstanceCount { get; set; }
        public int MaxVisibleParticles
        {
            get; private set;
        }
        //public float VisiblityDuration { get; set; }

        #region Initilisation
        public ParticleSystem02(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)
                    );
            }
            //
            instanceBuffer = new VertexBuffer(graphicsDevice, InstanceData.VertexDeclaration, MaxVisibleParticles, BufferUsage.WriteOnly);
            instanceBufferBinding = new VertexBufferBinding(instanceBuffer, 0, 1);
            instanceBuffer.SetData(instanceData);
        }

        #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 worldViewProj, GraphicsDevice graphicsDevice)
        {
            // Select the technique.
            particlesEffect.CurrentTechnique = particlesEffect.Techniques["ParticleDrawingSimple"];
            // Initialise our shader constants
            particlesEffect.Parameters["WorldViewProjection"].SetValue(worldViewProj);
            particlesEffect.Parameters["ParticleTexture"].SetValue(ParticleTexture);
            particlesEffect.Parameters["TotalTime"].SetValue((float)totalTime);
            // Set buffers to device
            graphicsDevice.SetVertexBuffers(vertexBufferBinding, instanceBufferBinding);
            graphicsDevice.Indices = indexBuffer;
            // Draw
            particlesEffect.CurrentTechnique.Passes[0].Apply();
            graphicsDevice.DrawInstancedPrimitives(PrimitiveType.TriangleList, 0, 0, 2, numInstancesToDraw);
        }
    }
}

The texture i used

1 Like

@PumpkinPudding Thanks… per the comment from @Jjagg in the issue I filed, looks like there’s a couple MG issues that need to be fixed. With the changes you guys recommended, looks like all is good now!

1 Like