Last Update:
Flexible particle system - The Container 2
Table of Contents
Last time I’ve written about problems that we can face when designing a particle container. This post will basically show my current (basic - without any optimizations) implementation. I will also write about possible improvements.
The Series
- Initial Particle Demo
- Introduction
- Particle Container 1 - problems
- Particle Container 2 - implementation (this post)
- Generators & Emitters
- Updaters
- Renderer
- Introduction to Optimization
- Tools Optimizations
- Code Optimizations
- Renderer Optimizations
- Summary
Introduction
Basic design:
ParticleData
class which represents the container- Allocates and manages memory for a given max number of particles
- Can kill and activate a particle
- Active particles are in the front of the buffer, stored continuously
- Each parameter is stored in a separate array. Most of them are 4d vectors
- No use of
std::vectors
. The reason: they are very slow in debug mode. Another thing is that I know the max size of elements so managing memory is quite simple. And also I have more control over it.
- So far GLM library is used, but it might change in the future
ParticleSystem
holds oneParticleData
- Generators and Updaters (stored also in
ParticleSystem
) operate onParticleData
The declaration
The gist is located here: gist.github.com/fenbf/BasicParticles
ParticleData class
class ParticleData
{
public:
std::unique_ptr<glm::vec4[]> m_pos;
std::unique_ptr<glm::vec4[]> m_col;
std::unique_ptr<glm::vec4[]> m_startCol;
std::unique_ptr<glm::vec4[]> m_endCol;
std::unique_ptr<glm::vec4[]> m_vel;
std::unique_ptr<glm::vec4[]> m_acc;
std::unique_ptr<glm::vec4[]> m_time;
std::unique_ptr<bool[]> m_alive;
size_t m_count{ 0 };
size_t m_countAlive{ 0 };
public:
explicit ParticleData(size_t maxCount) { generate(maxCount); }
~ParticleData() { }
ParticleData(const ParticleData &) = delete;
ParticleData &operator=(const ParticleData &) = delete;
void generate(size_t maxSize);
void kill(size_t id);
void wake(size_t id);
void swapData(size_t a, size_t b);
};
Notes:
- So far
std::unique_ptr
are used to hold raw arrays. But this will change, because we will need in the future to allocate aligned memory.
Implementation
Generation:
void ParticleData::generate(size_t maxSize)
{
m_count = maxSize;
m_countAlive = 0;
m_pos.reset(new glm::vec4[maxSize]);
m_col.reset(new glm::vec4[maxSize]);
m_startCol.reset(new glm::vec4[maxSize]);
m_endCol.reset(new glm::vec4[maxSize]);
m_vel.reset(new glm::vec4[maxSize]);
m_acc.reset(new glm::vec4[maxSize]);
m_time.reset(new glm::vec4[maxSize]);
m_alive.reset(new bool[maxSize]);
}
Kill:
void ParticleData::kill(size_t id)
{
if (m_countAlive > 0)
{
m_alive[id] = false;
swapData(id, m_countAlive - 1);
m_countAlive--;
}
}
Wake:
void ParticleData::wake(size_t id)
{
if (m_countAlive < m_count)
{
m_alive[id] = true;
swapData(id, m_countAlive);
m_countAlive++;
}
}
Swap:
void ParticleData::swapData(size_t a, size_t b)
{
std::swap(m_pos[a], m_pos[b]);
std::swap(m_col[a], m_col[b]);
std::swap(m_startCol[a], m_startCol[b]);
std::swap(m_endCol[a], m_endCol[b]);
std::swap(m_vel[a], m_vel[b]);
std::swap(m_acc[a], m_acc[b]);
std::swap(m_time[a], m_time[b]);
std::swap(m_alive[a], m_alive[b]);
}
Hints for optimizations:
- maybe full swap is not needed?
- maybe those
if
’s inwake
andkill
could be removed?
Improvements
Configurable attributes
SoA style object gives use a nice possibility to create various ParticleData
configurations. I do not have implemented it in current class, but I’ve used it before in some other system.
The simplest idea is to hold a mask
of configured params:
ParticleData::mask = Params::Pos | Params::Vel | Params::Acc | Params::Color...
In the constructor memory for only selected param will be allocated.
generate() {
// ..
if (mask & Params::Vel)
allocate ParticleData::vel array
// ...
The change is also needed in updaters and generators: briefly we will be able to update only active parameters. A lot of if
statements would be needed there. But it is doable.
update() {
// ..
if (mask & Params::Vel)
update ParticleData::vel array
// ...
Please not that the problem arise when one param depends on the other.
Limitations: there is a defined set of parameters, we only can choose a subset.
The second idea (not tested) would be to allow full dynamic configuration. Instead of having named set of available parameters we could store a map of <name, array>
. Both name and type of param (vector, scalar, int) would be configurable. This would mean a lot of work, but for some kind of an particle editor this could be a real benefit.
What’s Next
In the next article I will touch particle generation and update modules.
Read next: Generators & Emitters
Again: the gist is located here: gist.github.com/fenbf/BasicParticles
I've prepared a valuable bonus for you!
Learn all major features of recent C++ Standards on my Reference Cards!
Check it out here: