OpenGL 4 Shading Language Cookbook, Second Edition

4.8 (5 reviews total)
By David Wolff
  • Instant online access to over 7,500+ books and videos
  • Constantly updated with 100+ new titles each month
  • Breadth and depth in over 1,000+ technologies
  1. Getting Started with GLSL

About this book

OpenGL Shading Language (GLSL) is a programming language used for customizing parts of the OpenGL graphics pipeline that were formerly fixed-function, and are executed directly on the GPU. It provides programmers with unprecedented flexibility for implementing effects and optimizations utilizing the power of modern GPUs. With Version 4, the language has been further refined to provide programmers with greater power and flexibility, with new stages such as tessellation and compute.

OpenGL 4 Shading Language Cookbook provides easy-to-follow examples that first walk you through the theory and background behind each technique, and then go on to provide and explain the GLSL and OpenGL code needed to implement it. Beginner level through to advanced techniques are presented including topics such as texturing, screen-space techniques, lighting, shading, tessellation shaders, geometry shaders, compute shaders, and shadows.

OpenGL Shading Language 4 Cookbook is a practical guide that takes you from the fundamentals of programming with modern GLSL and OpenGL, through to advanced techniques. The recipes build upon each other and take you quickly from novice to advanced level code.

You’ll see essential lighting and shading techniques; examples that demonstrate how to make use of textures for a wide variety of effects and as part of other techniques; examples of screen-space techniques including HDR rendering, bloom, and blur; shadowing techniques; tessellation, geometry, and compute shaders; how to use noise effectively; and animation with particle systems.

OpenGL Shading Language 4 Cookbook provides examples of modern shading techniques that can be used as a starting point for programmers to expand upon to produce modern, interactive, 3D computer graphics applications.

Publication date:
December 2013
Publisher
Packt
Pages
394
ISBN
9781782167020

 

Chapter 1. Getting Started with GLSL

In this chapter, we will cover the following recipes:

  • Using a function loader to access the latest OpenGL functionality

  • Using GLM for mathematics

  • Determining the GLSL and OpenGL version

  • Compiling a shader

  • Linking a shader program

  • Sending data to a shader using vertex attributes and vertex buffer objects

  • Getting a list of active vertex input attributes and locations

  • Sending data to a shader using uniform variables

  • Getting a list of active uniform variables

  • Using uniform blocks and uniform buffer objects

  • Getting debug messages

  • Building a C++ shader program class

 

Introduction


The OpenGL Shading Language (GLSL) Version 4 brings unprecedented power and flexibility to programmers interested in creating modern, interactive, and graphical programs. It allows us to harness the power of modern Graphics Processing Units (GPUs) in a straightforward way by providing a simple yet powerful language and API. Of course, the first step towards using GLSL is to create a program that utilizes the latest version of the OpenGL API. GLSL programs don't stand on their own; they must be a part of a larger OpenGL program. In this chapter, we will provide some tips and techniques for getting a basic program up and running. First, let's start with some background.

The OpenGL Shading Language

The GLSL is now a fundamental and integral part of the OpenGL API. Going forward, every program written using the OpenGL API will internally utilize one or several GLSL programs. These "mini-programs" are often referred to as shader programs. A shader program usually consists of several components called shaders. Each shader executes within a different section of the OpenGL pipeline. Each shader runs on the GPU, and as the name implies, (typically) implement the algorithms related to the lighting and shading effects of an image. However, shaders are capable of doing much more than just implementing a shading algorithm. They are also capable of performing animation, tessellation, or even generalized computation.

Note

The field of study dubbed GPGPU (General Purpose Computing on Graphics Processing Units) is concerned with utilization of GPUs (often using specialized APIs such as CUDA or OpenCL) to perform general purpose computations such as fluid dynamics, molecular dynamics, cryptography, and so on. With compute shaders, introduced in OpenGL 4.3, we can now do GPGPU within OpenGL.

Shader programs are designed for direct execution on the GPU and are executed in parallel. For example, a fragment shader might be executed once for every pixel, with each execution running simultaneously on a separate GPU thread. The number of processors on the graphics card determines how many can be executed at one time. This makes shader programs incredibly efficient, and provides the programmer with a simple API for implementing highly parallel computation.

The computing power available in modern graphics cards is impressive. The following table shows the number of shader processors available for several models in the NVIDIA GeForce series cards (source: http://en.wikipedia.org/wiki/Comparison_of_Nvidia_graphics_processing_units).

Model

Unified Shader Processors

GeForce GTS 450

192

GeForce GTX 480

480

GeForce GTX 780

2304

Shader programs are intended to replace parts of the OpenGL architecture referred to as the fixed-function pipeline. Prior to OpenGL Version 2.0, the shading algorithm was "hard-coded" into the pipeline and had only limited configurability. This default lighting/shading algorithm was a core part of the fixed-function pipeline. When we, as programmers, wanted to implement more advanced or realistic effects, we used various tricks to force the fixed-function pipeline into being more flexible than it really was. The advent of GLSL will help by providing us with the ability to replace this "hard-coded" functionality with our own programs written in GLSL, thus giving us a great deal of additional flexibility and power. For more details on the programmable pipeline, see the introduction to Chapter 2, The Basics of GLSL Shaders.

In fact, recent (core) versions of OpenGL not only provide this capability, but they require shader programs as part of every OpenGL program. The old fixed-function pipeline has been deprecated in favor of a new programmable pipeline, a key part of which is the shader program written in GLSL.

Profiles – Core vs. Compatibility

OpenGL Version 3.0 introduced a deprecation model, which allowed for the gradual removal of functions from the OpenGL specification. Functions or features can be marked as deprecated, meaning that they are expected to be removed from a future version of OpenGL. For example, immediate mode rendering using glBegin/glEnd was marked deprecated in version 3.0 and removed in version 3.1.

In order to maintain backwards compatibility, the concept of compatibility profiles was introduced with OpenGL 3.2. A programmer that is writing code intended to be used with a particular version of OpenGL (with older features removed) would use the so-called core profile. Someone who also wanted to maintain compatibility with older functionality could use the compatibility profile.

Note

It may be somewhat confusing that there is also the concept of a forward compatible context, which is distinguished slightly from the concept of a core/compatibility profile. A context that is considered forward compatible basically indicates that all deprecated functionality has been removed. In other words, if a context is forward compatible, it only includes functions that are in the core, but not those that were marked as deprecated. Some window APIs provide the ability to select forward compatible status along with the profile.

The steps for selecting a core or compatibility profile are window system API dependent. For example, in GLFW, one can select a forward compatible, 4.3 core profile using the following code:

glfwWindowHint( GLFW_CONTEXT_VERSION_MAJOR, 4 );
glfwWindowHint( GLFW_CONTEXT_VERSION_MINOR, 3 );
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

GLFWwindow *window = glfwCreateWindow(640, 480, "Title", NULL, NULL);

All programs in this book are designed to be compatible with a forward compatible OpenGL 4.3 core profile.

 

Using a function loader to access the latest OpenGL functionality


The OpenGL ABI (application binary interface) is frozen to OpenGL version 1.1 on Windows. Unfortunately for Windows developers, that means that it is not possible to link directly to functions that are provided in newer versions of OpenGL. Instead, one must get access to these functions by acquiring a function pointer at runtime. Getting access to the function pointers is not difficult, but requires somewhat tedious work, and has a tendency to clutter your code. Additionally, Windows typically comes with a standard OpenGL gl.h file that also conforms to OpenGL 1.1. The OpenGL wiki states that Microsoft has no plans to ever update the gl.h and opengl32.lib that come with their compilers. Thankfully, others have provided libraries that manage all of this for us by transparently providing the needed function pointers, while also exposing the needed functionality in header files. There are several libraries available that provide this kind of support. One of the oldest and most common is GLEW (OpenGL Extension Wrangler). However, there are a few serious issues with GLEW that might make it less desirable, and insufficient for my purposes when writing this book. First, at time of writing, it doesn't yet support core profiles properly, and for this book, I want to focus only on the latest non-deprecated functionality. Second, it provides one large header file that includes everything from all versions of OpenGL. It might be preferable to have a more streamlined header file that only includes functions that we might use. Finally, GLEW is distributed as a library that needs to be compiled separately and linked into our project. It is often preferable to have a loader that can be included into a project simply by adding the source files and compiling them directly into our executable, avoiding the need to support another link-time dependency.

In this recipe, we'll use the OpenGL Loader Generator (GLLoadGen), available from https://bitbucket.org/alfonse/glloadgen/wiki/Home. This very flexible and efficient library solves all three of the issues described in the previous paragraph. It supports core profiles and it can generate a header that includes only the needed functionality, and also generates just a couple of files (a source file and a header) that we can add directly into our project.

Getting ready

To use GLLoadGen, you'll need Lua. Lua is a lightweight embeddable scripting language that is available for nearly all platforms. Binaries are available at http://luabinaries.sourceforge.net, and a fully packaged install for Windows (LuaForWindows) is available at:

https://code.google.com/p/luaforwindows

Download the GLLoadGen distribution from: https://bitbucket.org/alfonse/glloadgen/downloads. The distribution is compressed using 7zip, which is not widely installed, so you may need to install a 7zip utility, available at http://7-zip.org/. Extract the distribution to a convenient location on your hard drive. Since GLLoadGen is written in Lua, there's nothing to compile, once the distribution is uncompressed, you're ready to go.

How to do it...

The first step is to generate the header and source files for the OpenGL version and profile of choice. For this example, we'll generate files for an OpenGL 4.3 core profile. We can then copy the files into our project and compile them directly alongside our code:

  1. To generate the header and source files, navigate to the GLLoadGen distribution directory, and run GLLoadGen with the following arguments:

    lua LoadGen.lua -style=pointer_c -spec=gl -version=4.3 \-profile=core core_4_3
    
  2. The previous step should generate two files: gl_core_4_3.c and gl_core_4_3.h. Move these files into your project and include gl_core_4_3.c in your build. Within your program code, you can include the gl_core_4_3.h file whenever you need access to the OpenGL functions. However, in order to initialize the function pointers, you need to make sure to call a function to do so. The needed function is called ogl_LoadFunctions. Somewhere just after the GL context is created (typically in an initialization function), and before any OpenGL functions are called, use the following code:

    int loaded = ogl_LoadFunctions(); 
    if(loaded == ogl_LOAD_FAILED) {   
      //Destroy the context and abort
      return; 
    }  
    
    int num_failed = loaded - ogl_LOAD_SUCCEEDED; printf("Number of functions that failed to load: %i.\n",num_failed);

That's all there is to it!

How it works...

The lua command in step 1 generates a pair of files, that is; a header and a source file. The header provides prototypes for all of the selected OpenGL functions and redefines them as function pointers, and defines all of the OpenGL constants as well. The source file provides initialization code for the function pointers as well as some other utility functions. We can include the gl_core_4_3.h header file wherever we need prototypes for OpenGL functions, so all function entry points are available at compile time. At run time, the ogl_LoadFunctions() function will initialize all available function pointers. If some functions fail to load, the number of failures can be determined by the subtraction operation shown in step 2. If a function is not available in the selected OpenGL version, the code may not compile, because only function prototypes for the selected OpenGL version and profile are available in the header (depending on how it was generated).

The command line arguments available to GLLoadGen are fully documented here: https://bitbucket.org/alfonse/glloadgen/wiki/Command_Line_Options. The previous example shows the most commonly used setup, but there's a good amount of flexibility built into this tool.

Now that we have generated this source/header pair, we no longer have any dependency on GLLoadGen and our program can be compiled without it. This is a significant advantage over tools such as GLEW.

There's more...

GLLoadGen includes a few additional features that are quite useful. We can generate more C++ friendly code, manage extensions, and generate files that work without the need to call an initialization function.

Generating a C++ loader

GLLoadGen supports generation of C++ header/source files as well. This can be selected via the -style parameter. For example, to generate C++ files, use -style=pointer_cpp as in the following example:

lua LoadGen.lua -style=pointer_cpp -spec=gl -version=4.3 \-profile=core core_4_3

This will generate gl_core_4_3.cpp and gl_core_4_3.hpp. This places all OpenGL functions and constants within the gl:: namespace, and removes their gl (or GL) prefix. For example, to call the function glBufferData, you might use the following syntax.

gl::BufferData(gl::ARRAY_BUFFER, size, data, gl::STATIC_DRAW);

Loading the function pointers is also slightly different. The return value is an object rather than just a simple integer and LoadFunctions is in the gl::sys namespace.

gl::exts::LoadTest didLoad = gl::sys::LoadFunctions();

if(!didLoad) {   
    // Clean up (destroy the context) and abort.   
    return; 
}
printf("Number of functions that failed to load: %i.\n", didLoad.GetNumMissing());

No-load styles

GLLoadGen supports the automatic initialization of function pointers. This can be selected using the noload_c or noload_cpp options for the style parameter. With these styles, there is no need to call the initialization function ogl_LoadFunctions. The pointers are loaded automatically, the first time a function is called. This can be convenient, but there's very little overhead to loading them all at initialization.

Using Extensions

GLLoadGen does not automatically support extensions. Instead, you need to ask for them with command line parameters. For example, to request ARB_texture_view and ARB_vertex_attrib_binding extensions, you might use the following command.

lua LoadGen.lua -style=pointer_c -spec=gl -version=3.3 \-profile=core core_3_3 \-exts ARB_texture_view ARB_vertex_attrib_binding

The -exts parameter is a space-separated list of extensions. GLLoadGen also provides the ability to load a list of extensions from a file (via the -extfile parameter) and provides some common extension files on the website.

You can also use GLLoadGen to check for the existence of an extension at run-time. For details, see the GLLoadGen wiki.

See also

 

Using GLM for mathematics


Mathematics is core to all of computer graphics. In earlier versions, OpenGL provided support for managing coordinate transformations and projections using the standard matrix stacks (GL_MODELVIEW and GL_PROJECTION). In recent versions of core OpenGL however, all of the functionality supporting the matrix stacks has been removed. Therefore, it is up to us to provide our own support for the usual transformation and projection matrices, and then to pass them into our shaders. Of course, we could write our own matrix and vector classes to manage this, but some might prefer to use a ready-made, robust library.

One such library is GLM (OpenGL Mathematics) written by Christophe Riccio. Its design is based on the GLSL specification, so the syntax is very similar to the mathematical support in GLSL. For experienced GLSL programmers, this makes GLM very easy to use and familiar. Additionally, it provides extensions that include functionality similar to some of the much-missed OpenGL functions such as glOrtho, glRotate, or gluLookAt.

Getting ready

Since GLM is a header-only library, installation is simple. Download the latest GLM distribution from http://glm.g-truc.net. Then, unzip the archive file, and copy the glm directory contained inside to anywhere in your compiler's include path.

How to do it...

To use the GLM libraries, it is simply a matter of including the core header file, and headers for any extensions. For this example, we'll include the matrix transform extension as follows:

#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>

Then the GLM classes are available in the glm namespace. The following is an example of how you might go about making use of some of them:

glm::vec4 position = glm::vec4( 1.0f, 0.0f, 0.0f, 1.0f );
glm::mat4 view = glm::lookAt( glm::vec3(0.0,0.0,5.0),glm::vec3(0.0,0.0,0.0),glm::vec3(0.0,1.0,0.0) );
glm::mat4 model(1.0f);   // The identity matrix
model = glm::rotate( model, 90.0f, glm::vec3(0.0f,1.0f,0.0) );
glm::mat4 mv = view * model;
glm::vec4 transformed = mv * position;

How it works...

The GLM library is a header-only library. All of the implementation is included within the header files. It doesn't require separate compilation and you don't need to link your program to it. Just placing the header files in your include path is all that's required!

The previous example first creates a vec4 (four coordinate vector) representing a position. Then it creates a 4 x 4 view matrix by using the glm::lookAt function. This works in a similar fashion to the old gluLookAt function. Here, we set the camera's location at (0, 0, 5), looking towards the origin, with the "up" direction in the direction of the y-axis. We then go on to create the model matrix by first storing the identity matrix in the variable model (via the single argument constructor), and multiplying by a rotation matrix using the glm::rotate function. The multiplication here is implicitly done by the glm::rotate function. It multiplies its first parameter by the rotation matrix (on the right) that is generated by the function. The second parameter is the angle of rotation (in degrees), and the third parameter is the axis of rotation. Since before this statement, model is the identity matrix, the net result is that model becomes a rotation matrix of 90 degrees around the y-axis.

Finally, we create our modelview matrix (mv) by multiplying the view and model variables, and then using the combined matrix to transform the position. Note that the multiplication operator has been overloaded to behave in the expected way.

There's more...

It is not recommended to import all of the GLM namespace by using the following command:

using namespace glm;

This will most likely cause a number of namespace clashes. Instead, it is preferable to import symbols one at a time, as needed. For example:

#include <glm/glm.hpp>
using glm::vec3;
using glm::mat4;

Using the GLM types as input to OpenGL

GLM supports directly passing a GLM type to OpenGL using one of the OpenGL vector functions (with the suffix v ). For example, to pass a mat4 named proj to OpenGL we can use the following code:

glm::mat4 proj = glm::perspective( viewAngle, aspect, nearDist, farDist );
glUniformMatrix4fv(location, 1, GL_FALSE, &proj[0][0]);

See also

  • The Qt SDK includes many classes for vector/matrix mathematics, and is another good option if you're already using Qt

  • The GLM website http://glm.g-truc.net has additional documentation and examples

 

Determining the GLSL and OpenGL version


In order to support a wide range of systems, it is essential to be able to query for the supported OpenGL and GLSL version of the current driver. It is quite simple to do so, and there are two main functions involved: glGetString and glGetIntegerv.

How to do it...

The code shown as follows will print the version information to stdout:

const GLubyte *renderer = glGetString( GL_RENDERER );
const GLubyte *vendor = glGetString( GL_VENDOR );
const GLubyte *version = glGetString( GL_VERSION );
const GLubyte *glslVersion = glGetString( GL_SHADING_LANGUAGE_VERSION );

GLint major, minor;
glGetIntegerv(GL_MAJOR_VERSION, &major);
glGetIntegerv(GL_MINOR_VERSION, &minor);

printf("GL Vendor            : %s\n", vendor);
printf("GL Renderer          : %s\n", renderer);
printf("GL Version (string)  : %s\n", version);
printf("GL Version (integer) : %d.%d\n", major, minor);
printf("GLSL Version         : %s\n", glslVersion);

How it works...

Note that there are two different ways to retrieve the OpenGL version: using glGetString and glGetIntegerv. The former can be useful for providing readable output, but may not be as convenient for programmatically checking the version because of the need to parse the string. The string provided by glGetString(GL_VERSION) should always begin with the major and minor versions separated by a dot, however, the minor version could be followed with a vendor-specific build number. Additionally, the rest of the string can contain additional vendor-specific information and may also include information about the selected profile (see the Introduction section of this chapter). It is important to note that the use of glGetIntegerv to query for version information requires OpenGL 3.0 or greater.

The queries for GL_VENDOR and GL_RENDERER provide additional information about the OpenGL driver. The call glGetString(GL_VENDOR) returns the company responsible for the OpenGL implementation. The call to glGetString(GL_RENDERER) provides the name of the renderer which is specific to a particular hardware platform (such as the ATI Radeon HD 5600 Series). Note that both of these do not vary from release to release, so can be used to determine the current platform.

Of more importance to us in the context of this book is the call to glGetString( GL_SHADING_LANGUAGE_VERSION) which provides the supported GLSL version number. This string should begin with the major and minor version numbers separated by a period, but similar to the GL_VERSION query, may include other vendor-specific information.

There's more...

It is often useful to query for the supported extensions of the current OpenGL implementation. In versions prior to OpenGL 3.0, one could retrieve a full, space separated list of extension names with the following code:

GLubyte *extensions = glGetString(GL_EXTENSIONS);

The string that is returned can be extremely long and parsing it can be susceptible to error if not done carefully.

In OpenGL 3.0, a new technique was introduced, and the previous functionality was deprecated (and finally removed in 3.1). Extension names are now indexed and can be individually queried by index. We use the glGetStringi variant for this. For example, to get the name of the extension stored at index i, we use: glGetStringi(GL_EXTENSIONS, i). To print a list of all extensions, we could use the following code:

GLint nExtensions;
glGetIntegerv(GL_NUM_EXTENSIONS, &nExtensions);

for( int i = 0; i < nExtensions; i++ )
      printf("%s\n", glGetStringi( GL_EXTENSIONS, i ) );

See also

  • The GLLoadGen tool has additional support for querying version and extension information. Refer to the Using a function loader to access the latest OpenGL functionality recipe and the GLLoadGen website.

 

Compiling a shader


To get started, we need to know how to compile our GLSL shaders. The GLSL compiler is built right into the OpenGL library, and shaders can only be compiled within the context of a running OpenGL program. There is currently no external tool for precompiling GLSL shaders and/or shader programs.

Note

Recently, OpenGL 4.1 added the ability to save compiled shader programs to a file, enabling OpenGL programs to avoid the overhead of shader compilation by loading pre-compiled shader programs.

Compiling a shader involves creating a shader object, providing the source code (as a string or set of strings) to the shader object, and asking the shader object to compile the code. The process is roughly represented by the following diagram:

Getting ready

To compile a shader, we'll need a basic example to work with. Let's start with the following simple vertex shader. Save it in a file named basic.vert.

#version 430

in vec3 VertexPosition;
in vec3 VertexColor;

out vec3 Color;

void main()
{
   Color = VertexColor;
   gl_Position = vec4( VertexPosition, 1.0 );
}

In case you're curious about what this code does, it works as a "pass-through" shader. It takes the input attributes VertexPosition and VertexColor and passes them along to the fragment shader via the output variables gl_Position and Color.

Next, we'll need to build a basic shell for an OpenGL program using a window toolkit that supports OpenGL. Examples of cross-platform toolkits include GLFW, GLUT, FLTK, Qt, or wxWidgets. Throughout this text, I'll make the assumption that you can create a basic OpenGL program with your favorite toolkit. Virtually all toolkits have a hook for an initialization function, a resize callback (called upon resizing of the window), and a drawing callback (called for each window refresh). For the purposes of this recipe, we need a program that creates and initializes an OpenGL context, it need not do anything other than display an empty OpenGL window. Note that you'll also need to load the OpenGL function pointers (refer to the Using a function loader to access the latest OpenGL functionality recipe).

Finally, load the shader source code into a character array named shaderCode. Don't forget to add the null character at the end! This example assumes that the variable shaderCode points to an array of GLchar that is properly terminated by a null character.

How to do it...

To compile a shader, use the following steps:

  1. Create the shader object as follows:

    GLuint vertShader = glCreateShader( GL_VERTEX_SHADER );
    if( 0 == vertShader )
    {
      fprintf(stderr, "Error creating vertex shader.\n");
      exit(EXIT_FAILURE);
    }
  2. Copy the source code (perhaps from multiple locations) into the shader object:

    const GLchar * shaderCode = loadShaderAsString("basic.vert");
    const GLchar* codeArray[] = {shaderCode};
    glShaderSource( vertShader, 1, codeArray, NULL );
  3. Compile the shader:

    glCompileShader( vertShader );
  4. Verify the compilation status:

    GLint result;
    glGetShaderiv( vertShader, GL_COMPILE_STATUS, &result );
    if( GL_FALSE == result )
    {
      fprintf(stderr, "Vertex shader compilation failed!\n");
    
      GLint logLen;
      glGetShaderiv(vertShader, GL_INFO_LOG_LENGTH, &logLen);
       
      if( logLen > 0 )
      {
        char * log = new char[logLen];
          
        GLsizei written;
        glGetShaderInfoLog(vertShader, logLen, &written, log);
          
        fprintf(stderr, "Shader log:\n%s", log);
        delete [] log;
      }
    }

How it works...

The first step is to create the shader object using the glCreateShader function. The argument is the type of shader, and can be one of the following: GL_VERTEX_SHADER, GL_FRAGMENT_SHADER, GL_GEOMETRY_SHADER, GL_TESS_EVALUATION_SHADER, GL_TESS_CONTROL_SHADER, or (as of version 4.3) GL_COMPUTE_SHADER. In this case, since we are compiling a vertex shader, we use GL_VERTEX_SHADER. This function returns the value used for referencing the vertex shader object, sometimes called the object "handle". We store that value in the variable vertShader. If an error occurs while creating the shader object, this function will return 0, so we check for that and if it occurs, we print an appropriate message and terminate.

Following the creation of the shader object, we load the source code into the shader object using the function glShaderSource. This function is designed to accept an array of strings (as opposed to just a single one) in order to support the option of compiling multiple sources (files, strings) at once. So before we call glShaderSource, we place a pointer to our source code into an array named sourceArray. The first argument to glShaderSource is the handle to the shader object. The second is the number of source code strings that are contained in the array. The third argument is a pointer to an array of source code strings. The final argument is an array of GLint values that contains the length of each source code string in the previous argument. In the previous code, we pass a value of NULL, which indicates that each source code string is terminated by a null character. If our source code strings were not null terminated then this argument must be a valid array. Note that once this function returns, the source code has been copied into OpenGL internal memory, so the memory used to store the source code can be freed.

The next step is to compile the source code for the shader. We do this by simply calling glCompileShader, and passing the handle to the shader that is to be compiled. Of course, depending on the correctness of the source code, the compilation may fail, so the next step is to check whether or not the compilation was successful.

We can query for the compilation status by calling glGetShaderiv, which is a function for querying the attributes of a shader object. In this case we are interested in the compilation status, so we use GL_COMPILE_STATUS as the second argument. The first argument is of course the handle to the shader object, and the third argument is a pointer to an integer where the status will be stored. The function provides a value of either GL_TRUE or GL_FALSE in the third argument indicating whether or not the compilation was successful.

If the compile status is GL_FALSE, then we can query for the shader log, which will provide additional details about the failure. We do so by first querying for the length of the log by calling glGetShaderiv again with a value of GL_INFO_LOG_LENGTH. This provides the length of the log in the variable logLen. Note that this includes the null termination character. We then allocate space for the log, and retrieve the log by calling glGetShaderInfoLog. The first parameter is the handle to the shader object, the second is the size of the character buffer for storing the log, the third argument is a pointer to an integer where the number of characters actually written (excluding the null terminator character) will be stored, and the fourth argument is a pointer to the character buffer for storing the log itself. Once the log is retrieved, we print it to stderr and free its memory space.

There's more...

The previous example only demonstrated compiling a vertex shader. There are several other types of shaders including fragment, geometry, and tessellation shaders. The technique for compiling is nearly identical for each shader type. The only significant difference is the argument to glCreateShader.

It is also important to note that shader compilation is only the first step. To create a working shader program, we often have at least two shaders to compile, and then the shaders must be linked together into a shader program object. We'll see the steps involved in linking in the next recipe.

Deleting a Shader Object

Shader objects can be deleted when no longer needed by calling glDeleteShader. This frees the memory used by the shader and invalidates its handle. Note that if a shader object is already attached to a program object (refer to the Linking a shader program recipe), it will not be immediately deleted, but flagged for deletion when it is detached from the program object.

See also

  • The Linking a shader program recipe

 

Linking a shader program


Once we have compiled our shaders and before we can actually install them into the OpenGL pipeline, we need to link them together into a shader program. Among other things, the linking step involves making the connections between input variables from one shader to output variables of another, and making the connections between the input/output variables of a shader to appropriate locations in the OpenGL environment.

Linking involves steps that are similar to those involved in compiling a shader. We attach each shader object to a new shader program object and then tell the shader program object to link (making sure that the shader objects are compiled before linking).

Getting ready

For this recipe, we'll assume that you've already compiled two shader objects whose handles are stored in the variables vertShader and fragShader.

For this and a few other recipes in this Chapter, we'll use the following source code for the fragment shader:

#version 430

in vec3 Color;

out vec4 FragColor;

void main() {
  FragColor = vec4(Color, 1.0);
}

For the vertex shader, we'll use the source code from the previous recipe, Compiling a shader.

How to do it...

In our OpenGL initialization function, and after the compilation of shader objects referred to by vertShader and fragShader, use the following steps:

  1. Create the program object using the following code:

    GLuint programHandle = glCreateProgram();
    if( 0 == programHandle )
    {
      fprintf(stderr, "Error creating program object.\n");
      exit(1);
    }
  2. Attach the shaders to the program object as follows:

    glAttachShader( programHandle, vertShader );
    glAttachShader( programHandle, fragShader );
  3. Link the program:

    glLinkProgram( programHandle );
  4. Verify the link status:

    GLint status;
    glGetProgramiv( programHandle, GL_LINK_STATUS, &status );
    if( GL_FALSE == status ) {
    
      fprintf( stderr, "Failed to link shader program!\n" );
    
      GLint logLen;
      glGetProgramiv(programHandle, GL_INFO_LOG_LENGTH, &logLen);
      if( logLen > 0 )
      {
        char * log = new char[logLen];
        GLsizei written;
        glGetProgramInfoLog(programHandle, logLen, &written, log);
        fprintf(stderr, "Program log: \n%s", log);
        delete [] log;
      }
    }
  5. If linking is successful, install the program into the OpenGL pipeline:

    else
    {
      glUseProgram( programHandle );
    }

How it works...

We start by calling glCreateProgram to create an empty program object. This function returns a handle to the program object, which we store in a variable named programHandle. If an error occurs with program creation, the function will return 0. We check for that, and if it occurs, we print an error message and exit.

Next, we attach each shader to the program object using glAttachShader. The first argument is the handle to the program object, and the second is the handle to the shader object to be attached.

Then, we link the program by calling glLinkProgram, providing the handle to the program object as the only argument. As with compilation, we check for the success or failure of the link, with the subsequent query.

We check the status of the link by calling glGetProgramiv. Similar to glGetShaderiv, glGetProgramiv allows us to query various attributes of the shader program. In this case, we ask for the status of the link by providing GL_LINK_STATUS as the second argument. The status is returned in the location pointed to by the third argument, in this case named status.

The link status is either GL_TRUE or GL_FALSE indicating the success or failure of the link. If the value of status is GL_FALSE, we retrieve and display the program information log, which should contain additional information and error messages. The program log is retrieved by the call to glGetProgramInfoLog. The first argument is the handle to the program object, the second is the size of the buffer to contain the log, the third is a pointer to a GLsizei variable where the number of bytes written to the buffer will be stored (excluding the null terminator), and the fourth is a pointer to the buffer that will store the log. The buffer can be allocated based on the size returned by the call to glGetProgramiv with the parameter GL_INFO_LOG_LENGTH. The string that is provided in log will be properly null terminated.

Finally, if the link is successful, we install the program into the OpenGL pipeline by calling glUseProgram, providing the handle to the program as the argument.

With the simple fragment shader from this recipe and the vertex shader from the previous recipe compiled, linked, and installed into the OpenGL pipeline, we have a complete OpenGL pipeline and are ready to begin rendering. Drawing a triangle and supplying different values for the Color attribute yields an image of a multi-colored triangle where the vertices are red, green, and blue, and inside the triangle, the three colors are interpolated, causing a blending of colors throughout.

There's more...

You can use multiple shader programs within a single OpenGL program. They can be swapped in and out of the OpenGL pipeline by calling glUseProgram to select the desired program.

Deleting a Shader program

If a program is no longer needed, it can be deleted from OpenGL memory by calling glDeleteProgram, providing the program handle as the only argument. This invalidates the handle and frees the memory used by the program. Note that if the program object is currently in use, it will not be immediately deleted, but will be flagged for deletion when it is no longer in use.

Also, the deletion of a shader program detaches the shader objects that were attached to the program but does not delete them unless those shader objects have already been flagged for deletion by a previous call to glDeleteShader.

See also

  • The Compiling a shader recipe

 

Sending data to a shader using vertex attributes and vertex buffer objects


The vertex shader is invoked once per vertex. Its main job is to process the data associated with the vertex, and pass it (and possibly other information) along to the next stage of the pipeline. In order to give our vertex shader something to work with, we must have some way of providing (per-vertex) input to the shader. Typically, this includes the vertex position, normal vector, and texture coordinates (among other things). In earlier versions of OpenGL (prior to 3.0), each piece of vertex information had a specific "channel" in the pipeline. It was provided to the shaders using functions such as glVertex, glTexCoord, and glNormal (or within client vertex arrays using glVertexPointer, glTexCoordPointer, or glNormalPointer). The shader would then access these values via built-in variables such as gl_Vertex and gl_Normal. This functionality was deprecated in OpenGL 3.0 and later removed. Instead, vertex information must now be provided using generic vertex attributes, usually in conjunction with (vertex) buffer objects. The programmer is now free to define an arbitrary set of per-vertex attributes to provide as input to the vertex shader. For example, in order to implement normal mapping, the programmer might decide that position, normal vector and tangent vector should be provided along with each vertex. With OpenGL 4, it's easy to define this as the set of input attributes. This gives us a great deal of flexibility to define our vertex information in any way that is appropriate for our application, but may require a bit of getting used to for those of us who are used to the old way of doing things.

In the vertex shader, the per-vertex input attributes are defined by using the GLSL qualifier in. For example, to define a 3-component vector input attribute named VertexColor, we use the following code:

in vec3 VertexColor;

Of course, the data for this attribute must be supplied by the OpenGL program. To do so, we make use of vertex buffer objects. The buffer object contains the values for the input attribute. In the main OpenGL program we make the connection between the buffer and the input attribute and define how to "step through" the data. Then, when rendering, OpenGL pulls data for the input attribute from the buffer for each invocation of the vertex shader.

For this recipe, we'll draw a single triangle. Our vertex attributes will be position and color. We'll use a fragment shader to blend the colors of each vertex across the triangle to produce an image similar to the one shown as follows. The vertices of the triangle are red, green, and blue, and the interior of the triangle has those three colors blended together. The colors may not be visible in the printed text, but the variation in the shade should indicate the blending.

Getting ready

We'll start with an empty OpenGL program, and the following shaders:

The vertex shader (basic.vert):

#version 430

layout (location=0) in vec3 VertexPosition;
layout (location=1) in vec3 VertexColor;

out vec3 Color;

void main()
{
  Color = VertexColor;

  gl_Position = vec4(VertexPosition,1.0);
}

Attributes are the input variables to a vertex shader. In the previous code, there are two input attributes: VertexPosition and VertexColor. They are specified using the GLSL keyword in. Don't worry about the layout prefix, we'll discuss that later. Our main OpenGL program needs to supply the data for these two attributes for each vertex. We will do so by mapping our polygon data to these variables.

It also has one output variable named Color, which is sent to the fragment shader. In this case, Color is just an unchanged copy of VertexColor. Also, note that the attribute VertexPosition is simply expanded and passed along to the built-in output variable gl_Position for further processing.

The fragment shader (basic.frag):

#version 430

in vec3 Color;

out vec4 FragColor;

void main() {
  FragColor = vec4(Color, 1.0);
}

There is just one input variable for this shader, Color. This links to the corresponding output variable in the vertex shader, and will contain a value that has been interpolated across the triangle based on the values at the vertices. We simply expand and copy this color to the output variable FragColor (more about fragment shader output variables in later recipes).

Write code to compile and link these shaders into a shader program (see "Compiling a shader" and "Linking a shader program"). In the following code, I'll assume that the handle to the shader program is programHandle.

How to do it...

Use the following steps to set up your buffer objects and render the triangle:

  1. Create a global (or private instance) variable to hold our handle to the vertex array object:

    GLuint vaoHandle;
  2. Within the initialization function, we create and populate the vertex buffer objects for each attribute:

    float positionData[] = {
          -0.8f, -0.8f, 0.0f,
          0.8f, -0.8f, 0.0f,
          0.0f,  0.8f, 0.0f };
    float colorData[] = {
          1.0f, 0.0f, 0.0f,
          0.0f, 1.0f, 0.0f,
          0.0f, 0.0f, 1.0f };
    
    // Create and populate the buffer objects
    GLuint vboHandles[2];
    glGenBuffers(2, vboHandles);
    GLuint positionBufferHandle = vboHandles[0];
    GLuint colorBufferHandle = vboHandles[1];
    
    // Populate the position buffer
    glBindBuffer(GL_ARRAY_BUFFER, positionBufferHandle);
    glBufferData(GL_ARRAY_BUFFER, 9 * sizeof(float), positionData, GL_STATIC_DRAW);
    
    // Populate the color buffer
    glBindBuffer(GL_ARRAY_BUFFER, colorBufferHandle);
    glBufferData(GL_ARRAY_BUFFER, 9 * sizeof(float), colorData, GL_STATIC_DRAW);
  3. Create and define a vertex array object, which defines the relationship between the buffers and the input attributes. (See "There's more…" for an alternate way to do this that is valid for OpenGL 4.3 and later.)

    // Create and set-up the vertex array object
    glGenVertexArrays( 1, &vaoHandle );
    glBindVertexArray(vaoHandle);
    
    // Enable the vertex attribute arrays
    glEnableVertexAttribArray(0);  // Vertex position
    glEnableVertexAttribArray(1);  // Vertex color
    
    // Map index 0 to the position buffer
    glBindBuffer(GL_ARRAY_BUFFER, positionBufferHandle);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, NULL);
    
    // Map index 1 to the color buffer
    glBindBuffer(GL_ARRAY_BUFFER, colorBufferHandle);
    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 0, NULL);
  4. In the render function, we bind to the vertex array object and call glDrawArrays to initiate rendering:

    glBindVertexArray(vaoHandle);
    glDrawArrays(GL_TRIANGLES, 0, 3 );

How it works...

Vertex attributes are the input variables to our vertex shader. In the given vertex shader, our two attributes are VertexPosition and VertexColor. The main OpenGL program refers to vertex attributes by associating each (active) input variable with a generic attribute index. These generic indices are simply integers between 0 and GL_MAX_VERTEX_ATTRIBS – 1. We can specify the relationship between these indices and the attributes using the layout qualifier. For example, in our vertex shader, we use the layout qualifier to assign VertexPosition to attribute index 0 and VertexColor to attribute index 1.

layout (location = 0) in vec3 VertexPosition;
layout (location = 1) in vec3 VertexColor;

We refer to the vertex attributes in our OpenGL code, by referring to the corresponding generic vertex attribute index.

Note

It is not strictly necessary to explicitly specify the mappings between attribute variables and generic attribute indexes, because OpenGL will automatically map active vertex attributes to generic indexes when the program is linked. We could then query for the mappings and determine the indexes that correspond to the shader's input variables. It may be somewhat more clear however, to explicitly specify the mapping as we do in this example.

The first step involves setting up a pair of buffer objects to store our position and color data. As with most OpenGL objects, we start by creating the objects and acquiring handles to the two buffers by calling glGenBuffers. We then assign each handle to a separate descriptive variable to make the following code more clear.

For each buffer object, we first bind the buffer to the GL_ARRAY_BUFFER binding point by calling glBindBuffer. The first argument to glBindBuffer is the target binding point. In this case, since the data is essentially a generic array, we use GL_ARRAY_BUFFER. Examples of other kinds of targets (such as GL_UNIFORM_BUFFER, or GL_ELEMENT_ARRAY_BUFFER) will be seen in later examples. Once our buffer object is bound, we can populate the buffer with our vertex/color data by calling glBufferData. The second and third arguments to this function are the size of the array and a pointer to the array containing the data. Let's focus on the first and last arguments. The first argument indicates the target buffer object. The data provided in the third argument is copied into the buffer that is bound to this binding point. The last argument is one that gives OpenGL a hint about how the data will be used so that it can determine how best to manage the buffer internally. For full details about this argument, take a look into the OpenGL documentation. In our case, the data is specified once, will not be modified, and will be used many times for drawing operations, so this usage pattern best corresponds to the value GL_STATIC_DRAW.

Now that we have set up our buffer objects, we tie them together into a Vertex Array Object (VAO). The VAO contains information about the connections between the data in our buffers and the input vertex attributes. We create a VAO using the function glGenVertexArrays. This gives us a handle to our new object, which we store in the (global) variable vaoHandle. Then we enable the generic vertex attribute indexes 0 and 1 by calling glEnableVertexAttribArray. Doing so indicates that that the values for the attributes will be accessed and used for rendering.

The next step makes the connection between the buffer objects and the generic vertex attribute indexes.

// Map index 0 to the position buffer
glBindBuffer(GL_ARRAY_BUFFER, positionBufferHandle);
glVertexAttribPointer( 0, 3, GL_FLOAT, GL_FALSE, 0, NULL );

First we bind the buffer object to the GL_ARRAY_BUFFER binding point, then we call glVertexAttribPointer, which tells OpenGL which generic index that the data should be used with, the format of the data stored in the buffer object, and where it is located within the buffer object that is bound to the GL_ARRAY_BUFFER binding point. The first argument is the generic attribute index. The second is the number of components per vertex attribute (1, 2, 3, or 4). In this case, we are providing 3-dimensional data, so we want 3 components per vertex. The third argument is the data type of each component in the buffer. The fourth is a Boolean which specifies whether or not the data should be automatically normalized (mapped to a range of [-1, 1] for signed integral values or [0, 1] for unsigned integral values). The fifth argument is the stride, which indicates the byte offset between consecutive attributes. Since our data is tightly packed, we can use a value of zero. The last argument is a pointer, which is not treated as a pointer! Instead, its value is interpreted as a byte offset from the beginning of the buffer to the first attribute in the buffer. In this case, there is no additional data in either buffer before the first element, so we use a value of zero.

Note

The glVertexAttribPointer function stores (in the VAO's state) a pointer to the buffer currently bound to the GL_ARRAY_BUFFER binding point. When another buffer is bound to that binding point, it does not change the value of the pointer.

The VAO stores all of the OpenGL state related to the relationship between buffer objects and the generic vertex attributes, as well as the information about the format of the data in the buffer objects. This allows us to quickly return all of this state when rendering. The VAO is an extremely important concept, but can be tricky to understand. It's important to remember that the VAO's state is primarily associated with the enabled attributes and their connection to buffer objects. It doesn't necessarily keep track of buffer bindings. For example, it doesn't remember what is bound to the GL_ARRAY_BUFFER binding point. We only bind to this point in order to set up the pointers via glVertexAttribPointer.

Once we have the VAO set up (a one-time operation), we can issue a draw command to render our image. In our render function, we clear the color buffer using glClear, bind to the vertex array object, and call glDrawArrays to draw our triangle. The function glDrawArrays initiates rendering of primitives by stepping through the buffers for each enabled attribute array, and passing the data down the pipeline to our vertex shader. The first argument is the render mode (in this case we are drawing triangles), the second is the starting index in the enabled arrays, and the third argument is the number of indices to be rendered (3 vertexes for a single triangle).

To summarize, we followed these steps:

  1. Make sure to specify the generic vertex attribute indexes for each attribute in the vertex shader using the layout qualifier.

  2. Create and populate the buffer objects for each attribute.

  3. Create and define the vertex array object by calling glVertexAttribPointer while the appropriate buffer is bound.

  4. When rendering, bind to the vertex array object and call glDrawArrays, or other appropriate rendering function (e.g. glDrawElements).

There's more...

In the following, we'll discuss some details, extensions, and alternatives to the previous technique.

Separate attribute format

With OpenGL 4.3, we have an alternate (arguably better) way of specifying the vertex array object state (attribute format, enabled attributes, and buffers). In the previous example, the glVertexAttribPointer function does two important things. First, it indirectly specifies which buffer contains the data for the attribute, which is the buffer currently bound (at the time of the call) to GL_ARRAY_BUFFER. Secondly, it specifies the format of that data (type, offset, stride, and so on). It is arguably clearer to separate these two concerns into their own functions. This is exactly what has been implemented in OpenGL 4.3. For example, to implement the same functionality as in step 3 of the previous How to do it… section, we would use the following code:

glGenVertexArrays(1, &vaoHandle);
glBindVertexArray(vaoHandle);
glEnableVertexArray(0);
glEnableVertexArray(1);

glBindVertexBuffer(0, positionBufferHandle, 0, sizeof(GLfloat)*3);
glBindVertexBuffer(1, colorBufferHandle, 0, sizeof(GLfloat)*3);

glVertexAttribFormat(0, 3, GL_FLOAT, GL_FALSE, 0);
glVertexAttribBinding(0, 0);
glVertexAttribFormat(1, 3, GL_FLOAT, GL_FALSE, 0);
glVertexAttribBinding(1, 1);

The first four lines of the previous code are exactly the same as in the first example. We create and bind to the VAO, then enable attributes 0 and 1. Next, we bind our two buffers to two different indexes within the vertex buffer binding point using glBindVertexBuffer. Note that we're no longer using GL_ARRAY_BUFFER binding point. Instead, we now have a new binding point specifically for vertex buffers. This binding point has several indexes (usually from 0 - 15), so we can bind multiple buffers to this point. The first argument to glBindVertexBuffer specifies the index within the vertex buffer binding point. Here, we bind our position buffer to index 0 and our color buffer to index 1.

Note

Note that the indexes within the vertex buffer binding point need not be the same as the attribute locations.

The other arguments to glBindVertexBuffer are as follows. The second argument is the buffer to be bound, the third is the offset from the beginning of the buffer to where the data begins, and the fourth is the stride, which is the distance between successive elements within the buffer. Unlike glVertexAttribPointer, we can't use a 0 value here for tightly packed data, because OpenGL can't determine the size of the data without more information, so we need to specify it explicitly here.

Next, we call glVertexAttribFormat to specify the format of the data for the attribute. Note that this time, this is decoupled from the buffer that stores the data. Instead, we're just specifying the format to expect for this attribute. The arguments are the same as the first four arguments to glVertexAttribPointer.

The function glVertexAttribBinding specifies the relationship between buffers that are bound to the vertex buffer binding point and attributes. The first argument is the attribute location, and the second is the index within the vertex buffer binding point. In this example, they are the same, but they need not be.

Also note that the buffer bindings of the vertex buffer binding point (specified by glBindVertexBuffer) are part of the VAO state, unlike the binding to GL_ARRAY_BUFFER, which is not.

This version is arguably more clear and easy to understand. It removes the confusing aspects of the "invisible" pointers that are managed in the VAO, and makes the relationship between attributes and buffers much more clear with glVertexAttribBinding. Additionally, it separates concerns that really need not be combined.

Fragment shader output

You may have noticed that I've neglected to say anything about the output variable FragColor in the fragment shader. This variable receives the final output color for each fragment (pixel). Like vertex input variables, this variable also needs to be associated with a proper location. Of course, we typically would like this to be linked to the back color buffer, which by default (in double buffered systems) is "color number" zero. (The relationship of the color numbers to render buffers can be changed by using glDrawBuffers). In this program, we are relying on the fact that the linker will automatically link our only fragment output variable to color number zero. To explicitly do so, we could (and probably should) have used a layout qualifier in the fragment shader:

layout (location = 0) out vec4 FragColor;

We are free to define multiple output variables for a fragment shader, thereby enabling us to render to multiple output buffers. This can be quite useful for specialized algorithms such as deferred rendering (see Chapter 5, Image Processing and Screen Space Techniques).

Specifying attribute indexes without using layout qualifiers

If you'd rather not clutter up your vertex shader code with the layout qualifiers (or you're using a version of OpenGL that doesn't support them), you can define the attribute indexes within the OpenGL program. We can do so by calling glBindAttribLocation just prior to linking the shader program. For example, we'd add the following code to the main OpenGL program just before the link step:

glBindAttribLocation(programHandle, 0, "VertexPosition");
glBindAttribLocation(programHandle, 1, "VertexColor");

This would indicate to the linker that VertexPosition should correspond to generic attribute index 0 and VertexColor to index 1.

Similarly, we can specify the color number for fragment shader output variables without using the layout qualifier. We do so by calling glBindFragDataLocation prior to linking the shader program:

glBindFragDataLocation(programHandle, 0, "FragColor");

This would tell the linker to bind the output variable FragColor to color number 0.

Using element arrays

It is often the case that we need to step through our vertex arrays in a non-linear fashion. In other words, we may want to "jump around" the data rather than just moving through it from beginning to end as we did in this example. For example, we might want to draw a cube where the vertex data consists of only eight positions (the corners of the cube). In order to draw the cube, we would need to draw 12 triangles (2 for each face), each of which consists of 3 vertices. All of the needed position data is in the original 8 positions, but to draw all the triangles, we'll need to jump around and use each position for at least three different triangles.

To jump around in our vertex arrays, we can make use of element arrays. The element array is another buffer that defines the indices used when stepping through the vertex arrays. For details on using element arrays, take a look at the function glDrawElements in the OpenGL documentation (http://www.opengl.org/sdk/docs/man).

Interleaved arrays

In this example, we used two buffers (one for color and one for position). Instead, we could have used just a single buffer and combined all of the data. In general, it is possible to combine the data for multiple attributes into a single buffer. The data for multiple attributes can be interleaved within an array, such that all of the data for a given vertex is grouped together within the buffer. Doing so just requires careful use of the stride argument to glVertexAttribPointer or glBindVertexBuffer. Take a look at the documentation for full details (http://www.opengl.org/sdk/docs/man).

The decision about when to use interleaved arrays and when to use separate arrays, is highly dependent on the situation. Interleaved arrays may bring better results due to the fact that data is accessed together and resides closer in memory (so-called locality of reference), resulting in better caching performance.

 

Getting a list of active vertex input attributes and locations


As covered in the previous recipe, the input variables within a vertex shader are linked to generic vertex attribute indices at the time the program is linked. If we need to specify the relationship, we can either use layout qualifiers within the shader, or we could call glBindAttribLocation before linking.

However, it may be preferable to let the linker create the mappings automatically and query for them after program linking is complete. In this recipe, we'll see a simple example that prints all the active attributes and their indices.

Getting ready

Start with an OpenGL program that compiles and links a shader pair. You could use the shaders from the previous recipe.

As in previous recipes, we'll assume that the handle to the shader program is stored in a variable named programHandle.

How to do it...

After linking and enabling the shader program, use the following code to display the list of active attributes:

  1. Start by querying for the number of active attributes:

    GLint numAttribs;
    glGetProgramInterfaceiv(programHandle, GL_PROGRAM_INPUT,GL_ACTIVE_RESOURCES, &numAttribs);
  2. Loop through each attribute and query for the length of the name, the type and the attribute location, and print the results to standard out:

    GLenum properties[] = {GL_NAME_LENGTH, GL_TYPE, GL_LOCATION};
    
    printf("Active attributes:\n");
    for( int i = 0; i < numAttribs; ++i ) {
      GLint results[3];
      glGetProgramResourceiv(programHhandle, GL_PROGRAM_INPUT,i, 3, properties, 3, NULL, results);
    
      GLint nameBufSize = results[0] + 1;
      char * name = new char[nameBufSize];
      glGetProgramResourceName(programHandle, GL_PROGRAM_INPUT, i, nameBufSize, NULL, name);
      printf("%-5d %s (%s)\n", results[2], name, getTypeString(results[1]));
      delete [] name;
    }

How it works...

In step 1, we query for the number of active attributes, by calling glGetProgramInterfaceiv. The first argument is the handle to the program object, and the second (GL_PROGRAM_INPUT) indicates that we are querying for information about the program input variables (the vertex attributes). The third argument (GL_ACTIVE_RESOURCES) indicates that we want the number of active resources. The result is stored in the location pointed to by the last argument numAttribs.

Now that we have the number of attributes, we query for information about each one. The indices of the attributes run from 0 to numAttribs-1. We loop over those indices and for each we call glGetProgramResourceiv to get the length of the name, the type and the location. We specify what information we would like to receive by means of an array of GLenum values called properties. The first argument is the handle to the program object, the second is the resource that we are querying (GL_PROGRAM_INPUT). The third is the index of the attribute, the fourth is the number of values in the properties array, which is the fifth argument. The properties array contains GLenums, which specify the specific properties we would like to receive. In this example, the array contains: GL_NAME_LENGTH, GL_TYPE, and GL_LOCATION, which indicates that we want the length of the attribute's name, the data type of the attribute and its location. The sixth argument is the size of the buffer that will receive the results; the seventh argument is a pointer to an integer that would receive the number of results that were written. If that argument is NULL, then no information is provided. Finally, the last argument is a pointer to a GLint array that will receive the results. Each item in the properties array corresponds to the same index in the results array.

Next, we retrieve the name of the attribute by allocating a buffer to store the name and calling glGetProgramResourceName. The results array contains the length of the name in the first element, so we allocate an array of that size with an extra character just for good measure. The OpenGL documentation says that the size returned from glGetProgramResourceiv includes the null terminator, but it doesn't hurt to make sure by making a bit of additional space. In my tests, I've found this to be necessary on the latest NVIDIA drivers.

Finally, we get the name by calling glGetProgramResourceName, and then print the information to the screen. We print the attribute's location, name and type. The location is available in the third element of the results array, and the type is in the second. Note the use of the function getTypeString. This is a simple custom function that just returns a string representation of the data type. The data type is represented by one of the OpenGL defined constants GL_FLOAT, GL_FLOAT_VEC2, GL_FLOAT_VEC3, and so on. The getTypeString function consists of just one big switch statement returning a human-readable string corresponding to the value of the parameter (see the source code for glslprogram.cpp in the example code for this book).

The output of the previous code looks like this when it is run on the shaders from the previous recipes:

Active attributes:
1    VertexColor (vec3)
0    VertexPosition (vec3)

There's more...

It should be noted that in order for a vertex shader input variable to be considered active, it must be used within the vertex shader. In other words, a variable is considered active if it is determined by the GLSL linker that it may be accessed during program execution. If a variable is declared within a shader, but not used, the previous code will not display the variable because it is not considered active and effectively ignored by OpenGL.

The previous code is only valid for OpenGL 4.3 and later. Alternatively, you can achieve similar results with the functions glGetProgramiv, glGetActiveAttrib and glGetAttribLocation.

See also

  • The Compiling a shader recipe

  • The Linking a shader program recipe

  • The Sending data to a shader using vertex attributes and vertex buffer objects recipe

 

Sending data to a shader using uniform variables


Vertex attributes provide one avenue for providing input to shaders; a second technique is uniform variables. Uniform variables are intended to be used for data that may change relatively infrequently compared to per-vertex attributes. In fact, it is simply not possible to set per-vertex attributes with uniform variables. For example, uniform variables are well suited for the matrices used for modeling, viewing, and projective transformations.

Within a shader, uniform variables are read-only. Their values can only be changed from outside the shader, via the OpenGL API. However, they can be initialized within the shader by assigning to a constant value along with the declaration.

Uniform variables can appear in any shader within a shader program, and are always used as input variables. They can be declared in one or more shaders within a program, but if a variable with a given name is declared in more than one shader, its type must be the same in all shaders. In other words, the uniform variables are held in a shared uniform namespace for the entire shader program.

In this recipe, we'll draw the same triangle as in previous recipes in this chapter, however, this time, we'll rotate the triangle using a uniform matrix variable.

Getting ready

We'll use the following vertex shader:

#version 430

layout (location = 0) in vec3 VertexPosition;
layout (location = 1) in vec3 VertexColor;

out vec3 Color;

uniform mat4 RotationMatrix;

void main()
{
  Color = VertexColor;
  gl_Position = RotationMatrix * vec4(VertexPosition,1.0);
}

Note the variable RotationMatrix is declared using the uniform qualifier. We'll provide the data for this variable via the OpenGL program. The RotationMatrix is also used to transform VertexPosition before assigning it to the default output position variable gl_Position.

We'll use the same fragment shader as in previous recipes:

#version 430

in vec3 Color;

layout (location = 0) out vec4 FragColor;

void main() {
  FragColor = vec4(Color, 1.0);
}

Within the main OpenGL code, we determine the rotation matrix and send it to the shader's uniform variable. To create our rotation matrix, we'll use the GLM library (see the Using the GLM for mathematics recipe in this chapter). Within the main OpenGL code, add the following include statements:

#include <glm/glm.hpp>
using glm::mat4;
using glm::vec3;

#include <glm/gtc/matrix_transform.hpp>

We'll also assume that code has been written to compile and link the shaders, and to create the vertex array object for the color triangle. We'll assume that the handle to the vertex array object is vaoHandle, and the handle to the program object is programHandle.

How to do it...

Within the render method, use the following code:

glClear(GL_COLOR_BUFFER_BIT);

mat4 rotationMatrix = glm::rotate(mat4(1.0f), angle, vec3(0.0f,0.0f,1.0f));

GLuint location = glGetUniformLocation(programHandle,"RotationMatrix");

if( location >= 0 )
{
  glUniformMatrix4fv(location, 1, GL_FALSE, &rotationMatrix[0][0]);
}

glBindVertexArray(vaoHandle);
glDrawArrays(GL_TRIANGLES, 0, 3 );

How it works...

The steps involved with setting the value of a uniform variable include finding the location of the variable, then assigning a value to that location using one of the glUniform functions.

In this example, we start by clearing the color buffer, then creating a rotation matrix using GLM. Next, we query for the location of the uniform variable by calling glGetUniformLocation. This function takes the handle to the shader program object, and the name of the uniform variable and returns its location. If the uniform variable is not an active uniform variable, the function returns -1.

We then assign a value to the uniform variable's location using glUniformMatrix4fv. The first argument is the uniform variable's location. The second is the number of matrices that are being assigned (note that the uniform variable could be an array). The third is a Boolean value indicating whether or not the matrix should be transposed when loaded into the uniform variable. With GLM matrices, a transpose is not required, so we use GL_FALSE here. If you were implementing the matrix using an array, and the data was in row-major order, you might need to use GL_TRUE for this argument. The last argument is a pointer to the data for the uniform variable.

There's more...

Of course uniform variables can be any valid GLSL type including complex types such as arrays or structures. OpenGL provides a glUniform function with the usual suffixes, appropriate for each type. For example, to assign to a variable of type vec3, one would use glUniform3f or glUniform3fv.

For arrays, one can use the functions ending in "v" to initialize multiple values within the array. Note that if it is desired, one can query for the location of a particular element of the uniform array using the [] operator. For example, to query for the location of the second element of MyArray:

GLuint location = glGetUniformLocation( programHandle, "MyArray[1]" );

For structures, the members of the structure must be initialized individually. As with arrays, one can query for the location of a member of a structure using something like the following:

GLuint location = glGetUniformLocation( programHandle, "MyMatrices.Rotation" );

Where the structure variable is MyMatrices and the member of the structure is Rotation.

See also

  • The Compiling a shader recipe

  • The Linking a shader program recipe

  • The Sending data to a shader using vertex attributes and vertex buffer objects recipe

 

Getting a list of active uniform variables


While it is a simple process to query for the location of an individual uniform variable, there may be instances where it can be useful to generate a list of all active uniform variables. For example, one might choose to create a set of variables to store the location of each uniform and assign their values after the program is linked. This would avoid the need to query for uniform locations when setting the value of the uniform variables, creating slightly more efficient code.

The process for listing uniform variables is very similar to the process for listing attributes (see the Getting a list of active vertex input attributes and locations recipe), so this recipe will refer the reader back to the previous recipe for detailed explanation.

Getting ready

Start with a basic OpenGL program that compiles and links a shader program. In the following, we'll assume that the handle to the program is in a variable named programHandle.

How to do it…

After linking and enabling the shader program, use the following code to display the list of active uniforms:

  1. Start by querying for the number of active uniform variables:

    GLint numUniforms = 0;
    glGetProgramInterfaceiv( handle, GL_UNIFORM, GL_ACTIVE_RESOURCES, &numUniforms);
  2. Loop through each uniform index and query for the length of the name, the type, the location and the block index:

    GLenum properties[] = {GL_NAME_LENGTH, GL_TYPE, GL_LOCATION, GL_BLOCK_INDEX};
    
    printf("Active uniforms:\n");
    for( int i = 0; i < numUniforms; ++i ) {
      GLint results[4];
      glGetProgramResourceiv(handle, GL_UNIFORM, i, 4, properties, 4, NULL, results);
      if( results[3] != -1 ) 
            continue;       // Skip uniforms in blocks 
      GLint nameBufSize = results[0] + 1;
      char * name = new char[nameBufSize];
      glGetProgramResourceName(handle, GL_UNIFORM, i, nameBufSize, NULL, name);
    printf("%-5d %s (%s)\n", results[2], name, getTypeString(results[1]));
      delete [] name;
    }

How it works...

The process is very similar to the process shown in the recipe Getting a list of active vertex input attributes and locations. I will focus on the main differences.

First and most obvious is that we use GL_UNIFORM instead of GL_PROGRAM_INPUT as the interface that we are querying in glGetProgramResourceiv and glGetProgramInterfaceiv. Second, we query for the block index (using GL_BLOCK_INDEX in the properties array). The reason for this is that some uniform variables are contained within a uniform block (see the recipe Using uniform blocks and uniform buffer objects). For this example, we only want information about uniforms that are not within blocks. The block index will be -1 if the uniform variable is not within a block, so we skip any uniform variables that do not have a block index of -1.

Again, we use the getTypeString function to convert the type value into a human-readable string (see example code).

When this is run on the shader program from the previous recipe, we see the following output:

Active uniforms:
0    RotationMatrix (mat4)

There's more...

As with vertex attributes, a uniform variable is not considered active unless it is determined by the GLSL linker that it will be used within the shader.

The previous code is only valid for OpenGL 4.3 and later. Alternatively, you can achieve similar results using the functions glGetProgramiv, glGetActiveUniform, glGetUniformLocation, and glGetActiveUniformName.

See also

  • The Sending data to a shader using uniform variables recipe

 

Using uniform blocks and uniform buffer objects


If your program involves multiple shader programs that use the same uniform variables, one has to manage the variables separately for each program. Uniform locations are generated when a program is linked, so the locations of the uniforms may change from one program to the next. The data for those uniforms may have to be regenerated and applied to the new locations.

Uniform blocks were designed to ease the sharing of uniform data between programs. With uniform blocks, one can create a buffer object for storing the values of all the uniform variables, and bind the buffer to the uniform block. When changing programs, the same buffer object need only be re-bound to the corresponding block in the new program.

A uniform block is simply a group of uniform variables defined within a syntactical structure known as a uniform block. For example, in this recipe, we'll use the following uniform block:

uniform BlobSettings {
  vec4 InnerColor;
  vec4 OuterColor;
  float RadiusInner;
  float RadiusOuter;
};

This defines a block with the name BlobSettings that contains four uniform variables. With this type of block definition, the variables within the block are still part of the global scope and do not need to be qualified with the block name.

The buffer object used to store the data for the uniforms is often referred to as a uniform buffer object. We'll see that a uniform buffer object is simply just a buffer object that is bound to a certain location.

For this recipe, we'll use a simple example to demonstrate the use of uniform buffer objects and uniform blocks. We'll draw a quad (two triangles) with texture coordinates, and use our fragment shader to fill the quad with a fuzzy circle. The circle is a solid color in the center, but at its edge, it gradually fades to the background color, as shown in the following image:

Getting ready

Start with an OpenGL program that draws two triangles to form a quad. Provide the position at vertex attribute location 0, and the texture coordinate (0 to 1 in each direction) at vertex attribute location 1 (see the Sending data to a shader using vertex attributes and vertex buffer objects recipe).

We'll use the following vertex shader:

#version 430

layout (location = 0) in vec3 VertexPosition;
layout (location = 1) in vec3 VertexTexCoord;

out vec3 TexCoord;

void main()
{
  TexCoord = VertexTexCoord;
  gl_Position = vec4(VertexPosition,1.0);
}

The fragment shader contains the uniform block, and is responsible for drawing our fuzzy circle:

#version 430

in vec3 TexCoord;
layout (location = 0) out vec4 FragColor;

layout (binding = 0) uniform BlobSettings {
  vec4 InnerColor;
  vec4 OuterColor;
  float RadiusInner;
  float RadiusOuter;
};

void main() {
  float dx = TexCoord.x - 0.5;
  float dy = TexCoord.y - 0.5;
  float dist = sqrt(dx * dx + dy * dy);
  FragColor =mix( InnerColor, OuterColor,smoothstep( RadiusInner, RadiusOuter, dist ));
}

Note the uniform block named BlobSettings. The variables within this block define the parameters of our fuzzy circle. The variable OuterColor defines the color outside of the circle. InnerColor is the color inside of the circle. RadiusInner is the radius defining the part of the circle that is a solid color (inside the fuzzy edge), and the distance from the center of the circle to the inner edge of the fuzzy boundary. RadiusOuter is the outer edge of the fuzzy boundary of the circle (when the color is equal to OuterColor).

The code within the main function computes the distance of the texture coordinate to the center of the quad located at (0.5, 0.5). It then uses that distance to compute the color by using the smoothstep function. This function provides a value that smoothly varies between 0.0 and 1.0 when the value of the third argument is between the values of the first two arguments. Otherwise it returns 0.0 or 1.0 depending on whether dist is less than the first or greater than the second, respectively. The mix function is then used to linearly interpolate between InnerColor and OuterColor based on the value returned by the smoothstep function.

How to do it...

In the OpenGL program, after linking the shader program, use the following steps to assign data to the uniform block in the fragment shader:

  1. Get the index of the uniform block using glGetUniformBlockIndex.

    GLuint blockIndex = glGetUniformBlockIndex(programHandle, "BlobSettings");
  2. Allocate space for the buffer to contain the data for the uniform block. We get the size using glGetActiveUniformBlockiv:

    GLint blockSize;
    glGetActiveUniformBlockiv(programHandle, blockIndex,GL_UNIFORM_BLOCK_DATA_SIZE, &blockSize);
    
    GLubyte * blockBuffer;
    blockBuffer = (GLubyte *) malloc(blockSize);
  3. Query for the offset of each variable within the block. To do so, we first find the index of each variable within the block:

    const GLchar *names[] = { "InnerColor", "OuterColor","RadiusInner", "RadiusOuter" };
    GLuint indices[4];
    glGetUniformIndices(programHandle, 4, names, indices);
    
    GLint offset[4];
    glGetActiveUniformsiv(programHandle, 4, indices, GL_UNIFORM_OFFSET, offset);
  4. Place the data into the buffer at the appropriate offsets:

    // Store data within the buffer at the appropriate offsets
    GLfloat outerColor[] = {0.0f, 0.0f, 0.0f, 0.0f};
    GLfloat innerColor[] = {1.0f, 1.0f, 0.75f, 1.0f};
    GLfloat innerRadius = 0.25f, outerRadius = 0.45f;
    
    memcpy(blockBuffer + offset[0], innerColor, 4 * sizeof(GLfloat));
    memcpy(blockBuffer + offset[1], outerColor, 4 * sizeof(GLfloat));
    memcpy(blockBuffer + offset[2], &innerRadius, sizeof(GLfloat));
    memcpy(blockBuffer + offset[3], &outerRadius, sizeof(GLfloat));
  5. Create the buffer object and copy the data into it:

    GLuint uboHandle;
    glGenBuffers( 1, &uboHandle );
    glBindBuffer( GL_UNIFORM_BUFFER, uboHandle );
    glBufferData( GL_UNIFORM_BUFFER, blockSize, blockBuffer, GL_DYNAMIC_DRAW );
  6. Bind the buffer object to the uniform buffer binding point at the index specified by the binding layout qualifier in the fragment shader (0):

    glBindBufferBase(GL_UNIFORM_BUFFER, 0, uboHandle);

How it works...

Phew! This seems like a lot of work! However, the real advantage comes when using multiple programs where the same buffer object can be used for each program. Let's take a look at each step individually.

First we get the index of the uniform block by calling glGetUniformBlockIndex, then we query for the size of the block by calling glGetActiveUniformBlockiv. After getting the size, we allocate a temporary buffer named blockBuffer to hold the data for our block.

The layout of data within a uniform block is implementation dependent, and implementations may use different padding and/or byte alignment. So in order to accurately layout our data, we need to query for the offset of each variable within the block. This is done in two steps. First, we query for the index of each variable within the block by calling glGetUniformIndices. This accepts an array of variable names (third argument) and returns the indices of the variables in the array indices (fourth argument). Then we use the indices to query for the offsets by calling glGetActiveUniformsiv. When the fourth argument is GL_UNIFORM_OFFSET, this returns the offset of each variable in the array pointed to by the fifth argument. This function can also be used to query for the size and type, however, in this case we choose not to do so, to keep the code simple (albeit less general).

The next step involves filling our temporary buffer blockBuffer with the data for the uniforms at the appropriate offsets. Here we use the standard library function memcpy to accomplish this.

Now that the temporary buffer is populated with the data with the appropriate layout, we can create our buffer object and copy the data into the buffer object. We call glGenBuffers to generate a buffer handle, and then bind that buffer to the GL_UNIFORM_BUFFER binding point by calling glBindBuffer. The space is allocated within the buffer object and the data is copied when glBufferData is called. We use GL_DYNAMIC_DRAW as the usage hint here because uniform data may be changed somewhat often during rendering. Of course, this is entirely dependent on the situation.

Finally, we associate the buffer object with the uniform block by calling glBindBufferBase. This function binds to an index within a buffer binding point. Certain binding points are also so-called "indexed buffer targets". This means that the target is actually an array of targets, and glBindBufferBase allows us to bind to one index within the array. In this case, we bind it to the index that we specified in the layout qualifier in the fragment shader: layout (binding = 0) (see the "Getting ready…" section). These two indices must match.

Note

You might be wondering why we use glBindBuffer and glBindBufferBase with GL_UNIFORM_BUFFER. Aren't these the same binding points used in two different contexts? The answer is that the GL_UNIFORM_BUFFER point can be used in each function with a slightly different meaning. With glBindBuffer, we bind to a point that can be used for filling or modifying a buffer, but can't be used as a source of data for the shader. When we use glBindBufferBase, we are binding to an index within a location that can be directly sourced by the shader. Granted, that's a bit confusing.

There's more...

If the data for a uniform block needs to be changed at some later time, one can call glBufferSubData to replace all or part of the data within the buffer. If you do so, don't forget to first bind the buffer to the generic binding point GL_UNIFORM_BUFFER.

Using an instance name with a uniform block

A uniform block can have an optional instance name. For example, with our BlobSettings block we could have used the instance name Blob, as shown here:

uniform BlobSettings {
  vec4 InnerColor;
  vec4 OuterColor;
  float RadiusInner;
  float RadiusOuter;
} Blob;

In this case, the variables within the block are placed within a namespace qualified by the instance name. Therefore our shader code needs to refer to them prefixed with the instance name. For example:

FragColor =mix( Blob.InnerColor, Blob.OuterColor,smoothstep( Blob.RadiusInner, Blob.RadiusOuter, dist )
  );

Additionally, we need to qualify the variable names (with the block name: BlobSettings) within the OpenGL code when querying for variable indices:

const GLchar *names[] = { "BlobSettings.InnerColor", 
      "BlobSettings.OuterColor", "BlobSettings. RadiusInner",
      "BlobSettings.RadiusOuter" };
GLuint indices[4];
glGetUniformIndices(programHandle, 4, names, indices);

Using layout qualifiers with uniform blocks

Since the layout of the data within a uniform buffer object is implementation dependent, it required us to query for the variable offsets. However, one can avoid this by asking OpenGL to use the standard layout std140. This is accomplished by using a layout qualifier when declaring the uniform block. For example:

layout( std140 ) uniform BlobSettings {
  
};

The std140 layout is described in detail within the OpenGL specification document (available at http://www.opengl.org).

Other options for the layout qualifier that apply to uniform block layouts include packed and shared. The packed qualifier simply states that the implementation is free to optimize memory in whatever way it finds necessary (based on variable usage or other criteria). With the packed qualifier, we still need to query for the offsets of each variable. The shared qualifier guarantees that the layout will be consistent between multiple programs and program stages provided that the uniform block declaration does not change. If you are planning to use the same buffer object between multiple programs and/or program stages, it is a good idea to use the shared option.

There are two other layout qualifiers that are worth mentioning: row_major and column_major. These define the ordering of data within the matrix type variables within the uniform block.

One can use multiple (non-conflicting) qualifiers for a block. For example, to define a block with both the row_major and shared qualifiers, we would use the following syntax:

layout( row_major, shared ) uniform BlobSettings {
  
};

See also

  • The Sending data to a shader using uniform variables recipe

 

Getting debug messages


Prior to recent versions of OpenGL, the traditional way to get debug information was to call glGetError. Unfortunately, that is an exceedingly tedious method for debugging a program. The glGetError function returns an error code if an error has occurred at some point previous to the time the function was called. This means that if we're chasing down a bug, we essentially need to call glGetError after every function call to an OpenGL function, or do a binary search-like process where we call it before and after a block of code, and then move the two calls closer to each other until we determine the source of the error. What a pain!

Thankfully, as of OpenGL 4.3, we now have support for a more modern method for debugging. Now we can register a debug callback function that will be executed whenever an error occurs, or other informational message is generated. Not only that, but we can send our own custom messages to be handled by the same callback, and we can filter the messages using a variety of criteria.

Getting ready

Create an OpenGL program with a debug context. While it is not strictly necessary to acquire a debug context, we might not get messages that are as informative as when we are using a debug context. To create an OpenGL context using GLFW with debugging enabled, use the following function call prior to creating the window.

glfwWindowHint(GLFW_OPENGL_DEBUG_CONTEXT, GL_TRUE);

An OpenGL debug context will have debug messages enabled by default. If, however, you need to enable debug messages explicitly, use the following call.

glEnable(GL_DEBUG_OUTPUT);

How to do it...

Use the following steps:

  1. Create a callback function to receive the debug messages. The function must conform to a specific prototype described in the OpenGL documentation. For this example, we'll use the following one:

    void debugCallback(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar * message, void * param) {
    
        // Convert GLenum parameters to strings
    
    
      printf("%s:%s[%s](%d): %s\n", sourceStr, typeStr, severityStr, id, message);
    }
  2. Register our callback with OpenGL using glDebugMessageCallback:

    glDebugMessageCallback( debugCallback, NULL );
  3. Enable all messages, all sources, all levels, and all IDs:

    glDebugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DONT_CARE, 0, NULL, GL_TRUE);

How it works...

The callback function debugCallback has several parameters, the most important of which is the debug message itself (the sixth parameter, message). For this example, we simply print the message to standard output, but we could send it to a log file or some other destination.

The first four parameters to debugCallback describe the source, type, id number, and severity of the message. The id number is an unsigned integer specific to the message. The possible values for the source, type and severity parameters are described in the following tables.

The source parameter can have any of the following values:

Source

Generated By

GL_DEBUG_SOURCE_API

Calls to the OpenGL API

GL_DEBUG_SOURCE_WINDOW_SYSTEM

Calls to a window system API

GL_DEBUG_SOURCE_THIRD_PARTY

An application associated with OpenGL

GL_DEBUG_SOURCE_APPLICATION

This application itself.

GL_DEBUG_SOURCE_OTHER

Some other source

The type parameter can have any of the following values:

Type

Description

GL_DEBUG_TYPE_ERROR

An error from the OpenGL API.

GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR

Behavior that has been deprecated

GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR

Undefined behaviour

GL_DEBUG_TYPE_PORTABILITIY

Some functionality is not portable.

GL_DEBUG_TYPE_PERFORMANCE

Possible performance issues

GL_DEBUG_TYPE_MARKER

An annotation

GL_DEBUG_TYPE_PUSH_GROUP

Messages related to debug group push.

GL_DEBUG_TYPE_POP_GROUP

Messages related to debug group pop.

GL_DEBUG_TYPE_OTHER

Other messages

The severity parameter can have the following values:

Severity

Meaning

GL_DEBUG_SEVERITY_HIGH

Errors or dangerous behaviour

GL_DEBUG_SEVERITY_MEDIUM

Major performance warnings, other warnings or use of deprecated functionality.

GL_DEBUG_SEVERITY_LOW

Redundant state changes, unimportant undefined behaviour.

GL_DEBUG_SEVERITY_NOTIFICATION

A notification, not an error or performance issue.

The length parameter is the length of the message string, excluding the null terminator. The last parameter param is a user-defined pointer. We can use this to point to some custom object that might be helpful to the callback function. For example, if we were logging the messages to a file, this could point to an object containing file I/O capabilities. This parameter can be set using the second parameter to glDebugMessageCallback (more on that in the following content).

Within debugCallback we convert each GLenum parameter into a string. Due to space constraints, I don't show all of that code here, but it can be found in the example code for this book. We then print all of the information to standard output.

The call to glDebugMessageCallback registers our callback function with the OpenGL debug system. The first parameter is a pointer to our callback function, and the second parameter (NULL in this example) can be a pointer to any object that we would like to pass into the callback. This pointer is passed as the last parameter with every call to debugCallback.

Finally, the call to glDebugMessageControl determines our message filters. This function can be used to selectively turn on or off any combination of message source, type, id, or severity. In this example, we turn everything on.

There's more...

OpenGL also provides support for stacks of named debug groups. Essentially what this means is that we can remember all of our debug message filter settings on a stack and return to them later after some changes have been made. This might be useful, for example, if there are sections of code where we have needs for filtering some kinds of messages and other sections where we want a different set of messages.

The functions involved are glPushDebugGroup and glPopDebugGroup. A call to glPushDebugGroup generates a debug message with type GL_DEBUG_TYPE_PUSH_GROUP, and retains the current state of our debug filters on a stack. We can then change our filters using glDebugMessageControl, and later return to the original state using glPopDebugGroup. Similarly, the function glPopDebugGroup generates a debug message with type GL_DEBUG_TYPE_POP_GROUP.

 

Building a C++ shader program class


If you are using C++, it can be very convenient to create classes to encapsulate some of the OpenGL objects. A prime example is the shader program object. In this recipe, we'll look at a design for a C++ class that can be used to manage a shader program.

Getting ready

There's not much to prepare for with this one, you just need a build environment that supports C++. Also, I'll assume that you are using GLM for matrix and vector support, if not just leave out the functions involving the GLM classes.

How to do it...

First, we'll use a custom exception class for errors that might occur during compilation or linking:

class GLSLProgramException : public std::runtime_error {
public:
  GLSLProgramException( const string & msg ) : std::runtime_error(msg) { }
};

We'll use an enum for the various shader types:

namespace GLSLShader {
  enum GLSLShaderType {
        VERTEX = GL_VERTEX_SHADER, 
        FRAGMENT = GL_FRAGMENT_SHADER, 
        GEOMETRY = GL_GEOMETRY_SHADER, 
        TESS_CONTROL = GL_TESS_CONTROL_SHADER, 
        TESS_EVALUATION = GL_TESS_EVALUATION_SHADER, 
        COMPUTE = GL_COMPUTE_SHADER
  };
};

The program class itself has the following interface:

class GLSLProgram
{
private:
  int  handle;
  bool linked;
  std::map<string, int> uniformLocations;

  int  getUniformLocation(const char * name );

   // A few other helper functions

public:
  GLSLProgram();
  ~GLSLProgram();

  void compileShader( const char * filename )throw(GLSLProgramException);
  void compileShader( const char * filename, GLSLShader::GLSLShaderType type )throw(GLSLProgramException);
  void compileShader( const string & source, GLSLShader::GLSLShaderType type,const char * filename = NULL )throw(GLSLProgramException);
  void link()      throw(GLSLProgramException);
  void use()       throw(GLSLProgramException);
  void validate()  throw(GLSLProgramException);

  int    getHandle();
  bool   isLinked();

  void   bindAttribLocation( GLuint location, const char * name);
  void   bindFragDataLocation( GLuint location, const char * name );
  void   setUniform(const char *name, float x, float y,float z);
  void   setUniform(const char *name, const vec3 & v);
  void   setUniform(const char *name, const vec4 & v);
  void   setUniform(const char *name, const mat4 & m);
  void   setUniform(const char *name, const mat3 & m);
  void   setUniform(const char *name, float val );
  void   setUniform(const char *name, int val );
  void   setUniform(const char *name, bool val );

  void   printActiveUniforms();
  void   printActiveAttribs();
  void   printActiveUniformBlocks();
};

Tip

Code Download Tip

You can download the example code files for all Packt books you have purchased from your account at http://www.packtpub.com. If you purchased this book elsewhere, you can visit http://www.packtpub.com/support and register to have the files e-mailed directly to you.

Full source code for all of the recipes in this text is also available on GitHub at: https://github.com/daw42/glslcookbook.

The techniques involved in the implementation of these functions are covered in previous recipes in this chapter. Due to space limitations, I won't include the code here (it's available from this book's GitHub repository), but we'll discuss some of the design decisions in the next section.

How it works...

The state stored within a GLSLProgram object includes the handle to the OpenGL shader program object (handle), a Boolean variable indicating whether or not the program has been successfully linked (linked), and a map used to store uniform locations as they are discovered (uniformLocations).

The compileShader overloads will throw a GLSLProgramException if the compilation fails. The first version determines the type of shader based on the filename extension. In the second version, the caller provides the shader type, and the third version is used to compile a shader, taking the shader's source code from a string. The file name can be provided as a third argument in the case that the string was taken from a file, which is helpful for providing better error messages.

The GLSLProgramException's error message will contain the contents of the shader log or program log when an error occurs.

The private function getUniformLocation is used by the setUniform functions to find the location of a uniform variable. It checks the map uniformLocations first, and if the location is not found, queries OpenGL for the location, and stores the result in the map before returning. The fileExists function is used by compileShaderFromFile to check for file existence.

The constructor simply initializes linked to false and handle to zero. The variable handle will be initialized by calling glCreateProgram when the first shader is compiled.

The link function simply attempts to link the program by calling glLinkProgram. It then checks the link status, and if successful, sets the variable linked to true and returns true. Otherwise, it gets the program log (by calling glGetProgramInfoLog), stores the result in a GLSLProgramException and throws it.

The use function simply calls glUseProgram if the program has already been successfully linked, otherwise it does nothing.

The functions getHandle and isLinked are simply "getter" functions that return the handle to the OpenGL program object and the value of the linked variable.

The functions bindAttribLocation and bindFragDataLocation are wrappers around glBindAttribLocation and glBindFragDataLocation. Note that these functions should only be called prior to linking the program.

The setUniform overloaded functions are straightforward wrappers around the appropriate glUniform functions. Each of them calls getUniformLocation to query for the variable's location before calling the glUniform function.

Finally, the printActiveUniforms, printActiveUniformBlocks, and printActiveAttribs functions are useful for debugging purposes. They simply display a list of the active uniforms/attributes to standard output.

The following is a simple example of the use of the GLSLProgram class:

GLSLProgram prog;

try {
  prog.compileShader("myshader.vert");
  prog.compileShader("myshader.frag");
  prog.link();
  prog.validate();
  prog.use();
} catch( GLSLProgramException &e ) {
  cerr << e.what() << endl;
  exit(EXIT_FAILURE);
}


prog.printActiveUniforms();
prog.printActiveAttribs();

prog.setUniform("ModelViewMatrix", matrix);
prog.setUniform("LightPosition", 1.0f, 1.0f, 1.0f);

See also

About the Author

  • David Wolff

    David Wolff is a professor in the computer science department at Pacific Lutheran University (PLU). He received a PhD in Physics and an MS in computer science from Oregon State University. He has been teaching computer graphics to undergraduates at PLU for over 17 years, using OpenGL.

    Browse publications by this author

Latest Reviews

(5 reviews total)
Superb reference into the deeper corners of GLSL. I'm fairly new to OpenGL and so had to cover some of the fundamentals before delving into this book. But now with some experience, it has proven itself MOST invaluable. If you use GLSL you need this,
Very informative book on OpenGL shaders. Helped me a lot to get updated on the recent glsl shaders.
Gostei muito, livro bem escrito.