[ Previous Section | Next Section | Chapter Index | Main Index ]

Section 6.3

GLSL


You have seen a few short, simple examples of shader programs written in GLSL. In fact, shader programs are often fairly short, but they are not always so simple. To understand the more complex shaders that we will be using in the rest of this book, you will need to know more about GLSL. This section aims to give a short introduction to the major features of the language. This is a rather technical section. You should read it to get some familiarity with GLSL, and then use it as a reference when needed.

For WebGL 1.0, shaders must be written in version 1.00 of GLSL ES. WebGL 2.0 can use either version 1.00 or version 3.00, but some features of WebGL 2.0 are only available when shaders are written in GLSL ES 3.00. Although the two versions of GLSL are very similar, there are major differences and incompatibilities. Unless otherwise noted, the discussion here applies to both versions.

The vertex shader and the fragment shader in a shader program must be written using the same version of GLSL. A GLSL ES 3.00 shader program must begin with the line

#version 300 es

This must be the very first line of the shader source code. It cannot even be preceded by blank lines or comments. A shader program that does not start with this line is a version 1.00 shader. A version 1.00 shader does not include a declaration of the version number.


6.3.1  Basic Types

Variables in GLSL must be declared before they are used. GLSL is a strictly typed language, and every variable is given a type when it is declared.

GLSL has built-in types to represent scalars (that is, single values), vectors, and matrices. The scalar types are float, int, and bool. Version 3.00 adds an unsigned integer type, uint. A GPU might not support integers or booleans on the hardware level, so it is possible that the int and bool types are actually represented as floating point values.

The types vec2, vec3, and vec4 represent vectors of two, three, and four floats. There are also types to represent vectors of ints (ivec2, ivec3, and ivec4) and bools (bvec2, bvec3, and bvec4). GLSL has very flexible notation for referring to the components of a vector. One way to access them is with array notation. For example, if v is a four-component vector, then its components can be accessed as v[0], v[1], v[2], and v[3]. But they can also be accessed using the dot notation as v.x, v.y, v.z, andv.w. The component names x, y, z, and w are appropriate for a vector that holds coordinates. However, vectors can also be used to represent colors, and the components of v can alternatively be referred to as v.r, v.g, v.b, and v.a. Finally, they can be referred to as v.s, v.t, v.p, and v.q — names appropriate for texture coordinates.

Furthermore, GLSL allows you to use multiple component names after the dot, as in v.rgb or v.zx or even v.yyy. The names can be in any order, and repetition is allowed. This is called swizzling, and v.zx is an example of a swizzler. The notation v.zx can be used in an expression as a two-component vector. For example, if v is vec4(1.0,2.0,3.0,4.0), then v.zx is equivalent to vec2(3.0,1.0), and v.yyy is like vec3(2.0,2.0,2.0). Swizzlers can even be used on the left-hand side of an assignment, as long as they don't contain repeated components. For example,

vec4 coords = vec4(1.0, 2.0, 3.0, 4.0);
vec3 point = vec3(5.0, 6.0, 7.0);
coords.yzw = coords.wyz;  // Now, coords is (1.0, 4.0, 2.0, 3.0)
point.xy = coords.xx;     // Now, point is (1.0, 1.0, 7.0)

A notation such as vec2(1.0, 2.0) is referred to as a "constructor," although it is not a constructor in the sense of Java or C++, since GLSL is not object-oriented, and there is no new operator. A constructor in GLSL consists of a type name followed by a list of expressions in parentheses, and it represents a value of the type specified by the type name. Any type name can be used, including the scalar types. The value is constructed from the values of the expressions in parentheses. An expression can contribute more than one value to the constructed value; we have already seen this in examples such as

vec2 v = vec2( 1.0, 2.0 );
vec4 w = vec4( v, v );  // w is ( 1.0, 2.0, 1.0, 2.0 )

Note that the expressions can be swizzlers:

vec3 v = vec3( 1.0, 2.0, 3.0 );
vec3 w = vec3( v.zx, 4.0 );  // w is ( 3.0, 1.0, 4.0 )

Extra values from the last parameter will be dropped. This makes is possible to use a constructor to shorten a vector. However, it is not legal to have extra parameters that contribute no values at all to the result:

vec4 rgba = vec4( 0.1, 0.2, 0.3, 0.4 );
vec3 rgb = vec3( rgba );  // takes 3 items from rgba; rgb is (0.1, 0.2, 0.3)
float r = float( rgba );  // r is 0.1
vec2 v = vec2( rgb, rgba );    // ERROR: No values from rgba are used.

As a special case, when a vector is constructed from a single scalar value, all components of the vector will be set equal to that value:

vec4 black = vec4( 1.0 );  // black is ( 1.0, 1.0, 1.0, 1.0 )

When constructing one of the built-in types, type conversion will be applied if necessary. For purposes of conversion, the boolean values true/false convert to the numeric values zero and one; in the other direction, zero converts to false and any other numeric value converts to true. As far as I know, constructors are the only context in which GLSL does automatic type conversion. For example, you need to use a constructor to assign an int value to a float variable, and it is illegal to add an int to a float:

int k = 1;
float x = float(k);  //  "x = k" would be a type mismatch error
x = x + 1.0;         // OK
x = x + 1;           // ERROR: Can't add values of different types.

The built-in matrix types are mat2, mat3, and mat4. They represent, respectively, two-by-two, three-by-three, and four-by-four matrices of floating point numbers. (There are no matrices of integers or booleans, but there are some additional matrix types for representing non-square matrices.) The elements of a matrix can be accessed using array notation, such as M[2][1]. If a single index is used, as in M[2], the result is a vector. For example, if M is of type mat4, then M[2] is a vec4. Arrays in GLSL, as in OpenGL, use column-major order. This means that M[2] is column number 2 in M rather than row number 2 (as it would be in Java), and M[2][1] is the element in column 2 and row 1.

A matrix can be constructed from the appropriate number of values, which can be provided as scalars, vectors or matrices. For example, a mat3 can be constructed from nine float or from three vec3 parameters:

mat3 m1 = mat3( 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0 );
vec3 v = vec3( 1, 2, 3 );
mat3 m2 = mat3( v, v, v );

Keep in mind that the matrix is filled in column-major order; that is, the first three numbers go into column 0, the next three into column 1, and the last three into column 2.

As a special case, if a matrix M is constructed from a single scalar value, then that value is put into all the diagonal elements of M (M[0][0], M[1][1], and so on). The non-diagonal elements are all set equal to zero. For example, mat4(1.0) constructs the four-by-four identity matrix.

The only other built-in types are the so-called "sampler types", which are used for accessing textures. The sampler types can be used only in limited ways. They are not numeric types and cannot be converted to or from numeric types. The will be covered in the next section.


6.3.2  Data Structures

A GLSL program can define new types using the struct keyword. The syntax is the same as in C, with some limitations. A struct is made up of a sequence of named members, which can be of different types. The type of a member can be any of the built-in types, an array type, or a previously defined struct type. For example,

struct LightProperties {
    vec4 position;
    vec3 color;
    float intensity;
};

This defines a type named LightProperties. The type can be used to declare variables:

LightProperties light;

The members of the variable light are then referred to as light.position, light.color, and light.intensity. Struct types have constructors, but their constructors do not support type conversion: The constructor must contain a list of values whose types exactly match the types of the corresponding members in the struct. For example,

light = LightProperties( vec4(0.0, 0.0, 0.0, 1.0), vec3(1.0), 1.0 );

GLSL also supports arrays. Only one-dimensional arrays are allowed. The base type of an array can be any of the basic types or it can be a struct type. The size of the array must be specified in the variable declaration as an integer constant. For example

int A[10];
vec3 palette[8];
LightProperties lights[3];

In version 1.00, there are no array constructors, and it is not possible to initialize an array as part of its declaration. Version 3.00 does have array constructors, and it allows type names such as "int[10], representing an array of 10 integers:

int[4] B; // B is an array of 4 ints; GLSL ES 3.00 only!
B = int[4] (2, 3, 5, 7);  // Array constructor; GLSL ES 3.00 only!

Array indexing uses the usual syntax, such as A[0] or palette[i+1] or lights[3].color. In GLSL ES 1.00, there are some strong limitations on the expressions that can be used as array indices. With one exception, an expression that is used as the index for an array can contain only integer constants and for loop variables (that is, variables that are used as loop control variables in for loops). For example, the expression palette[i+1] would only be legal inside a for of the form for (int i = .... The single exception is that arbitrary index expressions can be used for arrays of uniforms in a vertex shader (and then only if the array does not contain samplers).

Just as in C, there is no check for array index out of bounds errors. It is up to the programmer to make sure that array indices are valid.


6.3.3  Qualifiers

Variable declarations can be modified by various qualifiers. You have seen examples of the qualifiers attribute, uniform, and varying. These are called storage qualifiers. The qualifiers attribute and varying do not exist in version 3.00; instead, an attribute is declared in the vertex shader using the storage qualifier in, and a varying variable is declared using out in the vertex shader and in in the fragment shader. The uniform qualifier is used in both versions. Only global variables, not local variables in function definition, can be attribute, uniform, or varying variables.

The attribute qualifier can only be used in a GLSL ES 1.00 vertex shader, and it only applies to the built-in floating point types float, vec2, vec3, vec4, mat2, mat3, and mat4. (Matrix attributes are not supported directly on the JavaScript side. A matrix attribute has to be treated as a set of vector attributes, one for each column. The attribute locations for the columns are successive integers, and the WebGL function gl.getAttribLocation will return the location for the first column. Matrix attributes would be rare, though perhaps useful for instanced drawing, and I won't go into further detail about them here.)

In GLSL ES 3.00, the in qualifier on a vertex shader variable defines it to be an attribute variable, and it can be applied to integer and unsigned integer scalars and vectors, as well as to the floating point types.

Also in GLSL ES 3.00, the out qualifier can be used on integer and floating point scalars and vectors in the fragment shader. In version 1.00, a fragment shader has the predefined variable gl_FragColor of type vec4 to specify the color of the pixel. In version 3.00, a fragment shader can have multiple outputs, and the outputs are not necessarily colors. Because the output type does not have to be vec4, it is not possible to have a predefined output variable. For now, we will only use one fragment shader output representing a color. So, a version 3.00 fragment shader will have one out variable of type vec4. (When we discuss framebuffers in Section 7.4, we will see how multiple outputs can be used.)

Both the vertex shader and the fragment shader can use uniform variables. The same variable can occur in both shaders, as long as the types in the two shaders are the same. Uniform variables can be of any type, including array and structure types. Now, JavaScript only has functions for setting uniform values that are scalar variables, vectors, or matrices. There are no functions for setting the values of structs or arrays. The solution to this problem requires treating every component of a struct or array as a separate uniform value. For example, consider the declarations

struct LightProperties {
    vec4 position;
    vec3 color;
    float intensity;
};
uniform LightProperties light[4];

The variable light contains twelve basic values, which are of type vec4, vec3, or float. To work with the light uniform in JavaScript, we need twelve variables to represent the locations of the 12 components of the uniform variable. When using gl.getUniformLocation to get the location of one of the 12 components, you need to give the full name of the component in the GLSL program. For example: gl.getUniformLocation(prog, "light[2].color"). It is natural to store the 12 locations in an array of JavaScript objects that parallels the structure of the array of structs on the GLSL side. Here is typical JavaScript code to create the structure and use it to initialize the uniform variables:

lightLocations = new Array(4);
for (i = 0; i < light.length; i++) {
    lightLocations[i] = {
        position: gl.getUniformLocation(prog, "light[" + i + "].position" );
        color: gl.getUniformLocation(prog, "light[" + i + "].color" );
        intensity: gl.getUniformLocation(prog, "light[" + i + "].intensity" );
    };
}

for (i = 0; i < light.length; i++) {
    gl.uniform4f( lightLocations[i].position, 0, 0, 0, 1 );
    gl.uniform3f( lightLocations[i].color, 1, 1, 1 );
    gl.uniforma1f( lightLocations[i].intensity, 0 );    
}

For uniform shader variables that are matrices, the JavaScript function that is used to set the value of the uniform is gl.uniformMatrix2fv for a mat2, gl.uniformMatrix3fv for a mat3, or gl.uniformMatrix4fv for a mat4. Even though the matrix is two-dimensional, the values are stored in a one dimensional array. The values are loaded into the array in column-major order. For example, if transform is a uniform mat3 in the shader, then JavaScript can set its value to be the identity matrix with

transformLoc = gl.getUniformLocation(prog, "transform");
gl.uniformMatrix3fv( transformLoc, false, [ 1,0,0, 0,1,0, 0,0,1 ] );

In Version 1.00, the second parameter must be false. In Version 3.00, the second parameter can be true to indicate that the entries of the matrix are provided in row-major rather than column-major order. Note that the 3 in uniformMatrix3fv refers to the number of rows and columns in the matrix, not to the length of the array, which must be 9. (By the way, it is OK to use a typed array rather than a normal JavaScript array for the value of a uniform.)

A varying variable should be declared with the same name and type in both the vertex shader and fragment shader. In version 1.00, the storage qualifier for declaring varying variables is varying, and it can only be used for for the built-in floating point types (float, vec2, vec3, vec4, mat2, mat3, and mat4) and for arrays of those types.

In version 3.00, a varying variable can also be an integer or unsigned integer scalar or vector. But there is a complication because it doesn't make sense to apply interpolation to integer values. So, a varying variable of integer type must be declared with the additional qualifier flat, which means it will not be interpolated. Instead, the value from the first vertex of a triangle or line segment will be used for every pixel. (Floating point varying variables can also, optionally, be declared as flat.) For example:

flat in ivec3 A;  // GLSL ES 3.00 fragment shader only!

Another possible storage qualifier is const, which means that the value of the variable cannot be changed after it has been initialized. The declaration of a const variable must include initialization.


Variable declarations can also be modified by precision qualifiers. The possible precision qualifiers are highp, mediump, and lowp. A precision qualifier sets the minimum range of possible values for an integer variable or the minimum range of values and number of decimal places for a floating point variable. GLSL doesn't assign a definite meaning to the precision qualifiers, but mandates some minimum requirements. For example, in version 1.00, lowp integers must be able to represent values in at least the range −28 to 28; mediump integers, in the range −210 to 210; and highp integers, in the range −216 to 216. For version 3.00, highp variables always use 32 bits, and the requirements for mediump and lowp are higher. It is even possible that all values are 32-bit values and the precision qualifiers have no real effect. But GPUs in embedded systems can be more limited.

A precision qualifier can be used on any variable declaration, including local variables and function parameters. If the variable also has a storage qualifier, the storage qualifier comes first. For example

lowp int n;
varying highp float v;
uniform mediump vec3 colors[3];

A varying variable can have different precisions in the vertex and in the fragment shader. The default precision for integers and floats in the vertex shader is highp. Fragment shaders are not required to support highp, although it is likely that they do so, except perhaps on older mobile hardware. In the fragment shader, the default precision for integers is mediump, but floats do not have a default precision. This means that every floating point variable in the fragment shader has to be explicitly assigned a precision. Alternatively, it is possible to set a default precision for floats with the statement

precision mediump float;

This statement was used at the start of each of the fragment shaders in the previous section. Of course, if the fragment shader does support highp, this restricts the precision unnecessarily. You can avoid that by using this code at the start of the fragment shader:

#ifdef GL_FRAGMENT_PRECISION_HIGH
    precision highp float;
#else
    precision mediump float;
#endif

This sets the default precision to highp if it is available and to mediump if not. The lines starting with "#" are preprocessor directives—an aspect of GLSL that I don't want to get into.


The next qualifier, invariant, is even more difficult to explain, and it has only a limited use. Invariance refers to the requirement that when the same expression is used to compute the value of the same variable (possibly in different shaders), then the value that is assigned to the variable should be exactly the same in both cases. This is not automatically the case. For example, the values can be different if a compiler uses different optimizations or evaluates the operands in a different order in the two expressions. The invariant qualifier on the variable will force the compiler to use exactly the same calculations for the two assignment statements. The qualifier can only be used on declarations of varying variables. It must be the first qualifier in the declaration. For example,

invariant varying mediump vec3 color;

It can also be used to make the predefined variables such as gl_Position and gl_FragCoord invariant, using a statement such as

invariant gl_Position;

Invariance can be important in a multi-pass algorithm that applies two or more shader programs in succession to compute an image. It is important, for example, that both shaders get the same answer when they compute gl_Position for the same vertex, using the same expression in both vertex shaders. Making gl_Position invariant in the shaders will ensure that.


The last type of qualifier, a "layout" qualifier, is only available in version 3.00. It can be used to specify the integer ID of an attribute variable, as an alternative to using the JavaScript function gl.getAttribLocation() to query the ID. An example would be

layout(location = 0) in vec3 a_coords; // GLSL ES 3.00 vertex shader only!

The same kind of layout qualifier can be used on an out variable in a GLSL ES 3.00 fragment shader that has multiple outputs. In that case, it specifies which of several output destinations should be used for that variable.


6.3.4  Expressions

Expressions in GLSL can use the arithmetic operators +, , *, /, ++ and −− for integer and floating point values. In version 3.00, the remainder operator, %, as well as left and right shift and bitwise logical operators, are also available for integer types. There is no automatic type conversion in expressions. If x is of type float, the expression x+1 is illegal. You have to say x+1.0 or x+float(1).

The arithmetic operators have been extended in various ways to work with vectors and matrices. If you use * to multiply a matrix and a vector, in either order, it multiplies them in the linear algebra sense, giving a vector as the result. The types of the operands must match in the obvious way; for example, a vec3 can only be multiplied by a mat3, and the result is a vec3. When used with two matrices of the same size, * does matrix multiplication.

If +, , *, or / is used on a vector and a scalar of the same basic type, then the operation is performed on each element of the vector. For example, vec2(3.0,3.0) / 2.0 is the vector vec2(1.5,1.5), and 2*ivec3(1,2,3) is the vector ivec3(2,4,6). When one of these operators is applied to two vectors of the same type, the operation is applied to each pair of components, and the result is a vector. For example, the value of

vec3( 1.0, 2.0, 3.0 ) + vec3( 4.2, -7.0, 1.7 )

is the vector vec3(5.2,-5.0,4.7). Note in particular that the usual vector arithmetic operations—addition and subtraction of vectors, multiplication of a vector by a scalar, and multiplication of a vector by a matrix—are written in the natural way is GLSL.

The relational operators <, >, <=, and >= can only be applied to integer and floating point scalars, and the types of the two operands must match exactly. However, the equality operators == and != have been extended to work on all of the built-in types except sampler types. Two vectors are equal only if the corresponding pairs of components are all equal. The same is true for matrices. The equality operators cannot be used with arrays, but they do work for structs, as long as the structs don't contain any arrays or samplers; again, every pair of members in two structs must be equal for the structs to be considered equal.

GLSL has logical operators !, &&, ||, and ^^ (the last one being an exclusive or operation). The operands must be of type bool.

Finally, there are the assignment operators =, +=, −=, *=, and /=, with the usual meanings.


GLSL also has a large number of predefined functions, more than I can discuss here. All of the functions that I will mention here require floating-point values as parameters, even if the function would also make sense for integer values.

Most interesting, perhaps, are functions for vector algebra. See Section 3.5 for the definitions of these operations. These functions have simple formulas, but they are provided as functions for convenience and because they might have efficient hardware implementations in a GPU. The function dot(x,y) computes the dot product x·y of two vectors of the same length. The return value is a float; cross(x,y) computes the cross product x×y, where the parameters and return value are of type vec3; length(x) is the length of the vector x and distance(x,y) gives the distance between two vectors; normalize(x) returns a unit vector that points in the same direction as x. There are also functions named reflect and refract that can be used to compute the direction of reflected and refracted light rays; I will cover them when I need to use them.

The function mix(x,y,t) computes x*(1−t) + y*t. If t is a float in the range 0.0 to 1.0, then the return value is a linear mixture, or weighted average, of x and y. This function might be used, for example, to do alpha-blending of two colors. The function clamp(x,low,high) clamps x to the range low to high; the return value could be computed as min(max(x,low),high). If rgb is a vector representing a color, we could ensure that all of the components of the vector lie in the range 0 to 1 with the command

rgb = clamp( rgb, 0.0, 1.0 );

If s and t are floats, with s < t, then smoothstep(s,t,x) returns 0.0 for x less than s and returns 1.0 for x greater than t. For values of x between s and t, the return value is smoothly interpolated from 0.0 to 1.0. Here is an example that might be used in a fragment shader for rendering a gl.POINTS primitive, with transparency enabled:

float dist = distance( gl_PointCoord, vec2(0.5) );
float alpha = 1.0 - smoothstep( 0.45, 0.5, dist );
if (alpha == 0.0) {
    discard; // discard fully transparent pixels
}
gl_FragColor = vec4( 1.0, 0.0, 0.0, alpha );

This would render the point as a red disk, with the color fading smoothly from opaque to transparent around the edge of the disk, as dist increases from 0.45 to 0.5. Note that for the functions mix, clamp, and smoothstep, the x and y parameters can be vectors as well as floats. In that case, they operate on each component of the vector individually.

The usual mathematical functions are available in GLSL, including sin, cos, tan, asin, acos, atan, log, exp, pow, sqrt, abs, floor, ceil, min, and max. (In version 3.00, abs, min, and max also apply to integer types.) For these functions, the parameters can be any of the types float, vec2, vec3, or vec4. The return value is of the same type, and the function is applied to each component separately. For example, the value of sqrt(vec3(16.0,9.0,4.0)) is the vector vec3(4.0,3.0,2.0). For min and max, there is also a second version of the function in which the first parameter is a vector and the second parameter is a float. For those versions, each component of the vector is compared to the float; for example, max(vec3(1.0,2.0,3.0),2.5) is vec3(2.5,2.5,3.0).

The function mod(x,y) computes the modulus, or remainder, when x is divided by y. The return value is computed as x − y*floor(x/y). As with min and max, x can be either a vector or a float. The mod function can be used as a substitute for the % operator, which is not supported in GLSL.

There are also a few functions for working with sampler variables that I will discuss in the next section.


6.3.5  Function Definitions

A GLSL program can define new functions, with a syntax similar to C. Unlike C, function names can be overloaded; that is, two functions can have the same name, as long as they have different numbers or types of parameters. A function must be declared before it is used. As in C, it can be declared by giving either a full definition or a function prototype.

Function parameters can be of any type. The return type for a function can be any type except for array types. A struct type can be a return type, as long as the structure does not include any arrays. When an array is used a formal parameter, the length of the array must be specified by an integer constant. For example,

float arraySum10( float A[10] ) {
   float sum = 0.0;
   for ( int i = 0; i < 10; i++ ) {
       sum += A[i];
   }
   return sum;
}

Function parameters can be modified by the qualifiers in, out, or inout. The default, if no qualifier is specified, is in. The qualifier indicates whether the parameter is used for input to the function, output from the function, or both. For input parameters, the value of the actual parameter in the function call is copied into the formal parameter in the function definition, and there is no further interaction between the formal and actual parameters. For output parameters, the value of the formal parameter is copied to the actual parameter when the function returns. For an inout parameter, the value is copied in both directions. This type of parameter passing is referred to as "call by value/return." Note that the actual parameter for an out or inout parameter must be something to which a value can be assigned, such as a variable or swizzler. (All parameters in C, Java, and JavaScript are input parameters, but passing a pointer as a parameter can have an effect similar to an inout parameter. GLSL, of course, has no pointers.) For example,

void cumulativeSum( in float A[10], out float B[10]) {
    B[0] = A[0];
    for ( int i = 1; i < 10; i++ ) {
        B[i] = B[i-1] + A[i];
    }
}

Recursion is not supported for functions in GLSL. This is a limitation of the type of processor that is typically found in GPUs. There is no way to implement a stack of activation records. Also, GLSL for WebGL does not support computations that can continue indefinitely.


6.3.6  Control Structures

The only control structures in GLSL ES 1.00 for WebGL are the if statement and a very restricted form of the for loop. There is no while or do..while loop, and there is no switch statement. However, all of these are supported in GLSL ES 3.00.

If statements are supported with the full syntax from C, including else and else if. In version 3.00, the syntax for all control structures is pretty much the same as in C.

In a for loop in a version 1.00 shader, the loop control variable must be declared in the loop, and it must be of type int or float. The initial value for the loop control variable must be a constant expression (that is, it can include operators, but all the operands must be literal constants or const variables) The code inside the loop is not allowed to change the value of the loop control variable. The test for ending the loop can only have the form var op expression, where var is the loop control variable, the op is one of the relational or equality operators, and the expression is a constant expression. Finally, the update expression must have one of the forms var++, var--, var+=expression, or var-=expression, where var is the loop control variable, and expression is a constant expression. Of course, this is the most typical form for for loops in other languages. Some examples of legal first lines for for loops:

for (int i = 0; i < 10; i++)

for (float x = 1.0; x < 2.0; x += 0.1)

for (int k = 10; k != 0; k -= 1)

In version 3.00, these restrictions do not apply. Note that all loops can include break and continue statements.

For loops can include break and continue statements.


6.3.7  Limits

WebGL puts limits on certain resources that are used by WebGL and its GLSL programs, such as the number of attribute variables or the size of a texture image. The limits are due in many cases to hardware limits in the GPU, and they depend on the device on which the program is running, and on the implementation of WebGL on that device. The hardware limits can be lower on mobile devices such as tablets and phones, but modern tablets and phones have pretty impressive GPUs. Although the limits can vary, WebGL imposes a set of minimum requirements that all implementations must satisfy. I will give the minimums for WebGL 1.0. The minimums for WebGL 2.0 are greater.

For example, any WebGL implementation must allow at least 8 attributes in a vertex shader. The actual limit for a particular implementation might be more, but cannot be less. The actual limit is available in a GLSL program as the value of a predefined constant, gl_MaxVertexAttribs. More conveniently, it is available on the JavaScript side as the value of the expression

gl.getParameter( gl.MAX_VERTEX_ATTRIBS )

Attribute variables of type float, vec2, vec3, and vec4 all count as one attribute against the limit. For a matrix-valued attribute, each column counts as a separate attribute as far as the limit goes.

Similarly, there are limits on varying variables, and there are separate limits on uniform variables in the vertex and fragment shaders. (The limits are on the number of four-component "vectors." There can be some packing of separate variables into a single vector, but the packing that is used does not have to be optimal. No packing is done for attribute variables.) The limits must satisfy

gl_MaxVertexAttribs >= 8;
gl_MaxVertexUniformVectors >= 128;
gl_MaxFragmentUniformVectors >= 16;
gl_MaxVaryingVectors >= 8;

There are also limits in GLSL on the number of texture units, which means essentially the number of textures that can be used simultaneously. These limits must satisfy

gl_MaxTextureImageUnits >= 8;         // limit for fragment shader
gl_MaxVertexTextureImageUnits >= 0;   // limit for vertex shader
gl_MaxCombinedTextureImageUnits >= 8; // total limit for both shaders

Textures are usually used in fragment shaders, but they can sometimes be useful in vertex shaders. Note however, that gl_MaxVertexTextureImageUnits can be zero, which means that implementations are not required to allow texture units to be used in vertex shaders. (This possibility is for WebGL 1.0 only.)

There are also limits on other things, including viewport size, texture image size, line width for line primitives, and point size for the POINTS primitive. All of the limits can be queried from the JavaScript side using gl.getParameter().

The following demo shows the actual values of the resource limits on the device on which you are viewing this page. The demo shows the limits for a WebGL 1.0 graphics context. You can use it to check the capabilities of various devices on which you want your WebGL programs to run. In general, the actual limits will be significantly larger than the required minimum values.


[ Previous Section | Next Section | Chapter Index | Main Index ]