Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do OpenGL's buffers work?

Tags:

c++

opengl

I don't understood how do OpenGL's buffers work. I learn OpenGL, by means of OpenGL Redbook 8th edition. For example, I have an array of position, an array of color and an array of indices:

static const GLfloat strip_position[] =
    {
        -4.0f,  0.0f, -1.0f, 1.0f,  //0
        -3.5f, -1.0f, -1.0f, 1.0f,  //1
        -3.0f,  0.0f, -1.0f, 1.0f,  //2
        -2.5f, -1.0f, -1.0f, 1.0f,  //3
        -2.0f,  0.0f, -1.0f, 1.0f,  //4
        -1.5f, -1.0f, -1.0f, 1.0f,  //5
        -1.0f,  0.0f, -1.0f, 1.0f,  //6
        -0.5f, -1.0f, -1.0f, 1.0f,  //7
         0.0f,  0.0f, -1.0f, 1.0f   //8
    };
static const GLfloat strip_colors[] =
    {
        1.0f, 1.0f, 1.0f, 1.0f,
        1.0f, 1.0f, 0.0f, 1.0f,
        1.0f, 0.0f, 1.0f, 1.0f,
        1.0f, 0.0f, 0.0f, 1.0f,
        0.0f, 1.0f, 1.0f, 1.0f,
        0.0f, 1.0f, 0.0f, 1.0f,
        0.0f, 0.0f, 1.0f, 1.0f,
        1.0f, 0.0f, 1.0f, 1.0f,
        1.0f, 1.0f, 0.0f, 1.0f,
    };

static const GLushort strip_indices[] =
{
    0, 1, 2, 3, 4, 5, 6, 7, 8
};

Good.Then I create Vertex Array Object is follows:

    GLuint vao[1]; // vertex array object
    glGenVertexArrays(1, vao);
    glBindVertexArray(vao[0]);

In my understanding, first parameter (GLsizei n) is number of an arrays of position(or coordinate of vertices of ONE my object). Then I create Element Array Buffer is follows:

GLuint ebo[1]; // element buffer object
glGenBuffers(1, ebo);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo[0]);
glBufferData(
             GL_ELEMENT_ARRAY_BUFFER, 
             sizeof(strip_indices), 
             strip_indices, 
             GL_STATIC_DRAW
);

Then I create Vertex Buffer Object is follows:

GLuint vbo[1]; // vertex buffer object
glGenBuffers(1, vbo);
    glBindBuffer(GL_ARRAY_BUFFER, vbo[0]);
    glBufferData(
                 GL_ARRAY_BUFFER, 
                 sizeof(strip_position) + sizeof(strip_colors), 
                 NULL, 
                 GL_STATIC_DRAW
    );
    glBufferSubData(
                    GL_ARRAY_BUFFER, 
                    0,                      //offset
                    sizeof(strip_position), //size date
                    strip_position          //data
    );
    glBufferSubData(
                    GL_ARRAY_BUFFER, 
                    sizeof(strip_position), //offset
                    sizeof(strip_colors),   //size data
                    strip_colors               //data
    );

Next I call glVertexAttribPointer() is follows:

glVertexAttribPointer(
                      0,         //index
                      4,         //size
                      GL_FLOAT,  //type
                      GL_FALSE,  //normalized
                      0,         //stride
                      NULL       //pointer
);
glVertexAttribPointer(
                      1, 
                      4, 
                      GL_FLOAT, 
                      GL_FALSE, 
                      0, 
                      (const GLvoid*)sizeof(strip_position)
);
glEnableVertexAttribArray(0);
glEnableVertexAttribArray(1);

What does that function?(glVertexAttribPointer() and glEnableVertexAttribArray())
Okay. I finished initialize a my data. Now I can draw it's follows:

glBindVertexArray(vao[0]);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo[0]);
glDrawElements(GL_TRIANGLE_STRIP, 8, GL_UNSIGNED_SHORT, NULL);

How OpenGL understood, which buffer need to use and where it is? Word "bind" is mean a relation? i.e. something bind with something? And If I want to display a two object, what do I do? For example, I have a two arrays of position, a two arrays of position and a two arrays of indices?

static const GLfloat TWOstrip_colors[] =
{
    1.0f, 1.0f, 1.0f, 1.0f,
    1.0f, 1.0f, 0.0f, 1.0f,
    1.0f, 0.0f, 1.0f, 1.0f,
    1.0f, 0.0f, 0.0f, 1.0f,
    0.0f, 1.0f, 1.0f, 1.0f,
    0.0f, 1.0f, 0.0f, 1.0f,
    0.0f, 0.0f, 1.0f, 1.0f,
    1.0f, 0.0f, 1.0f, 1.0f,
    1.0f, 1.0f, 0.0f, 1.0f
};
static const GLfloat TWOstrip_colors[] =
    {
        1.0f, 1.0f, 1.0f, 1.0f,
        1.0f, 1.0f, 0.0f, 1.0f,
        1.0f, 0.0f, 1.0f, 1.0f,
        1.0f, 0.0f, 0.0f, 1.0f,
        0.0f, 1.0f, 1.0f, 1.0f,
        0.0f, 1.0f, 0.0f, 1.0f,
        0.0f, 0.0f, 1.0f, 1.0f,
        1.0f, 0.0f, 1.0f, 1.0f,
        1.0f, 1.0f, 0.0f, 1.0f,
    };

static const GLushort TWOstrip_indices[] =
{
    0, 1, 2, 3, 4, 5, 6, 7, 8
};

How do this?

like image 222
Ivan Avatar asked Dec 25 '22 16:12

Ivan


2 Answers

OpenGL has the notion of so called objects. Those are not models or geometrical objects, but encapsulations of internal state. If you're familiar with object oriented programming, and the C++ STL OpenGL objects can be thought of kind of class instances.

The call glGenBuffers(count, out_names) could be roughtly interpreted into something like

std::map<GLuint, openglobject*> bufferobjects;

glGenBuffers(GLuint count, std::vector<GLuint> *out_names)
{
    out_names->resize(count);
    for(int i=0; i < count; i++) {
        GLuint name = get_next_free_handle_ID();
        bufferobjects[name] = NULL;
        out_names.set(i, name);
    }
}

So what it does is, it reserves a handle ID (OpenGL calls them names) and allocates a slot for it in the internal mapping between handles and bufferobject instance pointers.

The call glBindBuffer actually creates the buffer object, something like that

glBindBuffer(GLenum target, GLuint name)
{

    openglobject *objinstance = NULL;

    if( name != 0 ) {
        if( !bufferobjects.has_key(name) ) {
            push_openglerror( INVALID_NAME );
            return;
        }

        objinstance = bufferobjects[name];

        if( NULL == bufferobjects[name] ) {
            switch(target) {
            case GL_ARRAY_BUFFER:
                objinstance = new OpenGLArrayBuffer; break;

            case GL_ELEMENT_ARRAY_BUFFER:
                objinstance = new OpenGLElementArrayBuffer; break;

            /* ... and so on */    

            default:
                push_openglerror( INVALID_TARGET ); return;
            }

            bufferobjects[name] = objinstance;

            }
        }
    }

    if( objinstance != NULL && target_of(objinstance) != target ) {
         opengl_pusherror( INVALID_TARGET );
    }

    switch( target ) {
    case GL_ARRAY_BUFFER:
         /* this would be a static function of the subclass setting 
          * global singleton instance pointer
          */
         OpenGLArrayBuffer::make_current(objinstance);
         break;

         /* ... and so on */
    }
}

I think you can see there this is going: The buffer target specifies the type of subclass the instance is you're working with and its static members.

glBufferData then actually allocates memory of the particular object, and can initialize it with the contents of a buffer you pass to it. glBufferSubData just copies data to the internal storage.

So much for the Buffer Objects (of which there are several kinds).


The other part are the Vertex Array Objects. Those are special OpenGL objects that create an association between vertex attributes, which are per-vertex data passed to the shaders based on their attribute index and the array buffer objects from which this data is takes.

When you call glGenVertexArray something like this happens:

std::map<GLuint, openglobject*> vertexarrayobjects;

glGenVertexArrays(GLuint count, std::vector<GLuint> *out_names)
{
    out_names->resize(count);
    for(int i=0; i < count; i++) {
        GLuint name = get_next_free_handle_ID();
        vertexarrayrobjects[name] = NULL;
        out_names.set(i, name);
    }
}

Looks familiar, doesn't it? The only difference is, that a different mapping structure is used. glBindVertexArray does the allocation of an instance and so on.

Now the calls glEnableVertexAttribute and glVertexAttribPointer can be thought as the following:

glEnableVertexAttribute(GLuint idx)
{
    ((OpenGLVertexArrayObject*)currentvertexarray)->add_attribute(idx);
}

glVertexAttribPointer(GLuint idx, ..., void *ptr)
{
    ((OpenGLVertexArrayObject*)currentvertexarray)->bind_attribute(
          idx,
          OpenGLArrayBuffer::get_current(),
          (off_t)ptr );
}

Okay, that last bit requires some explanation. That you pass a pointer to glVertexAttribPointer is a legacy from OpenGL-1.1 where there were no OpenGL buffer objects and instead you pointed directly to memory of your program. Then buffer objects got introduced and those don't require a pointer but a byte sized offset when binding. So the OpenGL devs went the dirty route and just lied to the compilers about it. I did explain the details in my answer to the question "What is the result of NULL + int?"

Note that OpenGL-4 introduced a new, much more powerfull and flexible API to create VAO attribute ←→ VBO bindings.

like image 159
datenwolf Avatar answered Dec 28 '22 07:12

datenwolf


there is always a "current Buffer" of each target set by glBindBuffer(target, id) on which most the buffer operations know to operate.

openGL uses glEnableVertexAttribArray to know which attributes it should look for, if not called then openGL will not use the data.

glVertexAttribPointer tells openGL where in the currently bound GL_ARRAY_BUFFER the attributes must be found for the current vertexArrays. in your example: (assuming vbo[0] is still bound to GL_ARRAY_BUFFER)

  1. attribute for index 0 is found in vbo[0] with 4 floats per vertex tightly packed and starting from 0
  2. attribute for index 1 is found in vbo[0] with 4 floats per vertex tightly packed and starting from sizeof(strip_position)

these bindings persist over glBindBuffer calls so if you want to rebind then you'll need to bind the other buffer call glVertexAttribPointer and then you can unbind again

I suggest you always call glBindBuffer with a 0 buffer so openGL knows you don't want to work with the current buffer anymore and avoid strange behaviors

to create the second object you can refill the various buffers each time you switch objects

or you can either create 2 sets of buffers:

GLuint vao[2]; // vertex array object
glGenVertexArrays(2, vao);
glBindVertexArray(vao[0]);

GLuint ebo[2]; // element buffer object
glGenBuffers(2, ebo);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo[0]);
glBufferData(
             GL_ELEMENT_ARRAY_BUFFER, 
             sizeof(strip_indices), 
             strip_indices, 
             GL_STATIC_DRAW
);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo[1]);
glBufferData(
             GL_ELEMENT_ARRAY_BUFFER, 
             sizeof(strip_indices), 
             TWO_strip_indices, 
             GL_STATIC_DRAW
);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);

GLuint vbo[2]; // vertex buffer object
glGenBuffers(2, vbo);
glBindBuffer(GL_ARRAY_BUFFER, vbo[0]);
glBufferData(
             GL_ARRAY_BUFFER, 
             sizeof(strip_position) + sizeof(strip_colors), 
             NULL, 
             GL_STATIC_DRAW
);
glBufferSubData(
                GL_ARRAY_BUFFER, 
                0,                      //offset
                sizeof(strip_position), //size date
                strip_position          //data
);
glBufferSubData(
                GL_ARRAY_BUFFER, 
                sizeof(strip_position), //offset
                sizeof(strip_colors),   //size data
                strip_colors               //data
);
//fill other buffer (assuming the first TWOstrip_colors was actually TWOstrip_position
glBindBuffer(GL_ARRAY_BUFFER, vbo[1]);
glBufferData(
             GL_ARRAY_BUFFER, 
             sizeof(TWOstrip_position) + sizeof(TWOstrip_colors), 
             NULL, 
             GL_STATIC_DRAW
);
glBufferSubData(
                GL_ARRAY_BUFFER, 
                0,                      //offset
                sizeof(TWOstrip_position), //size date
                strip_position          //data
);
glBufferSubData(
                GL_ARRAY_BUFFER, 
                sizeof(TWOstrip_position), //offset
                sizeof(TWOstrip_colors),   //size data
                strip_colors               //data
);
glBindBuffer(GL_ARRAY_BUFFER, 0);


glBindVertexArray(vao[0]);
glBindBuffer(GL_ARRAY_BUFFER, vbo[0])
glVertexAttribPointer(
                      0,         //index
                      4,         //size
                      GL_FLOAT,  //type
                      GL_FALSE,  //normalized
                      0,         //stride
                      NULL       //pointer
);
glVertexAttribPointer(
                      1, 
                      4, 
                      GL_FLOAT, 
                      GL_FALSE, 
                      0, 
                      (const GLvoid*)sizeof(strip_position)
);

glBindVertexArray(vao[1]);
glBindBuffer(GL_ARRAY_BUFFER, vbo[1]);
glVertexAttribPointer(
                      0,         //index
                      4,         //size
                      GL_FLOAT,  //type
                      GL_FALSE,  //normalized
                      0,         //stride
                      NULL       //pointer
);
glVertexAttribPointer(
                      1, 
                      4, 
                      GL_FLOAT, 
                      GL_FALSE, 
                      0, 
                      (const GLvoid*)sizeof(TWOstrip_position)
);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);

then to draw:

glEnableVertexAttribArray(0); 
glEnableVertexAttribArray(1);

glBindVertexArray(vao[0]);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo[0]);
glDrawElements(GL_TRIANGLE_STRIP, 8, GL_UNSIGNED_SHORT, NULL);

glBindVertexArray(vao[1]);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo[1]);
glDrawElements(GL_TRIANGLE_STRIP, 8, GL_UNSIGNED_SHORT, NULL);
like image 20
ratchet freak Avatar answered Dec 28 '22 06:12

ratchet freak