perf(particlesys): Batch same type particles to improve particle rendering performance by 15 - 30% - #3155
Conversation
PR Summary by Qodoperf(particlesys): Batch consecutive particle systems to reduce draw calls
AI Description
Diagram
High-Level Assessment
Files changed (4)
|
Code Review by Qodo
1.
|
|
I think W3DParticleSys.h was supposed to have been moved to core in #3014 but wasn't... |
Outside the scope of this change, but i noticed that too. |
62747ca to
315c30c
Compare
|
Ah stupid VC6 loop handling, will just fixing now |
315c30c to
0df2c07
Compare
|
Fixed VC6 build and issues mentioned by the bot |
| W3DParticleSystemManager::W3DParticleSystemManager() | ||
| { | ||
| m_batchBillboard = true; | ||
| m_batchParticleSystems = true; |
There was a problem hiding this comment.
Perhaps use a define instead as this is always true?
There was a problem hiding this comment.
it's better to not use defines and to use constants instead if something is not changing.
There was a problem hiding this comment.
It is always true. What us the point of this bool?
| m_pointGroup->Set_Flag( PointGroupClass::TRANSFORM, true ); // transform to screen space | ||
|
|
||
| switch( sys->getShaderType() ) | ||
| if ( sys->getVolumeParticleDepth() > 1 ) |
There was a problem hiding this comment.
Would it make things simpler/more consistent if volume particles were batched as well?
There was a problem hiding this comment.
volume particles work in a different way as they have multiple surfaces.
The batching only really works with billboarded / flat particles
There was a problem hiding this comment.
Are they really that different though, they appear to use the same input arrays. The only difference is that they call a different render function and render more surfaces
There was a problem hiding this comment.
Would need to find something that uses volume particles, all my current tests don't show any activity down that path
There was a problem hiding this comment.
Found that the microwave tank uses them.
0df2c07 to
0e8506d
Compare
xezon
left a comment
There was a problem hiding this comment.
First review pass. Reference counting needs simplification.
| if (sys->isUsingDrawables()) | ||
| continue; | ||
|
|
||
| // TheSuperHackers @perf 16/08/2026 Mauller Test if particle system has any visible particles that can be drawn |
| const Coord3D* pos = vp->getPosition(); | ||
| Real psize = vp->getSize(); | ||
|
|
||
| //Test if particle is at the screen or terrain edges. |
There was a problem hiding this comment.
This test exists more than once in this file. Can consolidate and simplify.
There was a problem hiding this comment.
I tried by having the visibility test section put particles that are visible into a list, but the performance was lower than just testing again and running through all particles later on.
There is likely an element of memory locality to it which putting pointers to the particle system on a list loses.
There was a problem hiding this comment.
Fixed by setting the culled variable on the particles, they always had the variable but it has not been used till now.
|
|
||
| enum { MAX_POINTS_PER_GROUP = 512 }; | ||
|
|
||
| TextureClass *m_batchTexture; ///< the texture used as the drawing surface for batched particle draws |
| W3DParticleSystemManager::W3DParticleSystemManager() | ||
| { | ||
| m_batchBillboard = true; | ||
| m_batchParticleSystems = true; |
There was a problem hiding this comment.
It is always true. What us the point of this bool?
| // TheSuperHackers @perf 09/08/2026 Ronin/Mauller Implement batched rendering for similar particles | ||
| // Particles with the same blending will now be batched onto a single texture surface before being drawn | ||
| // If a different particle type appears before the batch is filled, the previous batch will be drawn first | ||
| TextureClass *texture = W3DDisplay::m_assetManager->Get_Texture( sys->getParticleTypeName().str() ); |
| Bool m_batchBillboard; | ||
| Bool m_batchParticleSystems; | ||
| ParticleSystemInfo::ParticleShaderType m_batchShaderType; | ||
| Int m_pointCount; |
There was a problem hiding this comment.
made it unsigned and put it back within doParticles() outside of the particle system list loop
| m_batchBillboard = true; | ||
| m_batchParticleSystems = true; | ||
| m_batchShaderType = ParticleSystemInfo::INVALID_SHADER; | ||
| m_pointCount = 0; |
There was a problem hiding this comment.
m_pointCount does not need to be a class member. Is used in one function. Can be passed as argument to the flush function. m_pointCount as class member also poses risk from early returns.
There was a problem hiding this comment.
Yeah when looking at it again i forgot that we flush the last batch anyway and don't batch between calls to doParticles()
going to make it a function member again.
There was a problem hiding this comment.
This function now does these culling tests 3 times. Can we optimize this?
There was a problem hiding this comment.
Something else i could try is adding a flag to the particle which the first visibility test sets to say if the particle is visible on screen.
There was a problem hiding this comment.
fixed by setting and using the culled variable on the particles.
0e8506d to
4b5ec70
Compare
|
Partially addressed review comments, things still appear to give a good 15 - 30% more performance with the tweaks. |
…visibility skip until the next upstream rebase

This can be squash merged
This PR is separated into two commits to aid reviewing.
The initial commit is a small refactor to make the diff slightly cleaner on the second commit.
The second commit implements the particle batching created by Ronin and cleaned up by myself.
When testing we see a 15-20% performance improvement on average. But this may be higher in some scenarios.
EDIT: An early particle visibility test has added 2 - 5% more performance on top of the original.
The batching works by creating a common texture that the particle effects are drawn to before being sent to the GPU. This reduces the number of draw calls, thus improving rendering performance.
Only particles with the same material and shader type can be batched to the texture, so once particles of a different type are observed. The system will flush the prior batch and start a new one based on the new particle type.
As draw order is preserved, particles adhere to their original layering.
Some performance comparison images
Using the chemical spray as the source of particles, all tractors are using their AoE spray ability when these images were taken.
Before:

After:

Using the firewall and flamethrower effect of the flame tank, a circle of flame tanks are creating firewalls in the centre while extra flamers spray fire into the centre of the inferno.
Before:

After:
