Click here to Skip to main content
15,881,381 members
Articles / General Programming / Performance

Flexible Particle System - The Container 2

Rate me:
Please Sign up or sign in to vote.
5.00/5 (2 votes)
16 May 2014CPOL2 min read 6.2K   4  
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)


Written By
Software Developer
Poland Poland
Software developer interested in creating great code and passionate about teaching.

Author of C++17 In Detail - a book that will teach you the latest features of C++17!

I have around 11 years of professional experience in C++/Windows/Visual Studio programming. Plus other technologies like: OpenGL, game development, performance optimization.

In 2018 I was awarded by Microsoft as MVP, Developer Technologies.

If you like my articles please subscribe to my weekly C++ blog or just visit www.bfilipek.com.

Comments and Discussions

 
-- There are no messages in this forum --