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, but reasonably complete, 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.

The version of GLSL for WebGL 1.0 is GLSL ES 1.0. However, the specification for GLSL ES 1.0
lists a number of language features as being optional. The WebGL specification
mandates that the optional features in GLSL ES 1.0 are **not** supported in WebGL.
These unsupported features include some that you would probably consider
pretty basic, such as *while* loops and certain kinds of array indexing.
The justification for having optional features in GLSL ES is that GPUs vary
in the set of features that can be efficiently implemented, and GPUs for embedded
systems can be especially limited. The justification for eliminating those optional
features in WebGL is presumably that WebGL programs are used on Web pages that can
be accessed by any device, so they should work on the full range of devices.

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. 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*, and*v.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. 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 *sampler2D* and *samplerCube*, 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.

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];

There are no array constructors, and it is not possible to initialize an array as part of its declaration.

Array indexing uses the usual syntax, such as *A*[0]
or *palette*[*i*+1] or *lights*[3].*color*.
However, 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.

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 other possible storage qualifier is *const*, which means that the value
of the variable cannot be changed after it has been initialized. In addition,
it is not legal to assign a value to an attribute or uniform variable; their
values come from the JavaScript side, and they are considered to be read-only.
There are implementation-dependent limits on the numbers of attribute, uniform,
and varying variables that can be used in a shader program; this is discussed
in the last subsection of this section.

The *attribute* qualifier can only be used for global variables in the
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. Matrix attributes would be very rare, and I won't go into any
detail about them here.)

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 ] );

The second parameter **must** be *false*. (In some other versions of OpenGL,
the second parameter can be set to *true* to indicate that the values are in row-major
instead of column-major order, but WebGL requires 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.)

As for the *varying* qualifier, it can be used only for the built-in floating
point types (*float, vec2, vec3, vec4,
mat2, mat3, and mat4*) and for arrays of those types.
A varying variable should be declared in both the vertex and fragment shader. (This is not
actually a requirement; an error only occurs if the fragment shader tries to use the
value of a varying variable that does not exist in the vertex shader.)
A variable must have the same type in both shaders. The variable is
read-only in the fragment shader.
The vertex shader should write a value to the varying variable, and it can also read its
value.

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, *lowp* integers must be able to represent values
in at least the range −2^{8} to 2^{8}; *mediump* integers, in the
range −2^{10} to 2^{10}; and *highp* integers, in the range
−2^{16} to 2^{16}. It is possible—and on desktop computers
it is likely—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. 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 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 last qualifier, invariant,
is even more difficult to explain, and it has only a very 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.

Expressions in GLSL can use the arithmetic operators +, −, *,
/, ++ and −− (but %, <<,
and >> are not supported). They are defined for the types int
and float. 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 ints and floats, 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.

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*.
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.

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 back 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.

The only control structures in GLSL 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.

*If* statements are supported with the full syntax from C, including
*else* and *else if*.

In a *for* loop, 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)

*For* loops can include *break* and *continue* statements.

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 will tend to be lower on mobile devices such as tablets and phones. Although the limits can vary, WebGL imposes a set of minimum requirements that all implementations must satisfy.

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 texture images 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.

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. 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.