Using VBOs

Last modification on 2019-02-14

Development of ecosim's graphic library has started. At this point a decision needs to be made:

A Comparison

Below is a rough explanation of the stages needed for each method, along with some code snippets:

Old school OpenGL

Repeat the following for each frame:
  1. The program logic generates an array of structs containing positional and color data for each agents, stored in RAM
  2. A "draw agents" function is called, which iterates through the array:
    1. For each agent, a "draw agent" function is called:
      1. The data is copied from RAM, to video memory, then drawn to the screen, via:
        glColor3f(agent->r, agent->g, agent->b);
        glVertex3f(agent->x, agent->x, 0.0f);
        

Using VBOs

First, initialize the graphics setup:
  1. Create a master array, stored in RAM, for the agent data
  2. Setup a VBO with:
    GLuint vbo = 0;
    glGenBuffers(1, &vbo);
    glBindBuffer(GL_ARRAY_BUFFER, vbo);
    glBufferData(GL_ARRAY_BUFFER, sizeof(master_array), master_array, GL_DYNAMIC_DRAW);
    glBindBuffer(GL_ARRAY_BUFFER, 0);
    

  3. Setup the shader (which is to be loaded from a file into a char array when the program is initialized):
    GLuint vs = glCreateShader(GL_VERTEX_SHADER);
    glShaderSource(vs, 1, &vertex_shader, NULL);
    glCompileShader(vs);
    GLuint fs = glCreateShader(GL_FRAGMENT_SHADER);
    glShaderSource(fs, 1, &fragment_shader, NULL);
    glCompileShader(fs);
    

  4. Link the shader:
    GLuint agent_shader = glCreateProgram();
    glAttachShader(agent_shader, fs);
    glAttachShader(agent_shader, vs);
    glLinkProgram(agent_shader);
    

Repeat the following for each frame:
  1. The program logic generates an array of structs containing positional and color data for each agent, stored in RAM.
  2. A function is called which iterates through the array:
    1. For each agent, a translate function is called:
      1. The positional data the current agent is added to an array, stored in RAM
      2. The color data for the current is added to another array, stored in RAM
    2. The two arrays are then joined together into the master array and returned
  3. Run a shader, which dictates how the data is drawn, via:
    glUseProgram(agent_shader);
    

  4. Load all the data from the master array in RAM into the video memory, with:
    glBindBuffer(GL_ARRAY_BUFFER, vbo);
    glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(vertexData), vertexData);
    

  5. Setup the vertex attribute pointers, to allow the shaders to access the data:
    glEnableVertexAttribArray(0);
    glEnableVertexAttribArray(1);
    glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 0, 0);
    glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, 0, (void*)48);
    

  6. Draw the data, with:
    glDrawArrays(GL_POINTS, 0, 3);
    

  7. Disable the vertex attribute array, and stop using the shader:
    glDisableVertexAttribArray(0);
    glDisableVertexAttribArray(1);
    glUseProgram(0);
    

Pros and Cons

Here is a quick rundown of the pros and cons of both methods:
 ProsCons
"Old School" Method
  • Cleaner code
  • Easier to read code
  • Quicker to write
  • Copying vertex information one at once causes a bottle neck
  • Doesn't take advantage of parallel computing power of GPU
VBO Method
  • Takes advantage of the GPU's parallel computing power
  • No bottleneck
  • Ability to use shaders
  • More dense code
  • Adds slightly more complexity to the program

Conclusion

The main difference between these two methods is the way the data is transferred between the CPU and GPU. Without using VBOs, the GPU has to wait for data to be sent for the CPU before rendering, this occurs in a sequential way, one vertex after another, each time glVertex3f() is called. In contrast, by using VBOs, all the information needed by the GPU is loaded at once, which then allows the GPU to render the scene in parallel, which is much more efficient.

At any one time there could be hundreds, if not thousands of agents populating the environment. This could cause issues in further stages of development if we are waiting for the CPU to load data sequentially to the GPU. At later stages of development this could prove difficult to transition from one method to another, so playing it safe is the best choice.

Taking all this into account, the most sensible option seems to be using VBOs. Whilst the "old school" method is easier to implement, not taking advantage of the hardware would be to save a bit of time in the short-run would be a mistake.