My recent posts about GPU based particle systems under Swift (One Million Particles and Two Million Particles) have looked at fairly simple systems: the particles involved have only interacted with one other body.
This article ups the ante and discusses a far more complex system where each particle interacts with every other particle. Hiroki Sayama's Swarm Chemistry is a simulation consisting of particles with simple kinetic rules such as cohesion, alignment and pace keeping and exhibits emergent morphogenesis reminiscent of amoeba.
I've played with CPU based Swarm Chemistry simulations in the past, but always limited the total number of swarm members to under 1,000. Since every swarm member interrogates every other, these systems exhibit O(n²) complexity and rapidly begin to slow down.
With Metal, I've been able to create a kernel shader that can calculate and render 4,096 swarm members at 30fps - so that's 16,777,216 particle interactions per frame or 503,316,480 particle interactions per second! Pretty impressive stuff for a tablet computer!
My shader isn't a million miles away from previous versions, however this time, it contains a simple loop to iterate over every other particle in the system:
kernelvoid particleRendererShader(texture2d<float, access::write> outTexture [[texture(0)]],
texture2d<float, access::read> inTexture [[texture(1)]],
constdevice Particle *inParticles [[ buffer(0) ]],
device Particle *outParticles [[ buffer(1) ]],
uint id [[thread_position_in_grid]])
{
Particle inParticle = inParticles[id];
[...]
for (uint i = 0; i < 4096; i++)
{
if (i != id)
{
const Particle candidateNeighbour = inParticles[i];
// do swarm chemistry!
}
}
}
In this project, there are less particles that there are pixels in the image, so I can't use my recent trick of reusing the particle loop to add trails and a glow. There is a Boolean in my view controller that controls an additional shader for that, but it does impact performance.
There's no random number generation inside Metal, so I've used trigonometric functions on arbitrary particle properties to fake a random number between -1 and 1:
constfloat randomThree = fast::abs(fast::cos(candidateNeighbour.velocityX + candidateNeighbour.velocityY));
One other experiment I tried was to have a total population of 8,192 swarm members but only interrogate half of them in the shader loop. This, to my eye, gave a very similar effect with double the particles at a very reasonable 20 frames per second:
All my timings have been on an iPad Air 2. I can run the 4,096 member system at 20fps on my iPhone 6 which is still astounding!
There's no user interface to this app - I'll be working on that over the next few weeks.
All the source code for tis project is available at my GitHub repository here.