The GlShaderMgr is a C++ utility for managing GLSL (GL Shader Language) programs. It allows reading GLSL programs from files, with multiple code blocks stored in a single file. It also compiles and links programs with OpenGL, including error checking.

This page describes the features and usage of GlShaderMgr. Examples of its usage can be found in other programs on these web pages. GlShaderMgr is a component of a series of programs illustrating features of Modern OpenGL.

For an example of the simplest way to use GlShaderMgr, see the sample code in SimpleDrawShaderMgr, along with the page explaining how it uses GlShaderMgr.

I. File format for .GLSL files

GlShaderMgr uses a special format for .glsl files, which allows multiple blocks of code to be included in a single file. A block of code can be an entire shader program, such as a vertex shader or a fragment shader. A block of code can also be a portion of a shader program, such as a separate function, or block of variable definitions, that is meant to be part of a shader program. You can combine different code blocks by concatenating them to form an entire shader source code.

Code blocks in .glsl files are marked by directives #beginglsl and #endglsl. The syntax for a #beginglsl is:

    #beginglsl BLOCKTYPE BLOCKNAME

where BLOCKTYPE must be one of the values "VertexShader", "FragmentShader", "GeometryShader", or "CodeBlock". (Currently only these four types are supported, but more will be added.) The value BLOCKNAME is a user-supplied unique name for the code block. For example, a single .glsl file might contain:

    #beginglsl VertexShader myVertShader
	…
    #endglsl
    #beginglsl FragmentShader myFragShader
	…
    #endglsl
    #beginglsl CodeBlock myFunc
	…
    #endglsl

The ellipses (…) indicate lines of GLSL code. The #beginglsl, #endglsl pairs delimit blocks of code; any lines in the .glsl file outside the scope of a #beginglsl, #endglsl pair are ignored. (The block type specifiers, e.g. "VertexShader", are not case sensitive, but the block names are case sensitive.)

The convention of using #beginglsl, #endglsl pairs is intended as a replacement for having a #include directive. A big advantage to this approach is that it means that multiple shaders can be stored in a single file. The block type of CodeBlock is used in GlShader to allow concatenation of different code blocks to form a single shader. For instance, a complete fragment shader can be formed by concatenating multiple code blocks, one of type FragmentShader and the rest of type CodeBlock. This means that a single code block can be shared with multiple shaders (duplicating the functionality of #include). Using code blocks also allows additional flexibility in building multiple shaders that combine common code from different code blocks; for instance, a single fragment shader main program can be reused by combining it with different functions defined in different code blocks.

We describe below how to use GlShaderMgr::CompileShader() to compile shaders formed from multiple code blocks. For instance, to define and compile a vertex shader, you give GlShaderMgr::CompileShader() a list of blocks of code: exactly one must be of type VertexShader; the rest must be of type CodeBlock. The blocks are concatenated and compiled, and are then available for linking.

II. Loading shader code from files and strings

GlShaderMgr lets you load GLSL code from either a file or a string. To read from a file, use the command

    GlShaderMgr::LoadShaderSource("myShaderFile.glsl");

The only parameter is the filename, and it expects the file to contain code delimited by #beginglsl, #endglsl pairs. It is fine to use multiple calls to LoadShaderSource to load code from multiple files. There should, however, be no duplicated code block names (BLOCKNAME's).

It is also possible use a single call to LoadShaderSource to load multiple files. The function prototype for this is:

    bool GlShaderMgr::LoadShaderSource(int numFiles, const char* filenamePtr[])

This expects filenamePtr to be an array of pointers to null-terminated filenames. It just issues multiple calls to GlShaderMgr::LoadShaderSource(char*) to read the files.

There are a couple "legacy" methods that let you use GLSL code that does not contain #beginglsl, #endglsl pairs. The first reads the code from a file; the syntax is

    GlShaderMgr::LoadSingleShaderFile("myFileName.glsl", "BLOCKTYPE", "BLOCKNAME");

For LoadSingleShaderFile there should be no #beginglsl, #endglsl directives in the file. Instead, the second and third parameters specify the block type and the block name in lieu of a #beginglsl directive. The block type (BLOCKTYPE) should be one of the supported blocks types listed above, and the code block name (BLOCKNAME) is a user-specified name.

It is also possible to load a block of shader code from a string. The syntax for this is

    GlShaderMgr::LoadSingleShaderString(shaderSourcePtr,  "BLOCKTYPE", "BLOCKNAME");

where shaderSourcePtr is a pointer to a null-terminated string containing the code block.

LoadShaderSource and LoadSingleShaderFile return true if the file is successfully opened and parsed into code blocks. Otherwise, they output an error message to stderr, and return false. LoadSingleShaderString returns true if the parameters are valid, and otherwise outputs an print an error message to stderr. None of these calls do any compilation, or even syntax checking, of the shader code.

III. Compiling and linking

TERMINOLOGY: Shaders versus Shader Programs: The term "shader" means an individual shader such as a vertex shader, fragment shader, geometry shader. Each shader is a small program. The term "shader program" means a set of shaders that have been combined to run together as a complete rendering program. For example, a shader program might consist of a vertex shader plus a fragment shader. We "compile" source code to form a "shader". To "link a shader program" means to join together already-compiled shaders to form a "shader program".

After the shader code has been loaded, the shaders and shader programs can be compiled and linked. The simplest case of compiling and linking a shader program is when is a single shader of each type, and there are no CodeBlock pieces. (For an example, see SimpleDrawShaderMgr.) For instance, you might have a single vertex shader and a single fragment shader stored in a GLSL file. After the shaders have been read in, they can compiled and linked using

    unsigned int shaderProgram = GlShaderMgr::CompileAndLinkAll();

The returned value, shaderProgram, is the OpenGL handle for a shader program. It is zero if an error occurred when compiling or linking; when an error occurs a message is written to stderr.

In most cases, however, your program will need more than one shader program, so CompileAndLinkAll cannot be used. If there are no independent CodeBlock's, then CompileAndLinkProgram lets you compile and link two or three shaders to form a shader program with a single call. As an example, the program Chapter1Figs uses three vertex shaders and two fragment shaders to make three shader programs, using the following code:

    unsigned int shaderProgram1, shaderProgram2, shaderProgram3;
    GlShaderMgr::LoadShaderSource("Chapter1Figs.glsl");
    shaderProgram1 = GlShaderMgr::CompileAndLinkProgram("vertexShader_Chap1Figs", "fragmentShader_Chap1Figs");
    shaderProgram2 = GlShaderMgr::CompileAndLinkProgram("vertexShader_FlatChap1Figs", "fragmentShader_FlatChap1Figs");
    shaderProgram3 = GlShaderMgr::CompileAndLinkProgram("vertexShader_Chap1Figs", "fragmentShader_BrightColor");

CompileAndLinkProgram takes the names of either two or three code blocks as inputs. Each code block must be a complete shader, for instance a vertex shader. The value returned by CompileAndLinkProgram is the OpenGL handle for a shader (as created by OpenGL's glCreateShader), or equals zero if an error occurs during compilation.

When there are independent code blocks, it is necessary to first compile the shaders by calling CompileShader. CompileShader takes the names of one, two or three blocks of shader code. One (and only one) of these code blocks must be declared as a VertexShader, a FragmentShader, or a GeometryShader. The rest of the code blocks must have type CodeBlock. CompileShader concatenates these blocks of code, in the order specified, and compiles them to form a shader.

Once the individual shaders have been compiled, they are linked into a shader program using LinkShaderProgram. This is best illustrated by a example, so here is the code (slightly modified) used by EduPhong.cpp to compile two shader programs:

    GlShaderMgr::LoadShaderSource("EduPhong.glsl");

    unsigned int shader_VPG = GlShaderMgr::CompileShader("vertexShader_PhongGouraud", "calcPhongLighting");
    unsigned int shader_FPG = GlShaderMgr::CompileShader("fragmentShader_PhongGouraud", "applyTextureMap");
    unsigned int shaders_PG[2] = { shader_VPG, shader_FPG };
    unsigned int phShaderPhongGouraud = GlShaderMgr::LinkShaderProgram(2, shaders_PG);

    unsigned int shader_VPP = GlShaderMgr::CompileShader("vertexShader_PhongPhong");
    unsigned int shader_FPP = GlShaderMgr::CompileShader("fragmentShader_PhongPhong", "calcPhongLighting", "applyTextureMap");
    unsigned int shaders_PP[2] = { shader_VPP, shader_FPP };
    unsigned int phShaderPhongPhong = GlShaderMgr::LinkShaderProgram(2, shaders_PP);

The first argument to LinkShaderProgram is the number shaders to be linked. The second argument is a pointer to an array of shader names (unsigned integers).

There is a more general version of CompileShader which can take arbitrarily many code blocks as input. This version has two parameters, the number of code blocks, and an array of char* values. Its C++ function prototype is

    unsigned int GlShaderMgr::CompileShader(int numcodeBlocks, const char* shaderCodeNames[]);

The above examples only used a vertex and fragment shader. For examples of how to also use a geometry shader, see the code in the SimpleGeometryShader and GlGeomShapesTester programs.

IV. Miscellanea

You can call

    GlShaderMgr::FinalizeCompileAndLink();

to have GlShaderMgr delete all compiled shaders. This invalidates all the shaders returned by CompileShader, but does not invalidate any shader programs (as returned for instance by LinkShaderProgram). It also deallocates all the source code for shaders to free up space. The function CompileAndLinkAll automatically calls FinalizeCompileAndLink.

CompileAndLinkProgram keeps track of which shaders have already been compiled to avoid re-compilation of the same shader (at least when CodeBlock's are not used).