Please send suggestions or corrections for this page to the author at sbuss@ucsd.edu.

This page describes some of the features of Modern OpenGL used in the BasicDrawModes program. This is part of a series of programs illustrating features of Modern OpenGL. The first two programs, SimpleDrawModern and SimpleAnimModern, already introduced many of the basic features of any OpenGL program, and these were described on their web pages. On this page, we only discuss aspects of the software with are new to BasicDrawModes.

The code fragments listed below are taken from the programs BasicDrawModes.cpp. It is recommended to refer to the source code as you read this web page to see how the code fits together. In some cases, the code examples given below are slightly modified to make this web page more readable. We cannot cover all features of commands here, and it strongly recommended to your browser search to find on-line documentation for the OpenGL commands and keywords to learn more about their functionality.

BasicDrawModes draws the same geometry four times using different methods available in Modern OpenGL for rendering. OpenGL has a lot of different draw modes. The four drawing modes illustrated in BasicDrawModes are among the simplest drawing methods available. More advanced drawing modes include the "instanced" and "indirect" modes, but these are not covered here. Nor is the "immediate" draw mode of legacy OpenGL. Some of these may be covered in future programs; for instance, see FourTexturesModern for an example of how to use glDrawElementsBaseVertex.

I. Using multiple calls to glDrawArrays

This drawing mode was already used in SimpleAnimModern and SimpleAnimModern, but we cover it again here to contrast with the other drawing modes. The routine mySetupGeometries contains the following initialization:

    // FIRST GEOMETRY: Drawn with glDrawArrays.
    float firstGeom[][3] = {
        { 0.0f, 3.0f, 0.0 }, { 0.0f, 2.0f, 0.0 }, { 1.0f, 3.0f, 0.0 }, { 1.0f, 2.0f, 0.0 },
            { 2.0f, 3.0f, 0.0 }, { 2.0f, 2.0f, 0.0 }, { 3.0f, 3.0f, 0.0 }, { 3.0f, 2.0f, 0.0 },
        { 0.0f, 2.0f, 0.0 }, { 0.0f, 1.0f, 0.0 }, { 1.0f, 2.0f, 0.0 }, { 1.0f, 1.0f, 0.0 },
            { 2.0f, 2.0f, 0.0 }, { 2.0f, 1.0f, 0.0 }, { 3.0f, 2.0f, 0.0 }, { 3.0f, 1.0f, 0.0 },
        { 0.0f, 1.0f, 0.0 }, { 0.0f, 0.0f, 0.0 }, { 1.0f, 1.0f, 0.0 }, { 1.0f, 0.0f, 0.0 },
            { 2.0f, 1.0f, 0.0 }, { 2.0f, 0.0f, 0.0 }, { 3.0f, 1.0f, 0.0 }, { 3.0f, 0.0f, 0.0 },
    };

    // Bind (and initialize) the Vertex Array Object and its associated buffer objects
    glBindVertexArray(myVAO[vaoDrawArrays]);
    glBindBuffer(GL_ARRAY_BUFFER, myVBO[vboDrawArrays]);
    glBufferData(GL_ARRAY_BUFFER, sizeof(firstGeom), firstGeom, GL_STATIC_DRAW);
    glVertexAttribPointer(vertPos_loc, 3, GL_FLOAT, GL_FALSE, 0, (void*)0);    // Info about where positions are in the VBO
    glEnableVertexAttribArray(vertPos_loc);                                    // Enable the stored vertex positions

The routine myRenderScene renders this as three triangle strips with the code:

    glBindVertexArray(myVAO[vaoDrawArrays]);
    glVertexAttrib3f(vertColor_loc, 1.0f, 0.4f, 0.4f);     // A light red color
    theModelViewMatrix.Set_glTranslate(1.0, 1.0, 0.0);
    theModelViewMatrix.DumpByColumns(matEntries);
    glUniformMatrix4fv(modelviewMatLocation, 1, false, matEntries);
    glDrawArrays(GL_TRIANGLE_STRIP, 0, 8);
    glDrawArrays(GL_TRIANGLE_STRIP, 8, 8);
    glDrawArrays(GL_TRIANGLE_STRIP, 16, 8);

There are 16 unique vertices specified in the array firstGeom. However, since they are used to form three triangle strips with eight vertices each, there are a total of 24 vertex positions specified in the array. This causes a duplication of eight vertices. In addition, the vertices must be placed into firstGeom in the order needed for drawing the triangle strips; this is a rather counter-intuitive way to order vertices. The next draw mode will address these issues.

II. Using multiple calls to glDrawElements

For the second geometry (which renders identically to the first geometry), the initialization in mySetupGeometries is:

    float otherGeom[][3] = {
        { 0.0f, 3.0f, 0.0 }, { 1.0f, 3.0f, 0.0 }, { 2.0f, 3.0f, 0.0 }, { 3.0f, 3.0f, 0.0 },
        { 0.0f, 2.0f, 0.0 }, { 1.0f, 2.0f, 0.0 }, { 2.0f, 2.0f, 0.0 }, { 3.0f, 2.0f, 0.0 },
        { 0.0f, 1.0f, 0.0 }, { 1.0f, 1.0f, 0.0 }, { 2.0f, 1.0f, 0.0 }, { 3.0f, 1.0f, 0.0 },
        { 0.0f, 0.0f, 0.0 }, { 1.0f, 0.0f, 0.0 }, { 2.0f, 0.0f, 0.0 }, { 3.0f, 0.0f, 0.0 },
    };

    // EBO data for glDrawElements and glMultiDrawElements
    unsigned int drawmultidrawElts[] = {
        0, 4, 1, 5, 2, 6, 3, 7,
        4, 8, 5, 9, 6, 10, 7, 11,
        8, 12, 9, 13, 10, 14, 11, 15
    };
    
    ....
    
    glBindVertexArray(myVAO[vaoDrawElements]);
    glBindBuffer(GL_ARRAY_BUFFER, myVBO[vaoDrawElements]);
    glBufferData(GL_ARRAY_BUFFER, sizeof(otherGeom), otherGeom, GL_STATIC_DRAW);
    glVertexAttribPointer(vertPos_loc, 3, GL_FLOAT, GL_FALSE, 0, (void*)0);
    glEnableVertexAttribArray(vertPos_loc);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, myEBO[eboDrawMultiDrawElts]);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(drawmultidrawElts), drawmultidrawElts, GL_STATIC_DRAW);

Now the vertices in the array otherGeom are listed in rectangular order, and each vertex is listed only once. The array drawmultidrawElts contains the indices of the vertices needed for the triangle strips. For example, the first triangle strip starts with vertex 0, then vertex 4, ..., ending with vertex 3 and finally vertex 7. These vertex values refer to the positions of the vertices in the array otherGeom. The three triangle strips are rendered in myRenderScene with:

    glBindVertexArray(myVAO[vaoDrawElements]);
    glVertexAttrib3f(vertColor_loc, 1.0f, 1.0f, 0.4f);     // A light yellow color
    theModelViewMatrix.Set_glTranslate(5.0, 1.0, 0.0);
    theModelViewMatrix.DumpByColumns(matEntries);
    glUniformMatrix4fv(modelviewMatLocation, 1, false, matEntries);
    glDrawElements(GL_TRIANGLE_STRIP, 8, GL_UNSIGNED_INT, 0);
    glDrawElements(GL_TRIANGLE_STRIP, 8, GL_UNSIGNED_INT, (void*)(8 * sizeof(unsigned int)));
    glDrawElements(GL_TRIANGLE_STRIP, 8, GL_UNSIGNED_INT, (void*)(16*sizeof(unsigned int)));

The second argument to glDrawElements gives the number (8 for us) of vertices in the triangle strip. The fourth argument gives the displacement (aka "offset") in bytes to the locations holding the elements (indices of the vertices) for the triangle strip. In the code above, the first one starts at position 0, the second starts at element 8, and the third starts at element 16. The third argument, GL_USIGNED_INT specifies the data type used for the elements: that is, drawmultidrawElts is an array of unsigned integers.

In our simple example, there was no net savings of memory in using glDrawElements instead of glDrawArrays; however, vertices often contain other attributes besides positions such as surface normals or texture coordinates. In this case, there can be substantial memory savings from the use of glDrawElements.

A disadvantage of the two drawing modes discussed so far is that glDrawArrays or glDrawElements must be called once per primitive (e.g., once per triangle strip). Calls to these routines have a lot overhead, and slow down the rendering. The next two drawing modes give ways to avoid having multiple calls.

III. Using a single call to glMultiDrawElements.

A single call to glMultiDrawElements is used to simulate the effect of multiple calls to glDrawElements. The varying parameters to glDrawElements are taken from two arrays, multidrawCount and multidrawStartIdx.

mySetupGeometries sets up the VAO for using glMultiDrawElements exactly the same way that the VAO was set up in the previous code for using multiple calls to glDrawElements. The only difference is that this time there are no calls glBufferData: this is because the vertex position data and the element array data was already loaded into the buffer by the earlier calls to glBufferData. In other words, the two VAO's can use the same VBO buffers!

    // Do not need to give the glBufferData commands again. Data was already loaded into the buffer.
    glBindVertexArray(myVAO[vaoMultiDrawElements]);
    glBindBuffer(GL_ARRAY_BUFFER, myVBO[vboDrawEltsAll]);
    glVertexAttribPointer(vertPos_loc, 3, GL_FLOAT, GL_FALSE, 0, (void*)0);
    glEnableVertexAttribArray(vertPos_loc);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, myEBO[eboDrawMultiDrawElts]);

The following two arrays are defined as global variables to specify parameters of the type sent to glDrawElements.

// Each of the triangle strips has 8 vertices
int multidrawCount[3] = { 8, 8, 8 }; 
// The starting positions in the EBO of the three triangle strips
void* multidrawStartIdx[3] = { 0, (void*)(8 * sizeof(unsigned int)), (void*)(16 * sizeof(unsigned int)) };

myRenderScene renders this geometry with the commands:

    glBindVertexArray(myVAO[vaoMultiDrawElements]);
    glVertexAttrib3f(vertColor_loc, 0.6f, 0.6f, 1.0f);     // A light blue color
    theModelViewMatrix.Set_glTranslate(1.0, 5.0, 0.0);
    theModelViewMatrix.DumpByColumns(matEntries);
    glUniformMatrix4fv(modelviewMatLocation, 1, false, matEntries);
    glMultiDrawElements(GL_TRIANGLE_STRIP, multidrawCount, GL_UNSIGNED_INT, multidrawStartIdx, 3);

The final parameter (3) of glMultiDrawElements indicates the number of triangle strips to draw. The arrays multidrawCount and multidrawStartIdx have length 3: multidrawCount[i] specifies the number of triangles in the i-th triangle strip, and multidrawStartIdx[i] specifies position in the EBO of the first element for the i-th triangle strip. In this example, these values are identical to those used for the calls to glDrawArrays in Section II above.

Remark: The code above used a different VAO, myVAO[vaoMultiDrawElements]. This is actually optional in this program: The same call to glMultiDrawElements will work with myVAO[vaoDrawElements].

IV. Using glDrawElements with Primitive Restart.

Another elegant way to render multiple triangle strips with a single call is to use glDrawElements with a special element value indicating a primitive restart. A value restartPrimitiveCode is used to indicate the break between two triangle fans. BasicDrawModes defines the value of restartPrimitiveCode to equal 0xfffffff, but all that is really important is that it is not a valid element (i.e., not an index for a vertex). The array drawPrimRestartElts holds the complete list of elements needed for the three triangle strips, using restartPrimitiveCode to separate the triangle strips:

mySetupGeometries sets up data for the EBO and VAO with the following code:

    unsigned int restartPrimitiveCode = 0xffffffff;     // Max. unsigned integer
    …
    // EBO data for glDrawElementsPrimitiveRestart
    // Three triangle strips with eight vertices each.
    // Breaks between triangle strips indicated by restartPrimitiveCode.
    unsigned int drawPrimRestartElts[] = {
        0, 4, 1, 5, 2, 6, 3, 7, restartPrimitiveCode,
        4, 8, 5, 9, 6, 10, 7, 11, restartPrimitiveCode,
        8, 12, 9, 13, 10, 14, 11, 15
    };
    
    ...
    
    glBindVertexArray(myVAO[vaoDrawElementsPrimitiveRestart]);
    glBindBuffer(GL_ARRAY_BUFFER, myVBO[vboDrawEltsAll]);
    glVertexAttribPointer(vertPos_loc, 3, GL_FLOAT, GL_FALSE, 0, (void*)0);
    glEnableVertexAttribArray(vertPos_loc);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, myEBO[eboDrawEltsPrimRestart]);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(drawPrimRestartElts), drawPrimRestartElts, GL_STATIC_DRAW);

Note that there is again no call to glBufferData for the VBO, the VBO already contains the vertex data. However, there is a call to glBufferData for the new array drawPrimRestartElts.

myRenderScene renders this geometry with the commands:

    glBindVertexArray(myVAO[vaoDrawElementsPrimitiveRestart]);
    glVertexAttrib3f(vertColor_loc, 0.4f, 1.0f, 0.4f);     // A light green color
    theModelViewMatrix.Set_glTranslate(5.0, 5.0, 0.0);
    theModelViewMatrix.DumpByColumns(matEntries);
    glUniformMatrix4fv(modelviewMatLocation, 1, false, matEntries);
    
    glEnable(GL_PRIMITIVE_RESTART);
    glPrimitiveRestartIndex(restartPrimitiveCode);           // Only need to call this once
    glDrawElements(GL_TRIANGLE_STRIP, 26, GL_UNSIGNED_INT, 0);
    glDisable(GL_PRIMITIVE_RESTART);

The calls to glEnable and glDisable enable and disable the use of primitive restarts; the call to glPrimitiveRestartIndex specifies the restart code. It is not necessary to call these for every rendering, but it is included here to illustrate how they work.

Remark: As before, it was not necessary to use a different VAO. Using the same call to glMultiDrawElements with primitive restart will also work with myVAO[vaoDrawElements].