2.4. Language Overview

Because of its success as a standard, OpenGL has been the target of our efforts to define an industry-standard, high-level shading language. The shading language that has been defined as a result of the efforts of OpenGL ARB members is called the OpenGL Shading Language. This language has been designed to be forward looking and to eventually support programmability in other areas as well.

This section provides a brief overview of the OpenGL Shading Language. For a complete discussion of the language, see Chapter 3, Chapter 4, and Chapter 5.

2.4.1. Language Design Considerations

In the past few years, semiconductor technology has progressed to the point at which the levels of computation that can be done per vertex or per fragment have gone beyond what is feasible to describe by the traditional OpenGL mechanisms of setting state to influence the action of fixed pipeline stages. A natural way of taming this complexity and the proliferation of OpenGL extensions is to replace parts of the pipeline with user-programmable stages. This has been done in some recent OpenGL extensions, but the programming is done in assembly language. It can be difficult and time consuming to write shaders in such low-level languages, and maintaining such code can be costly. As programmable graphics hardware evolves, the current crop of shader assembly languages may also prove to be cumbersome and inefficient to support in hardware.

The ideal solution to these issues was to define a forward-looking, hardware-independent, high-level language that would be easy to use and powerful enough to stand the test of time and that would drastically reduce the need for extensions. These desires were tempered by the need for fast implementations within a generation or two of hardware.

The following design goals were fundamental to the design of the OpenGL Shading Language.

Define a language that works well with OpenGL The OpenGL Shading Language is designed specifically for use within the OpenGL environment. It provides programmable alternatives to certain parts of the fixed functionality of OpenGL. Therefore, the language itself and the programmable processors it defines must have at least as much functionality as what they replace. Furthermore, by design, it is quite easy to refer to existing OpenGL state from within a shader. By design, it is also quite easy to use fixed functionality in one part of the OpenGL processing pipeline and programmable processing in another.

Expose the flexibility of near-future hardware Graphics hardware has been changing rapidly to a model that allows general programmability for vertex and fragment processing. To expose this programmability, the shading language is high level, with appropriate abstractions for the graphics problem domain. The language includes a rich set of built-in functions that allow expression of operations on vectors as easily as on scalars. Exposing hardware capabilities through a high-level programming language also obviates the need for OpenGL extensions that define small changes to the fixed functionality behavior. Exposing an abstraction that is independent of the actual underlying hardware eliminates the plethora of piecemeal extensions to OpenGL.

Provide hardware independence As previously mentioned, the first attempts at exposing the programmability of graphics hardware focused on assembly language interfaces. This was a dangerous direction for software developers to take because it results in software that is inherently nonportable. The goal of a high-level shading language is for the abstraction level to be high enough that application developers can code in a portable way and that hardware vendors have plenty of room to provide innovative hardware architectures and compiler technology.

Define a language that exposes the performance of the underlying graphics hardwareToday's graphics hardware is based on programmable processor technology. It is an established fact nowadays that compiler technology can generate extremely high performance executable code. With the complexity of today's CPUs, it is difficult to manually generate code that can surpass the performance of code generated by a compiler. It is the intent that the object code generated for a shader be independent of other OpenGL state, so that recompiles or managing multiple copies of object code are not necessary.

Define a language that is easy to use One of the considerations here is that writing shaders should be simple and easy. Since most graphics application programmers are familiar with C and C++, this led us to adopt the salient features of these languages as the basis for the OpenGL Shading Language. We also believed that compilers, not application programmers, should perform difficult tasks. We concluded that a single language (with very minor variations) should be the basis for programming all the programmable processors that we were defining, as well as those we envisioned adding in future versions of OpenGL. This allows application programmers to become familiar with the basic shading language constructs and apply them to all programming tasks involving the programmable processors in OpenGL.

Define a language that will stand the test of time This design consideration also led us to base the design of the OpenGL Shading Language on previously successful programming languages such as C and RenderMan. Our hope is that programs written when the OpenGL Shading Language was first defined will still be valid in 10 years. Longevity also requires standardization of some sort, so we expended a great deal of effort both in making hardware vendors happy with the final language specification and in pushing the specification through the approval process of OpenGL's governing body, the OpenGL Architecture Review Board (ARB).

Don't preclude higher levels of parallel processing Newer graphics hardware architectures are providing more and more parallelism at both the vertex and the fragment processing levels. So we took great care with the definition of the OpenGL Shading Language to allow for even higher levels of parallel processing. This consideration has shaped the definition of the language in some subtle but important ways.

Don't include unnecessary language features Some features of C have made implementing optimizing compilers difficult. Some OpenGL Shading Language features address this issue. For example, C allows hidden aliasing of memory by using pointers and passing pointers as function arguments, which may give multiple names to the same memory. These potential aliases handicap the optimizer, leading to complexity or less optimized code. The OpenGL Shading language does not allow pointers, and it calls by value-return to prevent such aliasing. In general, aliasing is disallowed, simplifying the job of the optimizer.

2.4.2. C Basis

As stated previously, the OpenGL Shading Language is based on the syntax of the ANSI C programming language, and at first glance, programs written in this language look very much like C programs. This is intentional, to make the language easier to use for those most likely to be using it, namely, those developing graphics applications in C or C++.

The basic structure of programs written in the OpenGL Shading Language is the same as it is for programs written in C. The entry point of a set of shaders is the function void main(); the body of this function is delimited by curly braces. Constants, identifiers, operators, expressions, and statements are basically the same for the OpenGL Shading Language as they are for C. Control flow for looping, if-then-else, and function calls are virtually identical to C.

2.4.3. Additions to C

The OpenGL Shading Language has a number of language features that have been added because of its special-purpose nature as a language for encoding graphics algorithms. Here are some of the main things that have been added to the OpenGL Shading Language that are different from ANSI C.

Vector types are supported for floating-point, integer, and Boolean values. For floating-point values, these vector types are referred to as vec2 (two floats), vec3 (three floats), and vec4 (four floats). Operators work as readily on vector types as they do on scalars. To sum vectors v1 and v2, you simply would say v1 + v2. Individual components of a vector can be accessed either with array syntax or as fields of a structure. Color values can be accessed by appending .r to the name of a vector variable to access the first component, .g to access the second component, .b to access the third, and .a to access the fourth. Position values can be accessed with .x, .y, .z, and .w, and texture values can be accessed with .s, .t, .p, and .q. Multiple components can be selected by specification of multiple names, like .xy.

Floating-point matrix types are also supported as basic types. The data type mat2 refers to a 2 x 2 matrix of floating-point values, mat3 refers to a 3 x 3 matrix, and mat4 refers to a 4 x 4 matrix. This is a convenient type for expressing the linear transformations common in 3D graphics. Columns of a matrix can be selected with array syntax, yielding a vector whose components can be accessed as just described.

A set of basic types called SAMPLERS has also been added to create the mechanism by which shaders access texture memory. Samplers are a special type of opaque variable that access a particular texture map. A variable of type sampler1D can be used to access a 1D texture map, a variable of type sampler2D can be used to access a 2D texture map, and so on. Shadow and cube map textures are also supported through this mechanism.

Qualifiers have been added to manage the input and output of shaders. The attribute, uniform, and varying qualifiers specify what type of input or output a variable serves. Attribute variables communicate frequently changing values from the application to a vertex shader, uniform variables communicate infrequently changing values from the application to any shader, and varying variables communicate interpolated values from a vertex shader to a fragment shader.

Shaders written in the OpenGL Shading Language can use built-in variables that begin with the reserved prefix "gl_" in order to access existing OpenGL state and to communicate with the fixed functionality of OpenGL. For instance, both vertex and fragment shaders can access built-in uniform variables that contain state values that are readily available within the current rendering context. Some examples are gl_ModelViewMatrix for obtaining the current modelview matrix, gl_LightSource[i] for obtaining the current parameters of the ith light source, and gl_Fog.color for accessing the current fog color. The vertex shader must write the special variable gl_Position in order to provide necessary information to the fixed functionality stages between vertex processing and fragment processing, namely, primitive assembly, clipping, culling, and rasterization. A fragment shader typically writes into one or both of the special variables gl_FragColor or gl_FragDepth. These values represent the computed fragment color and computed fragment depth. These values are submitted to the back-end fixed functionality fragment operations such as alpha testing, stencil testing, and depth testing, before reaching their ultimate destination, the frame buffer.

A variety of built-in functions is also provided in the OpenGL Shading Language in order to make coding easier and to take advantage of possible hardware acceleration for certain operations. The language defines built-in functions for a variety of operations:

2.4.4. Additions from C++

The OpenGL Shading Language also includes a few notable language features from C++. In particular, it supports function overloading to make it easy to define functions that differ only in the type or number of arguments being passed. This feature is heavily used by the built-in functions. For instance, the dot product function is overloaded to deal with arguments that are types float, vec2, vec3, and vec4.

The concept of constructors also comes from C++. Initializers are done only with constructors in the OpenGL Shading Language. Using constructors allows for more than one way of initializing variables.

Another feature borrowed from C++ is that variables can be declared when they are needed; they do not have to be declared at the beginning of a basic block. The basic type bool is supported as in C++.

As in C++, functions must be declared before being used. This can be accomplished either with the function's definition (its body) or just with a prototype.

2.4.5. C Features Not Supported

Unlike ANSI C, the OpenGL Shading Language does not support automatic promotion of data types. Compiler errors are generated if variables used in an expression are of different types. For instance, an error is generated for the statement float f = 0; but not for the statement float f = 0.0;. This approach might seem like a bit of a nuisance, but it simplifies the language by eliminating the need for type promotion rules. It also removes a class of confusing mistakes made when argument type is the basis for calling a set of overloaded functions.

The OpenGL Shading Language does not support pointers, strings, or characters, or any operations based on these. It is fundamentally a language for processing numerical data, not for processing character or string data, so there is no need for these features to complicate the language. To lower the implementation burden (both for the compiler and for the graphics hardware), there is no support for double-precision floats; byte, short, or long integers; or unsigned variants of these.

A few other C language features that were eliminated from consideration in order to simplify the OpenGL Shading Language (or because there was no compelling need for them at the time) are unions, enumerated types, bit fields in structures, and bitwise operators. Finally, the language is not file based, so you won't see any #include directives or other references to file names.

2.4.6. Other Differences

There are a few areas in which the OpenGL Shading Language provides the same functionality as C but does so in a different way. One of these is that constructors, rather than type casts, are used for data type conversion. Constructors, not C-style initializers, are also used for variable initialization. There is no support at all for type casting without conversion, so constructors keep the language type safe. Constructors use the syntax of a function call, where the function name is the name of the desired type and the arguments are the values that will be used to construct the desired value.

Constructors allow a much richer set of operations than simple type casts or C-style initializers, and the flexibility that this richness provides comes in quite handy for dealing with vector and matrix data types. In addition to converting from one scalar type to another, constructors can create a larger type out of a smaller type or reduce a larger type to a smaller type. For instance, the constructor vec3(1.0, 2.0, 3.0) constructs a vec3 data type out of three scalar values, and the constructor vec3(myVec4) strips the fourth component from myVec4 to create a vec3 value.

The other area of difference is that, unlike the call-by-value calling convention used by C, the OpenGL Shading Language uses CALL BY VALUE-RETURN. Input parameters are copied into the function at call time, and output parameters are copied back to the caller before the function exits. Because the function deals only with copies of the function parameters, there are no issues regarding aliasing of variables within a function. Function parameters are identified as input parameters with the qualifier in, they are identified as output parameters with the qualifier out, and they are identified as both input and output parameters with the qualifier inout; if no qualifier is present, they are identified as input parameters.