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.
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.
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.
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.
You can call
to have GlShaderMgr delete all compiled shaders. This invalidates
all the shaders returned by
IV. Miscellanea
GlShaderMgr::FinalizeCompileAndLink();
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).