Home > Articles

Shader Fundamentals

This chapter is from the book

An Overview of the OpenGL Shading Language

This section provides an overview of the shading language used within OpenGL. GLSL shares many traits with C++ and Java, and is used for authoring shaders for all the stages supported in OpenGL, although certain features are available only for particular types of shaders. We will first describe GLSL’s requirements, types, and other language constructs that are shared between the various shader stages, and then discuss the features unique to each type of shader.

Creating Shaders with GLSL

Here, we describe how to create a complete shader.

The Starting Point

A shader program, just like a C program, starts execution in main(). Every GLSL shader program begins life as follows:

#version 330 core

void
main()
{
    // Your code goes here
}

The // construct is a comment and terminates at the end of the current line, just like in C. Additionally, C-type, multi-line comments—the /* and */ type—are also supported. However, unlike ANSI C, main() does not return an integer value; it is declared void. Also, as with C and its derivative languages, statements are terminated with a semicolon. While this is a perfectly legal GLSL program that compiles and even runs, its functionality leaves something to be desired. To add a little more excitement to our shaders, we’ll continue by describing variables and their operation.

Declaring Variables

GLSL is a typed language; every variable must be declared and have an associated type. Variable names conform to the same rules as those for C: You can use letters, numbers, and the underscore character (_) to compose variable names. However, a digit cannot be the first character in a variable name. Similarly, variable names cannot contain consecutive underscores; those names are reserved in GLSL.

Table 2.1 shows the basic types available in GLSL.

Table 2.1 Basic Data Types in GLSL

Type

Description

float

IEEE 32-bit floating-point value

double

IEEE 64-bit floating-point value

int

signed two´s-complement 32-bit integer value

uint

unsigned 32-bit integer value

bool

Boolean value

These types (and later, aggregate types composed of these) are all transparent. That is, their internal form is exposed and the shader code gets to assume what they look like internally.

An additional set of types, the opaque types, do not have their internal form exposed. These include sampler types, image types, and atomic counter types. They declare variables used as opaque handles for accessing texture maps, images, and atomic counters, as described in Chapter 4, “Color, Pixels, and Fragments.”

The various types of samplers and their uses are discussed in Chapter 6, “Textures and Framebuffers.”

Variable Scoping

While all variables must be declared, they may be declared any time before their use (just as in C++). The scoping rules of GLSL, which closely parallel those of C++, are as follows:

  • Variables declared outside of any function definition have global scope and are visible to all subsequent functions within the shader program.

  • Variables declared within a set of curly braces (e.g., function definition, block following a loop or “if” statement, etc.) exist within the scope of those braces only.

  • Loop iteration variables, such as i in the loop

    for (int i = 0; i < 10; ++i) {
         // loop body
    }
  • are scoped only for the body of the loop.

Variable Initialization

Variables may also be initialized when declared. For example:

int     i, numParticles = 1500;
float   force, g = -9.8;
bool    falling = true;
double  pi = 3.1415926535897932384626LF;

Integer literal constants may be expressed as octal, decimal, or hexadecimal values. An optional minus sign before a numeric value negates the constant, and a trailing ‘u’ or ‘U’ denotes an unsigned integer value.

Floating-point literals must include a decimal point, unless described in scientific format, as in 3E-7. (However, there are many situations where an integer literal will be implicitly converted to a floating-point value.) Additionally, they may optionally include an ‘f’ or ‘F’ suffix as in C on a float literal. You must include a suffix of ‘lF’ or ‘LF’ to make a literal have the precision of a double.

Boolean values are either true or false and can be initialized to either of those values or as the result of an operation that resolves to a Boolean expression.

Constructors

As mentioned, GLSL is more type safe than C++, having fewer implicit conversion between values. For example,

int f = false;

will result in a compilation error due to assigning a Boolean value to an integer variable. Types will be implicitly converted as shown in Table 2.2.

Table 2.2 Implicit Conversions in GLSL

Type Needed

Can Be Implicitly Converted From

uint

int

float

int, uint

double

int, uint, float

These type conversions work for scalars, vectors, and matrices of these types. Conversions will never change whether something is a vector or a matrix, or how many components it has. Conversions also don’t apply to arrays or structures.

Any other conversion of values requires explicit conversion using a conversion constructor. A constructor, as in other languages like C++, is a function with the same name as a type, which returns a value of that type. For example,

float f = 10.0;
int   ten = int(f);

uses an int conversion constructor to do the conversion. Likewise, the other types also have conversion constructors: float, double, uint, bool, and vectors and matrices of these types. Each accepts multiple other types to explicitly convert from. These functions also illustrate another feature of GLSL: function overloading, whereby each function takes various input types, but all use the same base function name. We will discuss more on functions in a bit.

Aggregate Types

GLSL’s basic types can be combined to better match core OpenGL’s data values and to ease computational operations.

First, GLSL supports vectors of two, three, or four components for each of the basic types of bool, int, uint, float, and double. Also, matrices of float and double are available. Table 2.3 lists the valid vector and matrix types.

Table 2.3 GLSL Vector and Matrix Types

Base Type

2D Vec

3D Vec

4D Vec

Matrix Types

float

vec2

vec3

vec4

mat2 mat3 mat4
mat2x2 mat2x3 mat2x4
mat3x2 mat3x3 mat3x4
mat4x2 mat4x3 mat4x4

double

dvec2

dvec3

dvec4

dmat2 dmat3 dmat4
dmat2x2 dmat2x3 dmat2x4
dmat3x2 dmat3x3 dmat3x4
dmat4x2 dmat4x3 dmat4x4

int

ivec2

ivec3

ivec4

uint

uvec2

uvec3

uvec4

bool

bvec2

bvec3

bvec4

Matrix types that list both dimensions, such as mat4x3, use the first value to specify the number of columns, the second the number of rows.

Variables declared with these types can be initialized similar to their scalar counterparts:

vec3 velocity = vec3(0.0, 2.0, 3.0);

Converting between types is equally accessible:

ivec3 steps = ivec3(velocity);

Vector constructors can also be used to truncate or lengthen a vector. If a longer vector is passed into the constructor of a smaller vector, the vector is truncated to the appropriate length.

vec4 color;
vec3 RGB = vec3(color); // now RGB only has three elements

Scalar values can be promoted to vectors, but that’s the only way a vector constructor takes fewer components than its size indicates:

vec3 white = vec3(1.0);  // white = (1.0, 1.0, 1.0)
vec4 translucent = vec4(white, 0.5);

Matrices are constructed in the same manner and can be initialized to either a diagonal matrix or a fully populated matrix. In the case of diagonal matrices, a single value is passed into the constructor, and the diagonal elements of the matrix are set to that value, with all others being set to zero, as in

042equ01.jpg

Matrices can also be created by specifying the value of every element in the matrix in the constructor. Values can be specified by combinations of scalars and vectors as long as enough values are provided and each column is specified in the same manner. Additionally, matrices are specified in column-major order, meaning the values are used to populate columns before rows (which is the opposite of how C initializes two-dimensional arrays).

For example, we could initialize a 3 × 3 matrix in any of the following ways:

mat3 M = mat3(1.0, 2.0, 3.0,
              4.0, 5.0, 6.0,
              7.0, 8.0, 9.0);

vec3 column1 = vec3(1.0, 2.0, 3.0);
vec3 column2 = vec3(4.0, 5.0, 6.0);
vec3 column3 = vec3(7.0, 8.0, 9.0);

mat3 M = mat3(column1, column2, column3);

or even

vec2 column1 = vec2(1.0, 2.0);
vec2 column2 = vec2(4.0, 5.0);
vec2 column3 = vec2(7.0, 8.0);

mat3 M = mat3(column1, 3.0,
column2, 6.0,
column3, 9.0);

all yielding the same matrix,

042equ02.jpg

Accessing Elements in Vectors and Matrices

The individual elements of vectors and matrices can be accessed and assigned. Vectors support two types of element access: a named-component method and an array-like method. Matrices use a two-dimensional array-like method.

Components of a vector can be accessed by name, as in

float red = color.r;
float v_y = velocity.y;

or by using a zero-based index scheme. The following yield identical results to the previous listing:

float red = color[0];
float v_y = velocity[1];

In fact, as shown in Table 2.4, there are three sets of component names, all of which do the same thing. The multiple sets are useful for clarifying the operations that you’re doing.

Table 2.4 Vector Component Accessors

Component Accessors

Description

(x, y, z,w)

Components associated with positions

(r, g, b, a)

Components associated with colors

(s, t, p, q)

Components associated with texture coordinates

A common use for component-wise access to vectors is for swizzling components, as you might do with colors, perhaps for color space conversion. For example, you could do the following to specify a luminance value based on the red component of an input color:

vec3 luminance = color.rrr;

Likewise, if you needed to move components around in a vector, you might do

color = color.abgr; // reverse the components of a color

The only restriction is that only one set of components can be used with a variable in one statement. That is, you can’t do

vec4 color = otherColor.rgz; // Error: 'z' is from a different group

Also, a compile-time error will be raised if you attempt to access an element that’s outside the range of the type. For example,

vec2 pos;
float zPos = pos.z; // Error: no 'z' component in 2D vectors

Matrix elements can be accessed using the array notation. Either a single scalar value or an array of elements can be accessed from a matrix:

mat4 m = mat4(2.0);
vec4 zVec = m[2];       // get column 2 of the matrix
float yScale = m[1][1]; // or m[1].y works as well

Structures

You can also logically group collections of different types into a structure. Structures are convenient for passing groups of associated data into functions. When a structure is defined, it automatically creates a new type and implicitly defines a constructor function that takes the types of the elements of the structure as parameters.

struct Particle {
    float lifetime;
    vec3 position;
    vec3 velocity;
};

Particle p = Particle(10.0, pos, vel); // pos, vel are vec3s

Likewise, to reference elements of a structure, use the familiar “dot” notation as you would in C.

Arrays

GLSL also supports arrays of any type, including structures. As with C, arrays are indexed using brackets ([ ]). The range of elements in an array of size n is 0 ... n – 1. Unlike in C, however, neither negative array indices nor positive indices out of range are permitted. As of GLSL 4.3, arrays can be made out of arrays, providing a way to handle multidimensional data. However, GLSL 4.2 and earlier versions do not allow arrays of arrays to be created (that is, you cannot create a multidimensional array).

Arrays can be declared sized or unsized. You might use an unsized array as a forward declaration of an array variable and later redeclare it to the appropriate size. Array declarations use the bracket notation, as in

float     coeff[3]; // an array of 3 floats
float[3]  coeff; // same thing
int       indices[]; // unsized. Redeclare later with a size

Arrays are first-class types in GLSL, meaning they have constructors and can be used as function parameters and return types. To statically initialize an array of values, you would use a constructor in the following manner:

float coeff[3] = float[3](2.38, 3.14, 42.0);

The dimension value on the constructor is optional.

Additionally, similar to Java, GLSL arrays have an implicit method for reporting their number of elements: the length() method. If you would like to operate on all the values in an array, here is an example using the length() method:

for (int i = 0; i < coeff.length(); ++i) {
    coeff[i] *= 2.0;
}

The length() method also works on vectors and matrices. A vector’s length is the number of components it contains, while a matrix’s length is the number of columns it contains. This is exactly what you need when using array syntax for indexing vectors and matrices. (m[2] is the third column of a matrix m.)

mat3x4 m;
int c = m.length();    // number of columns in m: 3
int r = m[0].length(); // number of components in column vector 0: 4

When the length is known at compile time, the length() method will return a compile-time constant that can be used where compile-time constants are required. For example:

mat4 m;
float diagonal[m.length()];  // array of size matching the matrix size
float x[gl_in.length()];     // array of size matching the number of
                             // geometry shader input vertices

For all vectors and matrices, and most arrays, length() is known at compile time. However, for some arrays, length() is not known until link time. This happens when relying on the linker to deduce the size from multiple shaders in the same stage. For shader storage buffer objects (declared with buffer, as described shortly), length() might not be known until render time. If you want a compile-time constant returned from length(), just make sure you establish the array size in your shader before using the length() method.

Multidimensional arrays are really arrays made from arrays and have a syntax similar to C:

float coeff[3][5];         // an array of size 3 of arrays of size 5
coeff[2][1] *= 2.0;        // inner-dimension index is 1, outer is 2
coeff.length();            // this returns the constant 3
coeff[2];                  // a one-dimensional array of size 5
coeff[2].length();         // this returns the constant 5

Multidimensional arrays can be formed in this way for virtually any type and resource. When shared with the application, the innermost (rightmost) dimension changes the fastest in the memory layout.

Storage Qualifiers

Types can also have modifiers that affect their behavior. There are several modifiers defined in GLSL, as shown in Table 2.5, with the behaviors they exhibit when used at global scope.

Table 2.5 GLSL Type Modifiers

Type Modifier

Description

const

Labels a variable as read-only. It will also be a compile-time constant if its initializer is a compile-time constant.

in

Specifies that the variable is an input to the shader stage.

out

Specifies that the variable is an output from a shader stage.

uniform

Specifies that the value is passed to the shader from the application and is constant across a given primitive.

buffer

Specifies read-write memory shared with the application. This memory is also referred to as a shader storage buffer.

shared

Specifies that the variables are shared within a local work group. This is used only in compute shaders.

const Storage Qualifier

Just as with C, const type modifier indicates that the variable is read-only. For example, the statement

const float Pi = 3.141529;

sets the variable Pi to an approximation of π. With the addition of the const modifier, it becomes an error to write to a variable after its declaration, so const variables must be initialized when declared.

in Storage Qualifier

The in modifier is used to qualify inputs into a shader stage. Those inputs may be vertex attributes (for vertex shaders) or output variables from the preceding shader stage.

Fragment shaders can further qualify their input values using some additional keywords that we discuss in Chapter 4, “Color, Pixels, and Fragments.”

out Storage Qualifier

The out modifier is used to qualify outputs from a shader stage. For example, the transformed homogeneous coordinates from a vertex shader or the final fragment color from a fragment shader.

uniform Storage Qualifier

The uniform modifier specifies that a variable’s value will be specified by the application before the shader’s execution and does not change across the primitive being processed. Uniform variables are shared among all the shader stages enabled in a program and must be declared as global variables. Any type of variable, including structures and arrays, can be specified as uniform. A shader cannot write to a uniform variable and change its value.

For example, you might want to use a color for shading a primitive. You might declare a uniform variable to pass that information into your shaders. In the shaders, you would make the declaration

uniform vec4 BaseColor;

Within your shaders, you can reference BaseColor by name, but to set its value in your application, you need to do a little extra work. The GLSL compiler creates a table of all uniform variables when it links your shader program. To set BaseColor’s value from your application, you need to obtain the index of BaseColor in the table, which is done using the glGetUniformLocation() routine.

Once you have the associated index for the uniform variable, you can set the value of the uniform variable using the glUniform*() or glUniformMatrix*() routine. Example 2.2 demonstrates obtaining a uniform variable’s index and assigning values.

Example 2.2 Obtaining a Uniform Variable’s Index and Assigning Values

GLint   timeLoc; /* Uniform index for variable "time" in shader */
GLfloat timeValue; /* Application time */

timeLoc = glGetUniformLocation(program, "time");
glUniform1f(timeLoc, timeValue);

buffer Storage Qualifier

The recommended way to share a large buffer with the application is through use of a buffer variable. Buffer variables are much like uniform variables, except that they can be modified by the shader. Typically, you’d use buffer variables in a buffer block, and blocks in general are described later in this chapter.

The buffer modifier specifies that the subsequent block is a memory buffer shared between the shader and the application. This buffer is both readable and writable by the shader. The size of the buffer can be established after shader compilation and program linking.

shared Storage Qualifier

The shared modifier is used only in compute shaders to establish memory shared within a local work group. This is discussed in more detail in Chapter 12, “Compute Shaders.”

Statements

The real work in a shader is done by computing values and making decisions. In the same manner as C++, GLSL has a rich set of operators for constructing arithmetic operations for computing values and a standard set of logical constructs for controlling shader execution.

Arithmetic Operations

No text describing a language is complete without the mandatory table of operator precedence (see Table 2.6). The operators are ordered in decreasing precedence. In general, the types being operated on must be the same, and for vector and matrices, the operands must be of the same dimension. In the table, integer types include int and uint and vectors of them; floating-point types include float and double types and vectors and matrices of them; arithmetic types include all integer and floating-point types; and any additionally includes structures and arrays.

Table 2.6 GLSL Operators and Their Precedence

Precedence

Operators

Accepted Types

Description

1

( )

Grouping of operations

2

[ ]
f( )
. (period)
++ --

arrays, matrices, vectors
functions
structures
arithmetic

Array subscripting
Function calls and constructors
Structure field or method access
Post-increment and -decrement

3

++ --
+ -
˜
!

arithmetic
arithmetic
integer
bool

Pre-increment and -decrement
Unary explicit positive or negation
Unary bit-wise not
Unary logical not

4

* / %

arithmetic

Multiplicative operations

5

+ -

arithmetic

Additive operations

6

<< >>

integer

Bit-wise operations

7

< > <= >=

arithmetic

Relational operations

8

== !=

any

Equality operations

9

&

integer

Bit-wise and

10

integer

Bit-wise exclusive or

11

|

integer

Bit-wise inclusive or

12

&&

bool

Logical and operation

13

∧∧

bool

Logical exclusive-or operation

14

||

bool

Logical or operation

15

a ? b : c

bool ? any : any

Ternary selection operation (inline “if„ operation; if (a) then (b) else (c))

16

=
+= -=
*= /=
%= <<=
>>=
&= ∧= |=

any
arithmetic
arithmetic
integer

integer

Assignment
Arithmetic assignment




17

,(comma)

any

Sequence of operations

Overloaded Operators

Most operators in GLSL are overloaded, meaning that they operate on a varied set of types. Specifically, arithmetic operations (including pre- and post-increment and -decrement) for vectors and matrices are well defined in GLSL. For example, to multiply a vector and a matrix (recalling that the order of operands is important; matrix multiplication is noncommutative, for all you math heads), use the following operation:

vec3 v;
mat3 m;
vec3 result = v * m;

The normal restrictions apply, that the dimensionality of the matrix and the vector must match. Additionally, scalar multiplication with a vector or matrix will produce the expected result. One notable exception is that the multiplication of two vectors will result in component-wise multiplication of components; however, multiplying two matrices will result in normal matrix multiplication.

vec2 a, b, c;
mat2 m, u, v;
c = a * b; //      c = (a.x*b.x, a.y*b.y)
m = u * v; //      m = (u00*v00+u01*v10   u00*v01+u01*v11
           //           u01*v00+u11*v10   u10*v01+u11*v11)

Additional common vector operations (e.g., dot and cross products) are supported by function calls, as well as various per-component operations on vectors and matrices.

Control Flow

GLSL’s logical control structures are the popular if-else and switch statements. As with the C language, the else clause is optional, and multiple statements require a block.

if (truth) {
    // true clause
}
else {
    // false clause
}

Similar to the situation in C, switch statements are available (starting with GLSL 1.30) in their familiar form:

switch (int_value) {
    case n:
      // statements
      break;

    case m:
      // statements
      break;

    default:
      // statements
      break;
}

GLSL switch statements also support “fall-through” cases—case statements that do not end with break statements. Each case does require some statement to execute before the end of the switch (before the closing brace). Also, unlike in C++, no statements are allowed before the first case. If no case matches the switch and a default label is present, then it is executed.

Looping Constructs

GLSL supports the familiar C form of for, while, and do ... while loops.

The for loop permits the declaration of the loop iteration variable in the initialization clause of the for loop. The scope of iteration variables declared in this manner is only for the lifetime of the loop.

for (int i = 0; i < 10; ++i) {
    ...
}

while (n < 10) {
    ...
}

do {
    ...
} while (n < 10);

Control-Flow Statements

Additional control statements beyond conditionals and loops are available in GLSL. Table 2.7 describes available control-flow statements.

Table 2.7 GLSL Control-Flow Statements

Statement

Description

break

Terminates execution of the block of a loop and continues execution after the scope of that block.

continue

Terminates the current iteration of the enclosing block of a loop, resuming execution with the next iteration of the loop.

return [result]

Returns from the current function, optionally providing a value to be returned from the function (assuming return value matches the return type of the enclosing function).

discard

Discards the current fragment and ceases shader execution. Discard statements are valid only in fragment shader programs.

The discard statement is available only in fragment programs. The execution of the fragment shader may be terminated at the execution of the discard statement, but this is implementation-dependent.

Functions

Functions permit you to replace occurrences of common code with a function call. This, of course, allows for smaller code and fewer chances for errors. GLSL defines a number of built-in functions, which are listed in Appendix C, as well as support for user-defined functions. User-defined functions can be defined in a single shader object and reused in multiple shader programs.

Declarations

Function declaration syntax is very similar to C, with the exception of the access modifiers on variables:

returnType functionName([accessModifier] type1 variable1,
                        [accessModifier] type2 variable2,
                          ...)
{
    // function body
    return returnValue; // unless returnType is void
}

Function names can be any combination of letters, numbers, and the underscore character, with the exception that it can neither begin with a digit nor with gl_ nor contain consecutive underscores.

Return types can be any built-in GLSL type or user-defined structure or array type. Arrays as return values must explicitly specify their size. If a function doesn’t return a value, its return type is void.

Parameters to functions can also be of any type, including arrays (which must specify their size).

Functions must be either declared with a prototype or defined with a body before they are called. Just as in C++, the compiler must have seen the function’s declaration before its use, or an error will be raised. If a function is used in a shader object other than the one where it’s defined, a prototype must be declared. A prototype is merely the function’s signature without its accompanying body. Here’s a simple example:

float HornerEvalPolynomial(float coeff[10], float x);

Parameter Qualifiers

While functions in GLSL are able to modify and return values after their execution, there’s no concept of a pointer or reference, as in C or C++. Rather, parameters of functions have associated parameter qualifiers indicating whether the value should be copied into, or out of, a function after execution. Table 2.8 describes the available parameter qualifiers in GLSL.

Table 2.8 GLSL Function Parameter Access Modifiers

Access Modifier

Description

in

Value copied into a function (default if not specified)

const in

Read-only value copied into a function

out

Value copied out of a function (undefined upon entrance into the function)

inout

Value copied into and out of a function

The in keyword is optional. If a variable does not include an access modifier, an in modifier is implicitly added to the parameter’s declaration. However, if the variable’s value needs to be copied out of a function, it must either be tagged with an out (for copy out-only variables) or an inout (for a variable both copied in and copied out) modifier. Writing to a variable not tagged with one of these modifiers will generate a compile-time error.

Additionally, to verify at compile time that a function doesn’t modify an input-only variable, adding a const in modifier will cause the compiler to check that the variable is not written to in the function. If you don’t do this and do write to an input-only variable, the write only modifies the local copy in the function.

Computational Invariance

GLSL does not guarantee that two identical computations in different shaders will result in exactly the same value. The situation is no different than for computational applications executing on the CPU, where the choice of optimizations may result in tiny differences in results. These tiny errors may be an issue for multipass algorithms that expect positions to be computed exactly the same for each shader pass. GLSL has two methods for enforcing this type of invariance between shaders, using the invariant or precise keywords.

Both of these methods will cause computations done by the graphics device to create reproducibility (invariance) in results of the same expression. However, they do not help reproduce the same results between the host and the graphics device. Compile-time constant expressions are computed on the compiler’s host, and there is no guarantee that the host computes in exactly the same way as the graphics device. For example:

uniform float ten;         // application sets this to 10.0
const float f = sin(10.0); // computed on compiler host
float g = sin(ten);        // computed on graphics device

void main()
{
    if (f == g)            // f and g might be not equal
        ;
}

In this example, it would not matter if invariant or precise was used on any of the variables involved, as they affect only two computations done on the graphics device.

The invariant Qualifier

The invariant qualifier may be applied to any shader output variable. It will guarantee that if two shader invocations each set the output variable with the same expression and the same values for the variables in that expression, both will compute the same value.

The output variable declared as invariant may be a built-in variable or a user-defined one. For example:

invariant gl_Position;
invariant centroid out vec3 Color;

As you may recall, output variables are used to pass data from one stage to the next. The invariant keyword may be applied at any time before use of the variable in the shader and may be used to modify built-in variables. This is done by declaring the variable only with invariant, as was shown earlier for gl_Position.

For debugging, it may be useful to impose invariance on all varying variables in shader. This can be accomplished by using the vertex shader preprocessor pragma.

#pragma STDGL invariant(all)

Global invariance in this manner is useful for debugging; however, it may likely have an impact on the shader’s performance. Guaranteeing invariance usually disables optimizations that may have been performed by the GLSL compiler.

The precise Qualifier

The precise qualifier may be applied to any computed variable or function return value. Despite its name, its purpose is not to increase precision, but to increase reproducibility of a computation. It is mostly used in tessellation shaders to avoid forming cracks in your geometry. Tessellation shading in general is described in Chapter 9, “Tessellation Shaders,” and there is additional discussion in that chapter about a use case for precise qualification.

Generally, you use precise instead of invariant when you need to get the same result from an expression, even if values feeding the expression are permuted in a way that should not mathematically affect the result. For example, the following expression should get the same result if the values for a and b are exchanged. It should also get the same result if the values for c and d and exchanged, or if both a and c are exchanged and b and d are exchanged, and so on.

Location = a * b + c * d;

The precise qualifier may be applied to a built-in variable, user variable, or function return value.

precise gl_Position;
precise out vec3 Location;
precise vec3 subdivide(vec3 P1, vec3 P2) { ... }

The precise keyword may be applied at any time before use of the variable in the shader and may be used to modify previously declared variables.

One practical impact in a compiler of using precise is an expression like the one above cannot be evaluated using two different methods of multiplication for the two multiply operations—for example, a multiply instruction for the first multiply and a fused multiply-and-add instruction for the second multiply. This is because these two instructions will get slightly different results for the same values. Because that was disallowed by precise, the compiler is prevented from doing this. Because use of fused multiply-and-add instructions is important to performance, it would be unfortunate to completely disallow them. So there is a built-in function in GLSL, fma(), that you can use to explicitly say this is okay.

precise out float result;
  ...
float f = c * d;
float result = fma(a, b, f);

Of course, you do that only if you weren’t going to have the values of a and c permuted, as you would be defeating the purpose of using precise.

Shader Preprocessor

The first step in compilation of a GLSL shader is parsing by the preprocessor. Similar to the C preprocessor, there are a number of directives for creating conditional compilation blocks and defining values. However, unlike in the C preprocessor, there is no file inclusion (#include).

Preprocessor Directives

Table 2.9 lists the preprocessor directives accepted by the GLSL preprocessor and their functions.

Table 2.9 GLSL Preprocessor Directives

Preprocessor Directive

Description

#define

Control the definition of constants and

#undef

macros similar to the C preprocessor.

#if

Conditional code management similar

#ifdef

to the C preprocessor, including the defined operator.

#ifndef

#else

Conditional expressions evaluate integer

#elif

expressions and defined values

#endif

(as specified by #define) only.

#error text

Cause the compiler to insert text (up to the first newline character) into the shader information log.

#pragma options

Control compiler specific options.

#extension options

Specify compiler operation with respect to specified GLSL extensions.

#version number

Mandate a specific version of GLSL version support.

#line options

Control diagnostic line numbering.

Macro Definition

The GLSL preprocessor allows macro definition in much the same manner as the C preprocessor, with the exception of the string substitution and concatenation facilities. Macros might define a single value, as in

#define NUM_ELEMENTS 10

or with parameters like

#define LPos(n) gl_LightSource[(n)].position

Additionally, there are several predefined macros for aiding in diagnostic messages (that you might issue with the #error directive, for example), as shown in Table 2.10.

Table 2.10 GLSL Preprocessor Predefined Macros

Macro Name

Description

_LINE_

Line number defined by one more than the number of newline characters processed and modified by the #line directive

_FILE_

Source string number currently being processed

_VERSION_

Integer representation of the OpenGL Shading Language version

Likewise, macros (excluding those defined by GLSL) may be undefined by using the #undef directive. For example,

#undef LPos

Preprocessor Conditionals

Identical to the processing by the C preprocessor, the GLSL preprocessor provides conditional code inclusion based on macro definition and integer constant evaluation.

Macro definition may be determined in two ways. Use the #ifdef directive:

#ifdef NUM_ELEMENTS
  ...
#endif

Or use the defined operator with the #if or #elif directives:

#if defined(NUM_ELEMENTS) && NUM_ELEMENTS > 3
  ...
#elif NUM_ELEMENTS < 7
  ...
#endif

Compiler Control

The #pragma directive provides the compiler additional information regarding how you would like your shaders compiled.

Optimization Compiler Option

The optimize option instructs the compiler to enable or disable optimization of the shader from the point where the directive resides forward in the shader source. You can enable or disable optimization by issuing either

#pragma optimize(on)

or

#pragma optimize(off)

respectively. These options may be issued only outside of a function definition. By default, optimization is enabled for all shaders.

Debug Compiler Option

The debug option enables or disables additional diagnostic output of the shader. You can enable or disable debugging by issuing either

#pragma debug(on)

or

#pragma debug(off)

respectively. As with the optimize option, these options may be issued only outside of a function definition, and by default, debugging is disabled for all shaders.

Global Shader-Compilation Option

One final #pragma directive that is available is STDGL. This option is currently used to enable invariance in the output of varying values.

Extension Processing in Shaders

GLSL, like OpenGL itself, may be enhanced by extensions. As vendors may include extensions specific to their OpenGL implementation, it’s useful to have some control over shader compilation in light of possible extensions that a shader may use.

The GLSL preprocessor uses the #extension directive to provide instructions to the shader compiler regarding how extension availability should be handled during compilation. For any or all extensions, you can specify how you would like the compiler to proceed with compilation:

#extension extension_name : <directive>

for a single extension, or

#extension all : <directive>

which affects the behavior of all extensions.

The options available are shown in Table 2.11

Table 2.11 GLSL Extension Directive Modifiers

Directive

Description

require

Flag an error if the extension is not supported or if the all-extension specification is used.

enable

Give a warning if the particular extensions specified are not supported, or flag an error if the all-extension specification is used.

warn

Give a warning if the particular extensions specified are not supported, or give a warning if any extension use is detected during compilation.

disable

Disable support for the particular extensions listed (that is, have the compiler act as if the extension is not supported even if it is) or all extensions if all is present, issuing warnings and errors as if the extension were not present.

InformIT Promotional Mailings & Special Offers

I would like to receive exclusive offers and hear about products from InformIT and its family of brands. I can unsubscribe at any time.

Overview


Pearson Education, Inc., 221 River Street, Hoboken, New Jersey 07030, (Pearson) presents this site to provide information about products and services that can be purchased through this site.

This privacy notice provides an overview of our commitment to privacy and describes how we collect, protect, use and share personal information collected through this site. Please note that other Pearson websites and online products and services have their own separate privacy policies.

Collection and Use of Information


To conduct business and deliver products and services, Pearson collects and uses personal information in several ways in connection with this site, including:

Questions and Inquiries

For inquiries and questions, we collect the inquiry or question, together with name, contact details (email address, phone number and mailing address) and any other additional information voluntarily submitted to us through a Contact Us form or an email. We use this information to address the inquiry and respond to the question.

Online Store

For orders and purchases placed through our online store on this site, we collect order details, name, institution name and address (if applicable), email address, phone number, shipping and billing addresses, credit/debit card information, shipping options and any instructions. We use this information to complete transactions, fulfill orders, communicate with individuals placing orders or visiting the online store, and for related purposes.

Surveys

Pearson may offer opportunities to provide feedback or participate in surveys, including surveys evaluating Pearson products, services or sites. Participation is voluntary. Pearson collects information requested in the survey questions and uses the information to evaluate, support, maintain and improve products, services or sites, develop new products and services, conduct educational research and for other purposes specified in the survey.

Contests and Drawings

Occasionally, we may sponsor a contest or drawing. Participation is optional. Pearson collects name, contact information and other information specified on the entry form for the contest or drawing to conduct the contest or drawing. Pearson may collect additional personal information from the winners of a contest or drawing in order to award the prize and for tax reporting purposes, as required by law.

Newsletters

If you have elected to receive email newsletters or promotional mailings and special offers but want to unsubscribe, simply email information@informit.com.

Service Announcements

On rare occasions it is necessary to send out a strictly service related announcement. For instance, if our service is temporarily suspended for maintenance we might send users an email. Generally, users may not opt-out of these communications, though they can deactivate their account information. However, these communications are not promotional in nature.

Customer Service

We communicate with users on a regular basis to provide requested services and in regard to issues relating to their account we reply via email or phone in accordance with the users' wishes when a user submits their information through our Contact Us form.

Other Collection and Use of Information


Application and System Logs

Pearson automatically collects log data to help ensure the delivery, availability and security of this site. Log data may include technical information about how a user or visitor connected to this site, such as browser type, type of computer/device, operating system, internet service provider and IP address. We use this information for support purposes and to monitor the health of the site, identify problems, improve service, detect unauthorized access and fraudulent activity, prevent and respond to security incidents and appropriately scale computing resources.

Web Analytics

Pearson may use third party web trend analytical services, including Google Analytics, to collect visitor information, such as IP addresses, browser types, referring pages, pages visited and time spent on a particular site. While these analytical services collect and report information on an anonymous basis, they may use cookies to gather web trend information. The information gathered may enable Pearson (but not the third party web trend services) to link information with application and system log data. Pearson uses this information for system administration and to identify problems, improve service, detect unauthorized access and fraudulent activity, prevent and respond to security incidents, appropriately scale computing resources and otherwise support and deliver this site and its services.

Cookies and Related Technologies

This site uses cookies and similar technologies to personalize content, measure traffic patterns, control security, track use and access of information on this site, and provide interest-based messages and advertising. Users can manage and block the use of cookies through their browser. Disabling or blocking certain cookies may limit the functionality of this site.

Do Not Track

This site currently does not respond to Do Not Track signals.

Security


Pearson uses appropriate physical, administrative and technical security measures to protect personal information from unauthorized access, use and disclosure.

Children


This site is not directed to children under the age of 13.

Marketing


Pearson may send or direct marketing communications to users, provided that

  • Pearson will not use personal information collected or processed as a K-12 school service provider for the purpose of directed or targeted advertising.
  • Such marketing is consistent with applicable law and Pearson's legal obligations.
  • Pearson will not knowingly direct or send marketing communications to an individual who has expressed a preference not to receive marketing.
  • Where required by applicable law, express or implied consent to marketing exists and has not been withdrawn.

Pearson may provide personal information to a third party service provider on a restricted basis to provide marketing solely on behalf of Pearson or an affiliate or customer for whom Pearson is a service provider. Marketing preferences may be changed at any time.

Correcting/Updating Personal Information


If a user's personally identifiable information changes (such as your postal address or email address), we provide a way to correct or update that user's personal data provided to us. This can be done on the Account page. If a user no longer desires our service and desires to delete his or her account, please contact us at customer-service@informit.com and we will process the deletion of a user's account.

Choice/Opt-out


Users can always make an informed choice as to whether they should proceed with certain services offered by InformIT. If you choose to remove yourself from our mailing list(s) simply visit the following page and uncheck any communication you no longer want to receive: www.informit.com/u.aspx.

Sale of Personal Information


Pearson does not rent or sell personal information in exchange for any payment of money.

While Pearson does not sell personal information, as defined in Nevada law, Nevada residents may email a request for no sale of their personal information to NevadaDesignatedRequest@pearson.com.

Supplemental Privacy Statement for California Residents


California residents should read our Supplemental privacy statement for California residents in conjunction with this Privacy Notice. The Supplemental privacy statement for California residents explains Pearson's commitment to comply with California law and applies to personal information of California residents collected in connection with this site and the Services.

Sharing and Disclosure


Pearson may disclose personal information, as follows:

  • As required by law.
  • With the consent of the individual (or their parent, if the individual is a minor)
  • In response to a subpoena, court order or legal process, to the extent permitted or required by law
  • To protect the security and safety of individuals, data, assets and systems, consistent with applicable law
  • In connection the sale, joint venture or other transfer of some or all of its company or assets, subject to the provisions of this Privacy Notice
  • To investigate or address actual or suspected fraud or other illegal activities
  • To exercise its legal rights, including enforcement of the Terms of Use for this site or another contract
  • To affiliated Pearson companies and other companies and organizations who perform work for Pearson and are obligated to protect the privacy of personal information consistent with this Privacy Notice
  • To a school, organization, company or government agency, where Pearson collects or processes the personal information in a school setting or on behalf of such organization, company or government agency.

Links


This web site contains links to other sites. Please be aware that we are not responsible for the privacy practices of such other sites. We encourage our users to be aware when they leave our site and to read the privacy statements of each and every web site that collects Personal Information. This privacy statement applies solely to information collected by this web site.

Requests and Contact


Please contact us about this Privacy Notice or if you have any requests or questions relating to the privacy of your personal information.

Changes to this Privacy Notice


We may revise this Privacy Notice through an updated posting. We will identify the effective date of the revision in the posting. Often, updates are made to provide greater clarity or to comply with changes in regulatory requirements. If the updates involve material changes to the collection, protection, use or disclosure of Personal Information, Pearson will provide notice of the change through a conspicuous notice on this site or other appropriate way. Continued use of the site after the effective date of a posted revision evidences acceptance. Please contact us if you have questions or concerns about the Privacy Notice or any objection to any revisions.

Last Update: November 17, 2020