Performance

Mercury Particle Engine has been developed with performance in mind. We really try to squeeze every bit of performance out of the code by create an effective and optimized structure that is easily extendable.
It can update and draw about 130,000 active particles on a dual core Athlon processor 3GHz. Even though it's a very high number, we hope to increase the active particle count in the future.

The active particle count depends on your computer and what kind modifiers you have enabled. Just drawing the particles without any modifiers will yield the best performance. As soon as you add some modifiers, the active particle count will decrease from the added load to the engine.

So how do we gain better performance? Well, modifiers are of course needed to create dynamic behavior and an explosion effect looks the best when a lot of particles are used. To increase performance, there is simply nothing to do but to decrease the budget on your emitters and use fewer modifiers.

Performance Strategies

Modifier consolidation

One strategy you can use to boost performance is to consolidate multiple modifiers into a single modifier, for example a single modifier that calculates colour, opacity, scale and applies a gravity force all at the same time. This will reduce the number of method calls significantly, especially if your particle system has a large number of active particles at any one time. The drawback of doing this is that if you want to maintain design time and content pipeline support you will need to go through the steps necessary to make your consolidated modifier available to the editor (see Advanced Topics)

An example of a consolidated colour\opacity\gravity modifier may look something like this:
public sealed class ColourOpacityGravityModifier : Modifier
{
    public float InitialOpacity;
    public float FinalOpacity;

    public Vector3 InitialColour;
    public Vector3 FinalColour;

    public Vector2 Gravity;

    public override unsafe void Process(float dt, Particle* particleArray, int count)
    {
        for (int i = 0; i < count; i++)
        {
            Particle* particle = (particleArray + i);

            particle->Colour.W = Calculator.LinearInterpolate(this.InitialOpacity, this.FinalOpacity, particle->Age);

            particle->Colour.X = Calculator.LinearInterpolate(this.InitialColour.X, this.FinalColour.X, particle->Age);
            particle->Colour.Y = Calculator.LinearInterpolate(this.InitialColour.Y, this.FinalColour.Y, particle->Age);
            particle->Colour.Z = Calculator.LinearInterpolate(this.InitialColour.Z, this.FinalColour.Z, particle->Age);

            particle->Momentum.X += (this.Gravity.X * dt);
            particle->Momentum.X += (this.Gravity.Y * dt);
        }
    }
}

Multi-threading

With ProjectMercury, particle effects can be updated on the CPU asynchronousley, which is especially useful on multi-core machines such as modern desktop processors and the Xbox 360. Aynchronous updating follows the standard Microsoft .NET pattern for asynchronous operations, exposing two extra methods: BeginUpdate() and EndUpdate(). The BeginUpdate method starts the update process in a seperate thread, and returns an IAsyncResult object which you must hold a reference to. The EndUpdate method blocks the calling thread until the updating thread is completed. Any code which runs between the BeginUpdate and EndUpdate methods is running in parallel, hopefully on a seperate CPU core although this is down to the schedular to determine. It is very important that once you have started updating a particle effect in a seperate thread, you do not alter that particle effect in any way until the two threads are synchronised again. Doing so will have very unpredictable and mostly bad results.

Edit** Threading support has currently been disabled due to performance issues on the xbox 360, we're hoping to get this working nicely as soon as possible!

A very basic example of updating a particle effect in a seperate thread:
ParticleEffect myParticleEffect = new ParticleEffect
{
    new Emitter
    {
        Budget = 1000,
        Term = 2f,
        ReleaseQuantity = 5,
        ReleaseSpeed = 150f,
        Modifiers = new ModifierCollection
        {
                new OpacityModifier
                {
                    Initial = 1f,
                    Ultimate = 0f
                },
                new ColourModifier
                {
                     Initial = Color.LimeGreen.ToVector3(),
                     Ultimate = Color.Tomato.ToVector3()
                }
        }
    }
};

public override void Update(GameTime gameTime)
{
    float dt = (float)gameTime.Elapsed.TotalSeconds;

    myParticleEffect.Trigger(...);

    IAsyncResult asyncToken = myParticleEffect.BeginUpdate(dt, null);

    // Any code here is running in parallel to the update...
    // But remember, do not trigger your particle effect or change any properties while it is updating!

    myParticleEffect.EndUpdate(asyncToken);
}

Last edited Jul 7, 2010 at 10:27 AM by JimJams, version 11