Jump to content
  • Advertisement
Sign in to follow this  
0x00000001

OpenGL Efficient data packing

This topic is 1882 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

Hi everyone I just had a few questions about OpenGL and performance.

 

I made a very simple 2D particle system, all the particles get attracted to a fixed point using gravitational attraction equations, so nothing too intensive.

 

For my data I used structs and packed it into a single buffer.

 typedef struct particle
    {
        GLfloat age;
        GLfloat pos[2];
        GLfloat vel[2];
        GLfloat col[3];

        void setPosition(float x, float y)
        {
            pos[0] = x;
            pos[1] = y;
        }

        void setColour(GLfloat r, GLfloat g, GLfloat b)
        {
            col[0] = r;
            col[1] = g;
            col[2] = b;
        }

        void setVelocity(GLfloat x, GLfloat y)
        {
            vel[0] = x;
            vel[1] = y;
        }

    }particle;

 I store the particles in a vector

 m_particles.resize(n);
 std::vector<particle>::iterator it;
    for(it = m_particles.begin(); it < m_particles.end(); ++it)
    {
       /// set position etc..

    }

Then the usual generating, binding and drawing vertex arrays code. All the calculations are done on CPU and I could get about 250,000 particles with good framerate.

 

 

Now to clean up the code and add more functionality, I decided to make a particle class.

class particle
{
public:

    particle(glm::vec3 _pos, glm::vec3 _vel, glm::vec3 _col, float _mass);
    ~particle();

    glm::vec3 getPosition()
    {
        return m_position;
    }

    glm::vec3 getVelocity()
    {
        return m_velocity;
    }

    void setColour(float _r,float  _g, float _b)
    {
        m_colour.x = _r;
        m_colour.y = _g;
        m_colour.z = _b;
    }


private:

    glm::vec3 m_position;
    glm::vec3 m_velocity;
    glm::vec3 m_colour;
    glm::vec3 m_force;
    glm::vec3 m_acceleration;

    float m_mass;
 

};

Then storing particles in a vector

 m_particles.reserve(n); ////////resize crashed the program so used reserve instead

    for(int i=0; i<n; ++i)
    {
        ///setting random values for each param
        particle p = particle(pos,vel,col, mass);
        m_particles.push_back(p);
    }

All the other code stays the same and I only needed to change the pointers to point to the correct offset in the buffer etc...

 

glVertexAttribPointer(col, 3, GL_FLOAT, GL_TRUE, sizeof(particle), (GLvoid *)NULL + (6*4)); //colour data

 

With this system I could only get about 80,000 particles with similar framerate and I kinda confused why.

 

There is extra private members in the class like force and acceleration that get passed into the single buffer but I removed them and still no difference. But is it ok if I leave that data in the buffer even if I never use it on the gpu side?

 

Should I always use structs for passing data to opengl?

 

My vector container holds the object:

std::vector<particle> m_particles;

 

I tried making it a pointer to the object but then I couldn't correctly pass the data to opengl. It would draw garbage, would making it a pointer even help in performance?

std::vector<particle *> m_particles;

 

Also I am using one buffer, is it better to split the buffers up so like a separate buffer for position,velocity,colour etc... 

 

Sorry for lots of questions I have tried researching but my problem is not a common error.

 

Thanks for your time.

 

Share this post


Link to post
Share on other sites
Advertisement
The change from
struct particle
to
class particle
doesn't make any difference. Making the data members into classes
glm::vec3 m_position;
would take some more storage space. And when you have all your data interleaved like this, iterating over the lot takes longer when there is more space between them since
sizeof(particle)
is larger.

You could probably increase performance by storing the positions separately from the other stuff, then drawing them would be more cache-friendly (look up 'data oriented design'). But the code would be less clear. Tradeoff time - maybe time to assess what parts are most critical for your program.

Share this post


Link to post
Share on other sites
One candidate for the slowdown is all that copying around of vector classes you are doing. Consider passing all those glm::vec3 by const reference (i.e. const glm::vec3 &v).

Another potential for slowdown is that the size of your class has grown significantly, since you started with 4 floats to represent position/velocity, and ended with 12 floats to represent position/velocity/acceleration/force. That takes you from 32 bytes per particle to 64 bytes - you've basically doubled the pressure on your cache.

Share this post


Link to post
Share on other sites

The change from

struct particle
to
class particle
doesn't make any difference. Making the data members into classes
glm::vec3 m_position;
would take some more storage space. And when you have all your data interleaved like this, iterating over the lot takes longer when there is more space between them since
sizeof(particle)
is larger.

You could probably increase performance by storing the positions separately from the other stuff, then drawing them would be more cache-friendly (look up 'data oriented design'). But the code would be less clear. Tradeoff time - maybe time to assess what parts are most critical for your program.

 

 

I'll try splitting the buffer and see what happens, but just curious about something.

Every single example/tutorial I have seen always stores the object in a vector but never a pointer to that object, why is that?

std::vector<particle *> /////////<--------------------------- would this make no performance difference.

 

 

One candidate for the slowdown is all that copying around of vector classes you are doing. Consider passing all those glm::vec3 by const reference (i.e. const glm::vec3 &v).

Another potential for slowdown is that the size of your class has grown significantly, since you started with 4 floats to represent position/velocity, and ended with 12 floats to represent position/velocity/acceleration/force. That takes you from 32 bytes per particle to 64 bytes - you've basically doubled the pressure on your cache.

 

Thanks for that, I am now passing all glm::vec3 by const reference and even did it for the floats. It managed to get like 3 frames per second more, nothing major but still better than nothing lol.

Share this post


Link to post
Share on other sites

I'll try splitting the buffer and see what happens, but just curious about something.
Every single example/tutorial I have seen always stores the object in a vector but never a pointer to that object, why is that?
std::vector<particle *> /////////<--------------------------- would this make no performance difference.

That might be more convenient if you want to have multiple lists of different particles (i.e. render all the 'fire' particles together in one way, all the 'water' ones together in a different way, but still simulate them all together in the same way). However, then they aren't necessarily consecutive in memory and you can't do your call to
glVertexAttribPointer(col, 3, GL_FLOAT, GL_TRUE, sizeof(particle), (GLvoid *)NULL + (6*4));
which incidentally looks dubious to me - the value of 6*4 might not work if a compiler does struct member packing differently, or you change the code later on.

If it's really performance-critical, I'd split out the positions into a float array, so it can be passed to GL in a simple, reliable way and store a pointer to the relevant index into this array within each instance of the particle class. All the other stuff I'd leave nice and tidy, wrapped up in particle.

Share this post


Link to post
Share on other sites

I tried making it a pointer to the object but then I couldn't correctly pass the data to opengl. It would draw garbage, would making it a pointer even help in performance?
std::vector<particle *> m_particles;


As you say doing this won't work as you want it to because all you are passing to OpenGL (if you pass this data) is a list of address values which OpenGL can't do anything with thus the garbage.

This would also slow down the simulation aspect as, instead of nice cache friendly (well, reasonably friendly, but I'll come to that) data for the CPU to chew on you instead require the CPU to fetch data from another location before working on it and do so for every single particle. This is likely to cause masses of cache misses and destroy your performance in the process.

Also I am using one buffer, is it better to split the buffers up so like a separate buffer for position,velocity,colour etc...


As long as your algorithm takes advantage of it, yes, this would be a much better solution.

The key to this is making best use of the cache; every time you fetch a value from memory you don't fetch a single value but the whole cache line it sits in; this means if you then access the value next to it the CPU can pull it from cache instead of having to take a trip back to main memory to fetch it (aka a cache miss which is VERY slow). CPUs tend to have another trick to help this as they will pre-fetch the next cache line of data they guess you will need next (forward and backwards walking of memory tends to get best results) so that you won't stall when you get to it.

Now, this works with the current setup you have but this is why I said 'reasonably friendly' when talking about the data.

Currently, assuming 'glm::vec3' really is a 3 component vector and not 4 under the hood (which in SSE land makes sense for alignment) your particle class takes up (4*3)*5 bytes or 60 bytes. I'm currently using an Ivy Bridge CPU and a single cache line on that is 64bytes in size which means your particle class only just fits in. (If vec3 is really 4 floats under the hood then it's 80 bytes long and you've blown two cache lines. Not that cache lines are wasted of course; in the 60byte example the next 4bytes would be the first 4 bytes of the next particle structure.

By thinking about the data you can split up the components into sections of memory and then read only what you need for each stage; so instead of doing each particle in turn's calculations (force -> velocity -> position for example) you do ALL the changes in velocity for the particles and then work out all the changes in position.

This ends up moving the same amount of data around but means you can do more ALU ops per cache line as each cache line can fit 4 particles worth of information on it; well assuming simple 'position', 'velocity', 'force' split - you could split it down further so that all the 'x' live together, all the 'y' lives together and so on (as the equation for moving them is, generally, completely independent for each component which means with the correct alignment (16byte memory address alignment) and usage of SSE you can calculate 4 particles worth of 'x' changes in one instruction, effectively 3 instructions for position(x,y,z) for 4 particles instead of (best case) 3 instructions per particle.

But that's taking it to the extreme end of things and brings with it the problem of how do you get the separate 'x','y' and 'z' streams efficiently back into a GPU ready format to render later.

Share this post


Link to post
Share on other sites

<snip>

 

 

Sorry for the late reply. Just got a chance to work on this project.  Thanks for all the info it was a great read and highly informative. 

I just tried a quick solution of splitting the data. So made a separate structure to hold particle position and colour only and passing that to the GPU. This turned out to be worse performance haha (probably due copying data from the class).

 

Anyway I think im just gonna leave this like it is and attempt to move all the calculations on GPU instead that should definitely boost performance.

 

Thanks again.

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

We are the game development community.

Whether you are an indie, hobbyist, AAA developer, or just trying to learn, GameDev.net is the place for you to learn, share, and connect with the games industry. Learn more About Us or sign up!

Sign me up!