Search code examples
openglvbovao

multiple calls to glVertexAttribPointer necessary?


while following the lighting chapter in the learnopengl series, the author provides this sort of code while creating multiple VAOs(Vertex Array Objects):

    unsigned int VBO, cubeVAO;
    glGenVertexArrays(1, &cubeVAO);
    glGenBuffers(1, &VBO);

    glBindBuffer(GL_ARRAY_BUFFER, VBO);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

    glBindVertexArray(cubeVAO);

    // position attribute
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0); //#this
    glEnableVertexAttribArray(0);

    // second, configure the light's VAO (VBO stays the same; the vertices are the same for the light object which is also a 3D cube)
    unsigned int lightVAO;
    glGenVertexArrays(1, &lightVAO);
    glBindVertexArray(lightVAO);

    // we only need to bind to the VBO (to link it with glVertexAttribPointer), no need to fill it; the VBO's data already contains all we need (it's already bound, but we do it again for educational purposes)
    glBindBuffer(GL_ARRAY_BUFFER, VBO);

    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0); //#this
    glEnableVertexAttribArray(0);

Here we're using the same VBO with multiple VAO, yet the call to glVertexAttribPointer is done twice with the same parameter. Earlier in this lesson he mentions:

Each vertex attribute takes its data from memory managed by a VBO and which VBO it takes its data from (you can have multiple VBOs) is determined by the VBO currently bound to GL_ARRAY_BUFFER when calling glVertexAttribPointer. Since the previously defined VBO is still bound before calling glVertexAttribPointer vertex attribute 0 is now associated with its vertex data.

so, doesn't that mean that these two calls are redundant, or is this necessary and will cause problems down the road if not done?


Solution

  • so, doesn't that mean that these two calls are redundant, or is this necessary and will cause problems down the road if not done?

    No. Look at the order of operations here.

    // the buffer bound to GL_ARRAY_BUFFER is VBO, 
    // from here, until the end of the code in this block
    glBindBuffer(GL_ARRAY_BUFFER, VBO);
    
    // start modifying the cubeVAO
    // cubeVAO currently knows NOTHING about which attributes are needed.
    glBindVertexArray(cubeVAO);
    
    // set up info about vertex attribute 0, within cubeVAO. 
    // cubeVAO now knows about 1 attribute, index == 0
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0); //#this
    glEnableVertexAttribArray(0);
    
    
    // now start setting up the lightVAO. 
    // note that at this point, VBO is still bound to GL_ARRAY_BUFFER
    glBindVertexArray(lightVAO);
    
    // set up info about vertex attribute 0, within lightVAO. 
    // lightVAO now knows about 1 attribute, index == 0
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0); 
    glEnableVertexAttribArray(0);
    

    If you omit the second calls to glVertexAttribPointer and glEnableVertexAttribArray, the lightVAO will contain bindings for exactly ZERO vertex attributes. This means no attributes in your vertex shader will receive any data.

    In your original code, the second call to glBindBuffer isn't needed. It's also true to say that since the cubeVAO and lightVAO only have one attribute, which is read from the same buffer; it would have been possible to simply use a single VAO here.

    \edit

    It's probably better to think of it in terms of slots, rather than instances. There is a fixed number of vertex attributes that your GPU will support (do a glGet on GL_MAX_VERTEX_ATTRIBS to find out how many). So the only supported indices will be: 0 -> (GL_MAX_VERTEX_ATTRIBS-1), so it's not really accurate to say 'a new instance is created' (because that implies dynamic allocation). The behaviour is more akin to:

    // struct to store info about a vertex attribute
    struct VertexAttribute
    {
      bool enabled = false; //< on or off?
      int size;
      GLenum dataType;
      bool normalise;
      int stride;
      size_t offset;
      GLuint buffer; //< which buffer was bound to GL_ARRAY_BUFFER
    };
    
    // the VAO just stores the current state of the vertex bindings 
    struct VAO
    {
      VertexAttribute vertexAttribs[GL_MAX_VERTEX_ATTRIBS];
    
      void glVertexAttribPointer(
         int index, int size, GLenum type, 
         bool normalise, int stride, size_t offset)
      {
        vertexAttribs[index].size = size;
        vertexAttribs[index].dataType = type;
        vertexAttribs[index].normalise = normalise;
        vertexAttribs[index].stride = stride;
        vertexAttribs[index].offset = offset;
    
        // grab buffer
        vertexAttribs[index].buffer = glGet(GL_ARRAY_BUFFER_BINDING);
      }
    
      void glDisableVertexAttribArray(uint32_t index)
      {
        vertexAttribs[index].enabled = false;
      }
    
      void glEnableVertexAttribArray(uint32_t index)
      {
        vertexAttribs[index].enabled = true;
      }
    };