API

Optimizing Solid Particle Systems

Previously, we saw how to use an SPS to optimize a scene containing multiple copies of a model mesh, provided the model properties remained fixed. Now we will look at ways to optimize when the particles are animated.

  1. Only update some particles on each render frame;
  2. Use the SPS vars property to re-use variables and limit garbage collection.

Restrict the Number of Particles Updated

For any solid particle animation, setParticles() is called on every render frame. When you have a large SPS with dozens of thousands of particles, you can improve performance by restricting the number of particles updated on each frame. There are three parameters you can use with setParticles() to do this. They are:

parameterdefinitiondefault value
start
(number) the index from where to start to iterate in the particles array
0
stop
(number) the index (included) where to stop to iterate in the particles array
nbParticles - 1
update
(boolean) to force the SPS mesh vertex buffer to be updated
true

If you pass a stop value greater than nbParticles - 1, the iteration will stop at nbParticles - 1 to prevent access to undefined elements.

Example 1: you may want to update your 10K particle mesh only every three frames

  • frame 1 : setParticles(0, 3300, false) computes everything for particles from 0 to 3300 and doesn't update the mesh.
  • frame 2 : setParticles(3301, 6600, false) computes everything for particles from 3301 to 6600 and doesn't update the mesh.
  • frame 3 : setParticles(6601, 9999, true) computes everything for particles from 6601 to 9999 and finally updates the mesh.

10,000 boxes updated per bunch of 3500 here: 10,000 Boxes Updated Per Bunch of 3500

Example 2: you could keep, say, the first 5,000 particles as unused ones and compute particle behavior only for the last 5,000 in your global pool.

Limit Garbage Collection

In JavaScript, the Garbage Collector is usually your friend: it takes care of cleaning up variables you no longer need and thus frees memory.
However, it can sometimes become an awkward friend because it may start cleaning while you are displaying a very smooth animation, taking the CPU for itself and leaving you with visible lag on the screen.
To avoid unpredictable GC pauses, it is best to avoid allocating new objects in loops that execute often, where particles are created or updated.
For example, updateParticle() is often called each frame for each particle or each particle vertex. Now imagine that you have an SPS with 30 000 particles. Suppose you write code like this to add a particle acceleration :

import { Vector3 } from "@babylonjs/core/Maths/math.vector";
SPS.updateParticle = function (particle) {
const accel = new Vector3(0, 0.5, 0);
particle.velocity = particle.velocity.add(accel);
// ...
};

Code like the above would create two new Vector3 objects each call, or 60 000 new objects over the course of the update. And although modern JS engines are quite good at cleaning up short-lived objects, it's best not to unnecessarily strain the GC this way.
Instead, make your update loops reuse variables that were declared outside the loop, and don't call any methods inside the loop that create new objects:

import { Vector3 } from "@babylonjs/core/Maths/math.vector";
let accel = new Vector3(0, 0.5, 0);
SPS.updateParticle = function (particle) {
particle.velocity.addInPlace(accel);
// ...
};

SPS also has a vars property, which is an object that you can use to store any variables you want to reuse. Any variables you store there will share the SPS' lifecycle, and get cleaned up when you dispose it:

import { Vector3 } from "@babylonjs/core/Maths/math.vector";
SPS.vars.tempVector = new Vector3(0, 0, 0);
// ...
SPS.dispose(); // cleans explicitly all your SPS.vars !

A good JS practice for the compiler is to never change the variable type once it has been set :

SPS.vars.myFloat = 0.01; // just keep setting float values to myFloat afterwards
SPS.vars.myInt = 5; // just keep setting integer values to myInt afterwards
SPS.vars.myString = "foo"; // just keep setting string values to myString afterwards

Here is an implementation of a simple particle AI called "flocking", a behavior based on association, cohesion, and separation. This example uses SPS.vars to allocate the memory used for results only once instead of using in-function temporary variables.

Solid Particle System Optimization