Home glsl - storagequalifier
Post
Cancel

glsl - storagequalifier

wiki

A type qualifier is used in the OpenGL Shading Language (GLSL) to modify the storage or behavior of global and locally defined variables. These qualifiers change particular aspects of the variable, such as where they get their data from and so forth. They come in a number of different categories.

Default

If no storage qualifier is specified when declaring a global variable, then the variable is just a normal global variable. All global variables have a scope of the particular shader stage. So if you have two vertex shader objects that define int someValue;, when you link them into a program, they will both be referring to the same value.

Each separate invocation of the same shader stage will refer to different versions of the same global variable.So invocations cannot communicate between each other through default-qualified global variables; that would require the shared qualifier.

If no storage qualifier is specified for a local variable, then the variable is a local variable that can be modified. Local variables are scoped as with C/C++: the local scope in which they are declared.

Constant qualifier

Global and local variables, as well as input function parameters, can be declared with the const qualifier. This means that the variable’s value cannot be changed after it is initialized. This also means that the variable declaration must initialize the variable.

For variables that are not function parameters, const-qualified variables which are initialized by Constant Expressions are themselves constant expressions. GLSL versions before 4.20 required that all non-parameter const-qualified variables be initialized with Constant Expressions. 4.20 permits such variables to be initialized with non-constant expressions, but the variable in that case will not be a constant expression.

Shader stage inputs and outputs

Global variables declared with the in qualifier are shader stage input variables. These variables are given values by the previous stage (possibly via interpolation of values output from multiple shader executions). These variables are not constant (in the sense of const), but they cannot be changed by user code.

Global variables declared with the out qualifier are shader stage output variables.These values are passed to the next stage of the pipeline (possibly via interpolation of values output from multiple shader executions).The shader must set all output variables at some point in its execution; there are only two exceptions to this:

  1. The shader is a Fragment Shader which executes the discard statement.
  2. The output variables which are not written are not read by the next shader stage or pipeline process. This includes writes to fragment shader outputs which are masked off.

These qualifiers can not be used on local variables. And on Function Parameters, they take on an entirely different meaning.

Variables qualified with these can be of any non-opaque basic type (though some stages have more restrictive limitations on types. See below). They cannot be of struct types, but they can be arrays. Input/output variables can be aggregated into interface blocks. There are usually very strict limits on the number of user-defined input and output values available to each shader stage.

The input and output-qualified variables define an interface between this shader and the previous/next part of the pipeline. The interfaces between two shader stages must match, though not necessarily exactly.

There are some special cases with input and output variables for different stages.

Vertex shader inputs

User-defined inputs for vertex shaders are called vertex attributes. They are passed via vertex arrays to the vertex shader from data stored in Buffer Objects.

Vertex shader inputs have attribute location indices that are used in vertex specification to identify a particular input. Most input variables take up only one attribute location index, but certain types (matrices, array inputs, and some double-precision types) can take up more than one index.

Tessellation control shader outputs

Tessellation Control Shader outputs can be either per-vertex or per-patch. Per-vertex outputs are aggregated into arrays, where the length of the arrays is the number of output vertices in the patch. Per-patch outputs (declared with the patch keyword) are not arrays (you can declare arrays of them, but they won’t be indexed per-vertex).

TCS’s can read from any per-vertex or per-patch output, so each TCS invocation can read data written by other TCS invocations (assuming you take steps to ensure that the other invocations have written to them). But each TCS invocation represents a single vertex in the output patch. Therefore, a TCS invocation can only write to the per-vertex output index that corresponds to the vertex for which that invocation is executing. This means that the only expression in the array subscript that can be used when writing to a per-vertex output is gl_InvocationID.

Tessellation evaluation shader inputs

Tessellation Evaluation Shader inputs are like TCS outputs: either per-vertex or per-patch (declared with the patch keyword. Non-patch inputs are aggregated into arrays, where the length of the arrays is the number of vertex in the patch (from the TCS, or if there is no TCS, then the patch size given when rendering). Any TES invocation can read any per-vertex or per-patch input.

Geometry shader inputs

Geometry Shader inputs are aggregated into arrays, one per vertex in the primitive. The length of the array depends on the input primitive type used by the GS. Each array index represents a single vertex in the input primitive.

Fragment shader outputs

Fragment shader outputs cannot be matrices or boolean types. They must be vectors or scalars of single-precision floating-point or integer types. They may also be arrays of these types. Like vertex shader inputs, fragment shader outputs are assigned indices, either explicitly by the user or implicitly by the system.

Each fragment shader output’s index corresponds to a draw buffer, as defined by the current draw framebuffer’s draw-buffer setting, set with the glDrawBuffers command. The indices in the array passed to this function correspond to an output variable’s assigned index. The value in the array at that index refers to a specific image in the current drawing framebuffer.

Output variables from the fragment shader do not have to be written. However, unwritten outputs will have undefined values. This is OK if the current Framebuffer Draw Buffer setting would discard that value (by using GL_NONE).

Interpolation qualifiers

Certain inputs and outputs can use interpolation qualifiers. These are for any values which could be interpolated as a result of rasterization. These include:

  • Vertex shader outputs
  • Tessellation control shader inputs (to match with outputs from the VS)
  • Tessellation evaluation shader outputs
  • Geometry shader inputs (to match with outputs from the TES/VS) and outputs
  • Fragment shader inputs

While interpolation qualifiers can be put on any of these variables, the only ones that actually have an effect are those on the fragment shader. The pre-GL 4.3 rules on matching interfaces mean you have to provide them on other stages, but interpolation is controlled entirely by the qualifiers on the fragment shader input variables.

Interpolation qualifiers control how interpolation of values happens across a triangle or other primitive. There are three basic interpolation qualifiers.

flat

The value will not be interpolated. The value given to the fragment shader is the value from the Provoking Vertex for that primitive.

noperspective

The value will be linearly interpolated in window-space. This is usually not what you want, but it can have its uses.

smooth

The value will be interpolated in a perspective-correct fashion. This is the default if no qualifier is present.

The centroid and sample (the latter requires OpenGL 4.0 or ARB_gpu_shader5) qualifiers affect interpolation, but they are somewhat special, grammatically speaking. Before OpenGL 4.2, these were considered part of the in/out storage qualifier. So both had to be used together, like centroid in or sample out; no qualifiers could come between them. In 4.2 and above, they are considered auxiliary qualifiers, so they can be separated from the in/out storage qualifier.

They may not technically be interpolation qualifiers, but they do control aspects of interpolation. They only have an effect when Multisampling is being used.

During multisampling, if centroid is not present, then the written value can be interpolated to to an arbitrary position within the pixel. This may be the pixel’s center, one of the sample locations within the pixel, or an arbitrary location. Most importantly of all, this sample may lie outside of the actual primitive being rendered, since a primitive can cover only part of a pixel’s area. If the implementation computes the sample based on the center of the pixel, and the primitive doesn’t actually cover the pixel’s center (remember: in multisampling, this can still produce a non-zero number of samples), then the interpolated value will be outside of the primitive’s borders.

The centroid qualifier is used to prevent this; the interpolation point must fall within both the pixel’s area and the primitive’s area. This is useful for parameters or computations that would have undefined values if they fell outside of the primitive’s area. A square root is only defined for positive numbers, so if you are taking the square root of an interpolated value, you may need to use centroid interpolation.

You should only use centroid if there is a real problem like this. In many cases interpolating without centroid doesn’t pose a problem.

The sample qualifier forces OpenGL to interpolate this qualifier to the location of the particular sample for each generated fragment. This is only really useful with per-sample shading.

Uniforms

Global variables and Interface Blocks can be declared with the uniform qualifier. This means that the value does not change between multiple executions of a shader during the rendering of a primitive (ie: during a glDraw* call). These values are set by the user from the OpenGL API.

They are constant, but not compile-time constant (so not const).

Buffer

In OpenGL 4.3 or ARB_shader_storage_buffer_object, interface blocks can be qualified with the buffer qualifier. This means that the storage for the contents of the block comes from a buffer object, similarly to Uniform Buffer Objects and uniform blocks. Unlike UBOs, storage blocks can be written to. They can also have an array with an unbounded size, who’s size is determined by the size of the SSBO bound at render time.

Interface blocks

Inputs, outputs, uniforms, and buffer variables for various stages can grouped into interface blocks.

Shared

Variables declared with the shared qualifier are shared among several shader invocations. Such variables can only be used in Compute Shaders. Shared variables are shared among all invocations in a work group.

Layout qualifiers

There are a large number of layout qualifiers which can be applied to a variety of defined constructs, from interface blocks to shader stage inputs and outputs. These affect the storage location of their data and many other properties about where the variable’s data comes from or other user-facing interfaces about the data.

Precision qualifiers

There are three precision qualifiers: highp, mediump, and lowp. They have no semantic meaning or functional effect. They can apply to any floating-point type (vector or matrix), or any integer type.

1
precision precision-qualifier type;

Memory qualifiers

coherent

Normally, the compiler is free to assume that this shader invocation is the only invocation that modifies values read through this variable. It also can freely assume that other shader invocations may not see values written through this variable.

Using this qualifier is required to allow dependent shader invocations to communicate with one another, as it enforces the coherency of memory accesses. Using this requires the appropriate memory barriers to be executed, so that visibility can be achieved.

When communicating between shader invocations for different rendering commands, glMemoryBarrier should be used instead of this qualifier.

volatile

The compiler normally is free to assume that values accessed through variables will only change after memory barriers or other synchronization. With this qualifier, the compiler assumes that the contents of the storage represented by the variable could be changed at any time.

restrict

Normally, the compiler must assume that you could access the same image/buffer object through separate variables in the same shader. Therefore, if you write to one variable, and read from a second, the compiler assumes that it is possible that you could be reading the value you just wrote. With this qualifier, you are telling the compiler that this particular variable is the only variable that can modify the memory visible through that variable within this shader invocation (other shader stages don’t count here). This allows the compiler to optimize reads/writes better.

You should use this wherever possible.

readonly

Normally, the compiler allows you to read and write from variables as you wish. If you use this, the variable can only be used for reading operations (atomic operations are forbidden as they also count as writes).

writeonly

Normally, the compiler allows you to read and write from variables as you wish. If you use this, the variable can only be used for writing operations (atomic operations are forbidden as they also count as reads).

Invariance qualifiers

There is a way to qualify certain output variables as being invariant. This allows different programs to compute the exact same answer, assuming certain conditions are met.

The invariant qualifier can be applied to an existing declaration, as in this case:

1
2
3
invariant gl_Position; //Not redeclared; just uses invariant.
out vec3 Color;
invariant Color; //Again makes existing declaration invariant.

Or you can use it at the declaration site.

1
invariant out vec3 Color;

Only input and output variables can be declared invariant. However, for input variables, it has no meaning; it is only allowed for symmetry. Note that the invariant qualifier does not participate in interface matching, so you do not have to use it at all on input variables from stages that declared that output as invariant.

Precise qualifiers

The precise qualifier allows shader computations to be carried out exactly as specified in the source code in order to avoid optimization-induced invariance issues. This is critical to avoid cracks in tessellation shaders, but also useful in many other scenarios where invariance is required.

Among other things, precise prevents the use of fused-multiply-add instructions to implement expressions of the form a * b + c, but the fma intrinsic can still be used explicitly to allow specifying the order in which operations are combined. For further details see Section 4.9 in https://www.khronos.org/registry/OpenGL/specs/gl/GLSLangSpec.4.50.pdf page 90.

Qualifier order

Unless you are in OpenGL 4.2 or ARB_shading_language_420pack, qualifiers always come in a particular order. For non-parameter values, the order is always this:

1
invariant-qualifier interpolation-qualifier layout-qualifier other-storage-qualifier precision-qualifier

The centroid qualifier, if present, must immediately precede in or out. For the purpose of ordering, it is considered part of the in or out storage qualifier, not part of the interpolation qualifier.

OpenGL 4.2 or ARB_shading_language_420pack removes the ordering restriction in most cases. centroid still has to immediate precede in or out. It also allows multiple layout qualifiers, but you can still only use one qualifier from most other groups (there can be multiple memory qualifiers). Also, all qualifiers must still come before the type specifier. The groups of qualifiers match the main headings above: storage, layout, precision, etc.

Built-in redeclaration

The GLSL defines a number of predefined variables at the various shader stages. These pre-defined variables are defined with a particular set of qualifiers, as stated in the above article. If you wish to use pre-defined variables with a different qualifier, you can re-declare the variable, but the re-declaration must use the same type. Some variables cannot be redeclared with a new qualifier. For example: gl_Position in the vertex shader cannot use an interpolation qualifier at all.

Removed qualifiers

The following qualifiers are deprecated as of GLSL 1.30 (OpenGL 3.0) and removed from GLSL 1.40 and above.

The attribute qualifier is effectively equivalent to an input qualifier in vertex shaders. It cannot be used in any other shader stage. It cannot be used in interface blocks.

The varying qualifier is equivalent to the input of a fragment shader or the output of a vertex shader. It cannot be used in any other shader stages. It cannot be used in interface blocks.

This post is licensed under CC BY 4.0 by the author.