The Core Language of GLSL is grammatically very similar to C, with some elements from C++. It contains the same looping and conditional keywords as C/C++.
One big difference is that GLSL shaders must start with a version declaration, which tells the compiler what version of the language to compile the rest. A shader that does not begin with a version declaration will be assumed to be the (very ancient) version 1.10.
Another large difference between C/C++ and GLSL is how functions
work. The general syntax is similar, but the parameter passing conventions are quite different, allowing functions to have dedicated output parameters without pointers. A more difficult issue is that GLSL does not support recursion
at all; a function must never call itself or call another function that calls itself.
GLSL has a large number of Data Types. It has scalar boolean, floating point, double-precision float (for GLSL v4.00+), integer, and unsigned integer types. These can be aggregated into vectors and matrices up to 4 dimensions in size.
Vectors have a powerful swizzling mechanism that allows one to arbitrarily order their components. This works on both reading and writing, allowing for constructs like, output.zyx = input.xxy.
Variables can be aggregated into arrays and structs more or less arbitrarily (though having arrays that contain arrays requires GLSL v4.30).
Among the types for GLSL variables are certain types that are designated Opaque Types. These are types that represent some construct of OpenGL that the shader is allowed to access. For example, variables that represent textures use a sampler opaque type.
Opaque types are treated specially by GLSL. They cannot be placed in structs, and their use in arrays is limited. They do not have a "value" in any real sense to GLSL; it is merely an interface to an OpenGL resource. The only way to use them, besides declaring them, is to pass them to other functions. And all opaque types have a number of built-in GLSL functions to read from or write to them.
Shader stages have a large number of interfaces. They have a means of receiving input from prior pipeline stages, sending output to later stages, retrieving uniform data provided by the application, and so forth. These interfaces, and the behavior of data accessed through them, are defined by global variables declared with special Type Qualifiers.
There are a wide range of special qualifiers. Among the most important qualifiers are the input qualifiers
, which designates input values from earlier stages; output qualifiers
, which designate output values to the next stage, and uniform qualifiers
, which designates parameters set from OpenGL. Certain GLSL types can only be used with certain qualifiers. For example, all opaque types must be declared as uniform
s, when declared as globals.
Among the qualifiers that can be applied to variables, the most complex set are the layout qualifiers. These specify certain attributes of a variable definition, such as where it gets its data from, how it interacts with other variables, or how to interpret the data provided to this variable. They define the layout of the variable.
These qualifiers are useful for defining many attributes of variables that would otherwise have to be defined at program linking time or after the program is linked. Thus, they allow the shader to be more standalone. In general, if a layout qualifier conflicts with user-specified data, the layout qualifier wins.
Global variables of certain kinds of storage qualifiers can be aggregated into Interface Blocks
. These blocks group all of these variables under a certain name. This allows different stages to more easily communicate, as these groups of variables can often be aggregated into arrays. Interface blocks are also used for aggregating large data structures whose storage comes from Buffer Objects
GLSL provides a number of built-in variables and functions. Most of the variables are specific to a certain stage, providing stage-specific information to that shader. Most of the functions are not stage specific; they can (usually) be called from any shader stage.
All built-in GLSL variables begin with the "gl_" prefix, which is reserved in GLSL for this purpose.
The Vertex Shader is the programmable Shader stage in the rendering pipeline that handles the processing of individual vertices. Vertex shaders are fed Vertex Attribute data, as specified from a vertex array object by a drawing command. A vertex shader receives a single vertex from the vertex stream and generates a single vertex to the output vertex stream. There must be a 1:1 mapping from input vertices to output vertices.
Vertex shaders typically perform transformations to post-projection space, for consumption by the Vertex Post-Processing
stage. They can also be used to do per-vertex lighting, or to perform setup work for later Vertex Processing
The Tessellation Control Shader (TCS) is the shader stage after the vertex shader. It exists to do user-defined setup work for Tessellation operations. It is an optional shader stage; even if you want to do tessellation, it is optional.
The purpose of the TCS is to define the size of the patch data consumed by the tessellator, compute the values for that patch data, and decide how much tessellation will be performed on each patch. The TCS executes on patches, but each TCS invocation executes on a specific input vertex of a patch. All of the TCS invocations for a patch can communicate with one another.
The Tessellation Evaluation Shader (TES) is the shader stage responsible for computing the actual tessellated vertices. The fixed-function tessellator takes a patch and breaks it into pieces. The TES takes those smaller pieces and figures out how to interpolate the original patch data to generate the new per-vertex data for each tessellated vertex.
The TES operates similarly to a vertex shader, in that there is a 1:1 correlation between a vertex in the tessellated patch and a TES invocation. Thus, every TES is responsible for computing a given vertex in the tessellated patch.
The TES is mandatory for tessellation. If there is no active TES stage, then no tessellation will happen.
The Geometry Shader is the shader stage just before Vertex Post-Processing. It takes a Primitive as input and outputs zero or more primitives. Geometry Shaders are used to do setup work for Transform Feedback operations, as well as tag primitives as being rasterized to specific layers in the Framebuffer Object.
Though Geometry Shaders can be used to implement a form of tessellation, you are advised to avoid using them for this purpose. On hardware that lacks Tessellation
support, GS's will be quite slow at it. And on hardware with proper Tessellation support, you should just use that.
When a Primitive is rasterized, it is converted into blocks of data based on sample-sized areas of the primitive being rasterized. This data is called a Fragment. The Fragment Shader is the shader stage responsible for processing fragments. For each input fragment, it produces an output fragment consisting of a number of colors, a depth, and a stencil value that will be output to the Framebuffer (after some post-fragment shader operations).
Fragment shader input values are interpolated across the surface of the primitive, based on the interpolation qualifiers present on the input variable declaration (and on the corresponding output declaration in the previous shader stage).
Fragment shaders can use most of the texture accessing functions
with full mipmapping support. They also can effectively abort processing, which not only ignores the output of the fragment shader, but culls the fragment entirely, as if the fragment had never been rasterized.
The Compute Shader is the shader stage for doing arbitrary computations. It uses an execution model based on groups of shader invocations which can share data. The invocations within these "work groups" can inter-communicate reasonably freely, with relatively minimal overhead. The compute shader determines how many invocations go into each work group, though the maximum number is limited.
Compute shaders are not invoked by rendering operations. They are instead started by calling a special dispatch function. Each execution of a compute shader will run the given number of work groups.
While compute shaders could in theory take the place of rendering, this is not a practical use for them.