Data Type (GLSL)

From OpenGL Wiki
(Redirected from Data Initialization (GLSL))
Jump to navigation Jump to search

The OpenGL Shading Language defines a number of data types. It also defines the means by which users can define types.

Basic types[edit]

Basic types in GLSL are the most fundamental types. Non-basic types are aggregates of these fundamental types.

Note: This document will mention double-precision types. These are only available on OpenGL 4.0 or ARB_gpu_shader_fp64.

Scalars[edit]

The basic non-vector types are:

  • bool: conditional type, values may be either true or false
  • int: a signed, two's complement, 32-bit integer
  • uint: an unsigned 32-bit integer
  • float: an IEEE-754 single-precision floating point number
  • double: an IEEE-754 double-precision floating-point number
Warning: The specific sizes and formats for integers and floats in GLSL are only for GLSL 1.30 and above. Lower versions of GLSL may not use these exact specifications.

Vectors[edit]

Each of the scalar types, including booleans, have 2, 3, and 4-component vector equivalents. The n digit below can be 2, 3, or 4:

  • bvecn: a vector of booleans
  • ivecn: a vector of signed integers
  • uvecn: a vector of unsigned integers
  • vecn: a vector of single-precision floating-point numbers
  • dvecn: a vector of double-precision floating-point numbers

Vector values can have the same math operators applied to them that scalar values do. These all perform the component-wise operations on each component. However, in order for these operators to work on vectors, the two vectors must have the same number of components.

Swizzling[edit]

You can access the components of vectors using the following syntax:

vec4 someVec;
someVec.x + someVec.y;

This is called swizzling. You can use x, y, z, or w, referring to the first, second, third, and fourth components, respectively.

The reason it has that name "swizzling" is because the following syntax is entirely valid:

vec2 someVec;
vec4 otherVec = someVec.xyxx;
vec3 thirdVec = otherVec.zyy;

You can use any combination of up to 4 of the letters to create a vector (of the same basic type) of that length. So otherVec.zyy is a vec3, which is how we can initialize a vec3 value with it. Any combination of up to 4 letters is acceptable, so long as the source vector actually has those components. Attempting to access the 'w' component of a vec3 for example is a compile-time error.

Swizzling also works on l-values:

vec4 someVec;
someVec.wzyx = vec4(1.0, 2.0, 3.0, 4.0); // Reverses the order.
someVec.zx = vec2(3.0, 5.0); // Sets the 3rd component of someVec to 3.0 and the 1st component to 5.0

However, when you use a swizzle as a way of setting component values, you cannot use the same swizzle component twice. So someVec.xx = vec2(4.0, 4.0); is not allowed.

Additionally, there are 3 sets of swizzle masks. You can use xyzw, rgba (for colors), or stpq (for texture coordinates). These three sets have no actual difference; they're just syntactic sugar. You cannot combine names from different sets in a single swizzle operation. So ".xrs" is not a valid swizzle mask.

In OpenGL 4.2 or ARB_shading_language_420pack, scalars can be swizzled as well. They obviously only have one source component, but it is legal to do this:

float aFloat;
vec4 someVec = aFloat.xxxx;

Matrices[edit]

In addition to vectors, there are also matrix types. All matrix types are floating-point, either single-precision or double-precision. Matrix types are as follows, where n and m can be the numbers 2, 3, or 4:

  • matnxm: A matrix with n columns and m rows (examples: mat2x2, mat4x3). Note that this is backward from convention in mathematics!
  • matn: Common shorthand for matnxn: a square matrix with n columns and n rows.

Double-precision matrices (GL 4.0 and above) can be declared with a dmat instead of mat (example: dmat4x4).

OpenGL stores matrices in column-major format—that is, elements are stored contiguously in columns.

Swizzling does not work with matrices. You can instead access a matrix's fields with array syntax:

mat3 theMatrix;
theMatrix[1] = vec3(3.0, 3.0, 3.0); // Sets the second column to all 3.0s
theMatrix[2][0] = 16.0; // Sets the first entry of the third column to 16.0.

However, the result of the first array accessor is a vector, so you can swizzle that:

mat3 theMatrix;
theMatrix[1].yzx = vec3(3.0, 1.0, 2.0);

Opaque types[edit]

Opaque types represent some external object which the shader references in some fashion. Opaque variables do not have "values" in the same way as regular types; they are markers that reference the real data. As such, they can only be used as parameters to functions. These functions return/modify the actual referenced data.

Variables of opaque types can only be declared in one of two ways. They can be declared at global scope, as a uniform variables. Such variables can be arrays of the opaque type. They can be declared as members of a struct, but if so, then the struct can only be used to declare a uniform variable (or to declare a member of a struct/array that itself a uniform variable). They cannot be part of a buffer-backed interface block or an input/output variable, either directly or indirectly.

Opaque type variables can also be declared as in-qualified function parameters. This allows you to pass opaque types to user-defined functions.

Opaque types cannot be l-values. Non-array opaque types can only be passed to a function that takes this type as a parameter; they cannot be used as any other part of an expression. Opaque types that are arrayed can use array-index and structure field selection (for .length()).

Samplers[edit]

Texture access is not as simple as reading a value from a memory address. Filtering and other processes are applied to textures, and how texture coordinates are interpreted can be part of the texture access operation. For these reason, texture access is somewhat complicated.

The sampler type is an opaque GLSL type that represents a texture bound to the OpenGL context. There are many sampler types, one for each type of texture (2D, 2D_ARRAY, etc). Samplers can only access textures of the proper type.

Images[edit]

Image variables refer to an image, of a particular type, stored within a texture. These are used for arbitrary loading/storing of values within shaders.

Atomic counters[edit]

Atomic variables represent a memory location within a Buffer Object upon which atomic operations can take place.

Implicit conversion[edit]

Certain values can be implicitly converted to certain types. This means that an explicit construction operation is not necessary.

Signed integers can be implicitly converted to unsigned integers, but the reverse is not true. Either integer type can be converted into floats, and integers and floats can be converted into doubles.

Vector and matrix values are implicitly converted if the basic type they contain is implicitly convertible.

Arrays[edit]

Basic types can be grouped into sequences of those elements, called arrays. This generally works like in C/C++, but there are some limitations. First and foremost is that arrays cannot be multidimensional (unless OpenGL 4.3 or ARB_arrays_of_arrays is available, as shown below).

Arrays usually must be declared with a size which must be initialized with a Constant Expression. Input arrays to Geometry, Tessellation Control, and Tessellation Evaluation Shaders do not need a size, nor do output arrays for non-patch outputs for Tessellation Control Shaders. Global variables can be declared without a size, but only if they are later redeclared with a size. An array in a shader storage interface block may be declared without a size, but only if it is the last variable in the block.

In all cases, the array size must be a compile-time integral Constant Expression.

With a few exceptions, arrays can be used for variables of all Storage Qualifier types: inputs, outputs, uniforms, constants, etc.

The length of an array variable can be computed with the .length() function. For example:

uniform float myValues[12];

...

myValues.length();    // Returns 12.

Invoking the length() function results in a Constant Expression, except when the variable is an unsized array that is the last member of a storage block.

Arrays can be accessed with arbitrary numeric expressions. They do not have to be compile-time constants (though there are a few exceptions to this rule; for example, the very next section).

Opaque arrays[edit]

When an array indexing expression, including struct field member accesses, results in an opaque types, the standard has special requirements on those array indices. Under GLSL version 3.30, Sampler arrays (the only opaque type 3.30 provides) can be declared, but they can only be accessed by compile-time integral Constant Expressions. So you cannot loop over an array of samplers, no matter what the array initializer, offset and comparison expressions are.

Under GLSL 4.00 and above, array indices leading to an opaque value can be accessed by non-compile-time constants, but these index values must be dynamically uniform. The value of those indices must be the same value, in the same execution order, regardless of any non-uniform parameter values, for all shader invocations in the invocation group.

For example, in 4.00, it is legal to loop over an array of samplers, so long as the loop index is based on constants and uniforms. So this is legal:

uniform sampler images[10];
uniform int imageCount;

void main()
{
  vec4 accum = vec4(0.0);
  for (int i = 0; i < imageCount; i++)
  {
    accum += texture(images[i], ...);
  }
}

This would add up all of the values in the textures, up to imageCount in size. Note that this is not legal in GLSL 3.30.

Arrays of arrays[edit]

Arrays of arrays
Core in version 4.6
Core since version 4.3
Core ARB extension ARB_arrays_of_arrays

Given the presence of this feature, arrays can be declared multidimensionally in GLSL:

uniform vec3 multidim[5][2];

multidim is an array of 5 elements, where each element is an array of 2 vec3 elements. So multidim.length() is 5, while multidim[0].length() is 2.

Arrays can also be declared like this:

uniform vec3[5][2] multidim;

This may make things more clear.

You can combine these:

uniform vec3[5][2] multidim1;
uniform vec3[5] multidim2[2];

Interpreting this split declaration is complex, because it is no longer read strictly left-to-right. The arrays on the variable name are read left-to-right, then the arrays on the type name are read left-to-right. So multidim1 is a 5-element array of 2 element arrays of vec3. By contrast, multidim2 is a 2 element array of 5 element arrays of vec3s.

If an array can be specified without a size (such as the last member of a shader storage block), only the first array index can be unsized.

layout(std430) buffer Buffer1
{
    vec3[][2] multidim; // legal
};

layout(std430) buffer Buffer2
{
    vec3[5][] multidim; // not legal
};

layout(std430) buffer Buffer3
{
    vec3[5] multidim[]; // legal: arrays on the variable come first.
};

Here, for the legal multidim definitions, multidim.length() will be computed based on the block of memory attached to this variable. While multidim[0].length() will be 2.

Structs[edit]

Structs are defined much like C++ (note: the C-style typedef struct *{} syntax is not supported). GLSL does not support anonymous structures (ie: structs without a type name), and structs must have at least one member declaration. Structs cannot be defined within another struct, but one struct can use another previously defined struct as a member.

struct Light
{
  vec3 eyePosOrDir;
  bool isDirectional;
  vec3 intensity;
  float attenuation;
} variableName;

The above definition not only defines a struct type Light, it also creates a variable of that type called variableName. As in C/C++, the variable name can be omitted.

Structs can contain opaque types, but such structs can only be used in the ways that opaque types can be used (declared as uniforms or function input variables). Furthermore, if a struct contains an opaque type, arrays of such structs, when used to fetch a member of an opaque type, have to use array indices which conform to the rules on Opaque Arrays.

There are limits on the storage type qualifiers that variables of struct types can be defined with. Specifically, structs cannot be used as input/output variables. They can be used in all other contexts, including global variables, local variables, uniforms, interface block definitions (as long as they aren't input/output blocks), function parameters and return values, and so forth.

Struct fields are accessed using the standard "." syntax from C/C++.

Constructors and initializers[edit]

Variables of almost any type can be initialized with an initial value. The exceptions are:

For basic types, the variable can be initialized by setting it equal to a literal value:

 uniform int aValue = 5;

Note that initializing a uniform only sets that variable at the moment that the Program Object is successfully compiled and linked. The user can override this value by manually setting that uniform value.

Literals[edit]

A literal is a value written into the shader. Literals are always basic types, and they can be used to initialize the value of a basic type. They can also be used wherever a constant basic value is needed.

Literals are typed. There are two literals of bool type: true and false.

Any integer value is by default of type int (a signed integer). To create an integer literal of unsigned type, use the suffix u or U.

Integer literals can be defined in base 8 or base 16 using the standard C/C++ conventions (prefix with 0 for base 8 or 0x for base 16).

A numeric literal that uses a decimal is by default of type float. To create a float literal from an integer value, use the suffix f or F as in C/C++.

Floats can also be defined using exponential notation, using the e or E to separate the base from the exponent. The exponent is always base-10.

In GLSL 4.00 and above, double-precision floats are available. By default, all floating-point literals are of type float. To create a double-precision float, use the lf or LF suffixes. This will force it to the double type.

Constructors[edit]

Types more complex than the base types are initialized with constructors. All constructors take the form:

 typename​(value1​, value2​, ...)

The typename​ is the name of the type to construct. The values can be literals, previously-defined variables, and other constructors.

Some constructors have special syntax.

Conversion constructors[edit]

The basic scalar type constructors can be used to convert values from one type to another. What you get depends on the kind of conversion.

from bool
If it is false, then you get 0, in whatever the output type is (floating-point types get 0.0). If it is true, then you get 1, in whatever the output type is.
to bool
A value equal to 0 or 0.0 becomes false; anything else is true.
between int and uint
Conversion between these types is guaranteed to preserve the bit pattern, which can change the sign of the value.

Note: This is only true for GLSL 1.30 and above; older version of GLSL did not guarantee this.

Vector constructors[edit]

Vector constructors take the number of values that they store. So a vec3 takes 3 values. However, all vectors can be constructed from a single value; this results in a vector where all of the values are the value given to the constructor. So:

vec3(1.0) == vec3(1.0, 1.0, 1.0);

Vectors can also be constructed from other vectors, or a combination of vectors and scalars. The values in the output vector are filled in, right to left, by the values in the input scalars and vectors. There must be enough values, counting scalars and vectors, to fill in all of the elements of the output vector. Here are some examples:

vec4(vec2(10.0, 11.0), 1.0, 3.5) == vec4(10.0, vec2(11.0, 1.0), 3.5);
vec3(vec4(1.0, 2.0, 3.0, 4.0)) == vec3(1.0, 2.0, 3.0);
vec4(vec3(1.0, 2.0, 3.0)); // error. Not enough components.
vec2(vec3(1.0, 2.0, 3.0)); // OK

Matrix constructors[edit]

For matrices, construction is rather more complicated.

If a matrix is constructed with a single scalar value, then that value is used to initialize all the values along the diagonal of the matrix; the rest are given zeros. Therefore, mat4(1.0) is a 4x4 identity matrix.

For multiple values, matrices are filled in in column-major order. That is, the first X values are the first column, the second X values are the next column, and so forth. Examples:

mat2(
  float, float,   // first column
  float, float);  // second column

mat4(
  vec4,           // first column
  vec4,           // second column
  vec4,           // third column
  vec4);          // fourth column

mat3(
  vec2, float,    // first column
  vec2, float,    // second column
  vec2, float);   // third column

A matrix can be constructed from another matrix A matrix can only be constructed from a single other matrix. The column and row values from the input matrix are copied to their corresponding values in the output; any values of the output not filled in are filled with the identity matrix.

Therefore:

 mat3 diagMatrix = mat3(5.0);  // Diagonal matrix with 5.0 on the diagonal.
 mat4 otherMatrix = mat4(diagMatrix);

The otherMatrix is a diagonal matrix, where the first 3 values are 5.0, and the last diagonal value is 1.0.

There are no restrictions on size when doing matrix construction from another matrix. So you can construct a 4x2 matrix from a 2x4 matrix; only the corresponding elements are copied.

Array constructors[edit]

Arrays can be constructed using array constructor syntax. In this case, the type also contains the [] array notation:

 const float array[3] = float[3](2.5, 7.0, 1.5);

The size is not necessary for array constructors, as the size is implicitly specified by the number of arguments to the constructor.

Struct constructors[edit]

Structs are constructed by filling in their values in the order in which they are defined. For example:

struct Data
{
  float first;
  vec2 second;
};

Data dataValue = Data(1.4, vec2(16.0, 22.5));

Notice the vector constructor in the middle of the struct constructor. Constructors can be nested. So if you have an array of structs of the above type, you can construct it as follows:

Data dataArray[3] = Data[3](
  Data(1.0, vec2(-19.0, 4.5)),
  Data(-3.0, vec2(2.718, 2.0)),
  Data(29.5, vec2(3.142, 3.333)));

Initializer lists[edit]

Initializer Lists
Core in version 4.6
Core since version 4.2
Core ARB extension ARB_shading_language_420pack

While the above initialization rules exist, they are somewhat verbose. Consider the above initializer:

Data dataArray[3] = Data[3](
  Data(1.0, vec2(-19.0, 4.5)),
  Data(-3.0, vec2(2.718, 2.0)),
  Data(29.5, vec2(3.142, 3.333)));

This requires the user to specify the same thing multiple times. The array typename Data[3] must be specified twice, and if you get it wrong once, you get a compiler error. For each item in the array, the type of that item Data must be specified, and if you get it wrong in one place, you get a compiler error. While these are compiler errors rather than runtime issues, it's not like the compiler doesn't already know what type has to go there.

Therefore, GLSL takes a feature from C++11 and allows the initialization of any type by using an initializer list. An initializer list is a list of initializers (which themselves could be initializer lists) that are used to initialize a variable. Initializer lists are bounded by curly braces ("{" and "}").

Initializer lists do not require specifying the type name; the compiler will deduce it correctly. And this deduction extends down the hierarchy of objects in aggregates (structs/arrays). For example, the above code could be initialized with the following initializer list:

Data dataArray[3] = {
  {1.0, {-19.0, 4.5} },
  {-3.0, {2.718, 2.0} },
  {29.5, {3.142, 3.333} } };

The compiler automatically deduces that the second element of each Data member is an vec2. This is based on the definition of Data.

Initializer list initialization is subject to all of the restraints and privileges of constructor initialization. So vec2(...) is always equivalent to {...} , when the latter is used to initialize a vec2.

An initializer list is (itself) not an expression. So you cannot do {...} + {...} and get a reasonable result.

Initializer lists can be used to initialize anything, even scalar values (bool val = {true};).

When initializing aggregates with initializer lists (aggregates being arrays or structs), the number of initializer values in the list must exactly match the number of items in the aggregate.

Interface blocks[edit]

Groups of variables that use the input, output, or uniform type qualifiers can be grouped into named blocks. These have special syntax, and appear similar to a struct definition. However, they are not equivalent to structs.

See also[edit]