Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / programming / performance

Flexible Particle System - The Container 2

5.00/5 (2 votes)
16 May 2014CPOL2 min read 6.3K  
Description of the implementation of my particle container

code and implementation

Last time, I wrote 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

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 one ParticleData
  • Generators and Updaters (stored also in ParticleSystem) operate on ParticleData

The Declaration

The gist is located here: gist.github.com/fenbf/BasicParticles

ParticleData class:

C++
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:

C++
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:

C++
void ParticleData::kill(size_t id)
{
    if (m_countAlive > 0)
    {
        m_alive[id] = false;
        swapData(id, m_countAlive - 1);
        m_countAlive--;
    }
}

Wake:

C++
void ParticleData::wake(size_t id)
{
    if (m_countAlive < m_count)
    {
        m_alive[id] = true;
        swapData(id, m_countAlive);
        m_countAlive++;
    }
}  

Swap:

C++
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 ifs in wake and kill could be removed?

Improvements

Configurable Attributes

SoA style object gives us a nice possibility to create various ParticleData configurations. I have not 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:

C++
ParticleData::mask = Params::Pos | Params::Vel | Params::Acc | Params::Color...

In the constructor memory for only selected param will be allocated.

C++
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.

C++
update() {
    // ..
    if (mask & Params::Vel)
        update ParticleData::vel array
    // ...

Please note that the problem arises 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.

Again: the gist is located here: gist.github.com/fenbf/BasicParticles

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)