Fact-checked by Grok 2 weeks ago

OpenGL Shading Language

The OpenGL Shading Language (GLSL) is a high-level, C-like programming language specifically designed for writing shaders that define custom processing in the programmable stages of the OpenGL graphics rendering pipeline, including vertex, tessellation, geometry, fragment, and compute shaders. Developed by the Khronos Group, GLSL enables developers to create flexible, high-performance graphics effects by allowing direct control over transformations, lighting, texturing, and computations on graphics hardware. It supports a range of data types such as scalars, vectors, matrices, and structures, along with built-in functions for operations like texture sampling, matrix manipulations, and atomic counters to facilitate parallel processing. Key language features include storage qualifiers (e.g., in, out, uniform), layout qualifiers for resource binding, control flow statements, and subroutine functionality for dynamic shader behavior. GLSL originated in the early 2000s as part of the transition to programmable pipelines, with its first specification (version 1.10) introduced alongside 2.0 in 2004 to replace fixed-function and fragment processing with extensible programs. Subsequent versions aligned with releases, such as GLSL 1.20 in 2.1 (2006) for improved matrix support, GLSL 1.40 in 3.1 (2009), and GLSL 3.30 in 3.3 (2010) to enhance compatibility and core profile features. The language evolved further in 4.0 (2010) with the addition of shaders (following shaders in 3.2), reaching version 4.60 with 4.6 in 2017 to support modern capabilities including SPIR-V for cross-API compatibility with . Today, GLSL remains integral to OpenGL and OpenGL ES ecosystems, with variants for embedded systems (e.g., GLSL ES 3.20) ensuring portability across desktop, mobile, and console platforms while maintaining strict type safety and implementation-defined limits on resources like uniform blocks and texture units.

Introduction

Overview

The OpenGL Shading Language (GLSL) is a high-level, C-like programming language designed for writing shaders that run on graphics processing units (GPUs) as part of the OpenGL rendering pipeline. It provides developers with the ability to create programmable stages for custom graphics processing, enabling effects such as lighting, texturing, and procedural geometry in vertex, fragment, and other shaders. Key characteristics of GLSL include its strongly typed nature, absence of pointers or pointer arithmetic, and built-in support for and operations optimized for graphics tasks. Shaders are compiled at runtime by the OpenGL driver, which translates the high-level code into GPU-executable instructions. GLSL was introduced alongside 2.0 in to facilitate programmable in the API. Over time, it has extended its utility beyond OpenGL by supporting compilation to SPIR-V, a binary intermediate representation that enables GLSL shaders to be used in and other APIs. A typical GLSL shader structure starts with a version directive to specify the language version, followed by declarations of input and output interfaces using qualifiers like in and out, and concludes with a main serving as the execution .

History and Versions

The Shading Language (GLSL) was first introduced with 2.0 in 2004, marking version 1.10 and enabling programmable and fragment shaders to replace parts of the fixed-function pipeline. This initial release provided a C-like syntax for writing shaders, supporting vector and matrix operations essential for graphics processing. Subsequent versions aligned closely with OpenGL advancements. GLSL 1.20 accompanied 2.1 in 2006, adding support for non-square matrices to enhance matrix operations in shaders. 3.0 in 2008 introduced a model for legacy features, while 3.2 in 2009 formalized core and profiles, with the core profile requiring GLSL 1.50 and support for earlier versions like 1.10 and 1.20 in favor of modern features. Geometry shaders arrived in GLSL 1.50 with 3.2 in 2009, allowing dynamic generation and manipulation between and fragment stages. Further milestones expanded shader capabilities significantly. GLSL 3.30 paired with 3.3 in 2010, refining precision qualifiers and introducing features like dual-source blending for advanced rendering effects. shaders were added in GLSL 4.00 alongside 4.0 in 2010, enabling subdivision of primitives for detailed surface rendering. Compute shaders debuted in GLSL 4.30 with 4.3 in 2012, extending GLSL to general-purpose GPU computing beyond graphics pipelines. More recent versions focused on optimization and interoperability. GLSL 4.50 arrived with 4.5 in 2014, incorporating enhanced uniform and buffer binding mechanisms for efficient resource management. The final core version, GLSL 4.60, was released with 4.6 in 2017, adding SPIR-V binary support for better compatibility with and clarifying precision behaviors in shaders. As of 2025, no new major GLSL versions have been issued, though extensions such as GL_EXT_mesh_shader (October 2025) introduce mesh and task shaders via vendor-agnostic mechanisms, building on earlier NVIDIA-specific NV_mesh_shader from 2018. Shaders declare their GLSL version using the #version preprocessor directive at the top of the source, such as #version 110 for GLSL 1.10 or #version 460 for GLSL 4.60, ensuring compatibility with the targeted context. Core profiles, formalized starting with 3.2 and GLSL 1.50, deprecate fixed-function pipeline references and legacy features like immediate mode, promoting purely programmable rendering to streamline modern implementations. A related variant, GLSL ES, serves as a subset for embedded systems via and . GLSL ES 1.00 launched with 2.0 in 2007, supporting vertex and fragment shaders on mobile platforms. Later, GLSL ES 3.00 accompanied 3.0 in 2012, adding integer textures and uniform buffers while maintaining a lighter footprint for resource-constrained environments. Subsequent releases include GLSL ES 3.10 with 3.1 in 2014, introducing compute shaders, and GLSL ES 3.20 with 3.2 in 2015, adding and shaders.

Shader Pipeline Integration

Core Shader Stages

The core shader stages in the OpenGL Shading Language (GLSL) represent the programmable components of the rendering pipeline, allowing developers to customize processing, primitive generation, fragment coloring, surface subdivision, and general-purpose computation on the GPU. These stages process data in a fixed sequence for graphics rendering, with compute s operating independently for non-graphics tasks. Each stage is implemented as a separate object compiled from GLSL source code, and they interact through well-defined interfaces that pass transformed data downstream. The shader is the initial programmable , executing once per to transform input attributes such as , , and coordinates into clip-space coordinates. It computes the output via the built-in gl_Position and may calculate per- varyings that are interpolated across for subsequent . This handles tasks like model-view-projection transformations and calculations at the level, replacing fixed-function processing in earlier versions. Following primitive assembly, the optional geometry shader processes entire input primitives—such as points, lines, or triangles—allowing for primitive generation, amplification, or culling. It can emit new vertices using functions like EmitVertex() to produce output primitives of the same or different types, enabling effects such as exploding polygons into particles or generating billboards from points. Introduced in GLSL version 1.50 alongside OpenGL 3.2, this stage operates after vertex processing and before rasterization, with restrictions on input and output primitive topologies that must match declared layouts. Tessellation introduces two additional optional stages for adaptive surface subdivision: the tessellation control shader and the tessellation evaluation shader, added in GLSL version 4.00 with OpenGL 4.0. The control shader processes input patches (groups of vertices representing a coarse patch), computing tessellation levels that determine subdivision density and outputting per-patch and per-vertex data shared among invocations. It facilitates tasks like determining detail based on distance or curvature for subdivision surfaces. The evaluation shader then generates detailed vertices from the patch by evaluating positions and attributes at computed tessellation points, effectively subdividing the surface into finer primitives for downstream processing. These stages insert between vertex and geometry shaders, supporting efficient rendering of complex geometry without excessive vertex buffer storage. The fragment shader, also known as the pixel shader, executes per fragment after rasterization, using interpolated inputs from prior stages to compute final color and depth values for each pixel. It supports operations like texturing, , and , and can discard fragments for effects such as alpha testing via the discard statement, preventing contribution to the . This stage replaces fixed-function fragment processing and runs in parallel across fragments within a primitive. Separate from the , the compute shader enables general-purpose GPU computing and was introduced in GLSL version 4.30 with 4.3. It lacks fixed inputs or outputs tied to rendering primitives, instead operating on workgroups dispatched via glDispatchCompute calls, with invocations synchronized using barriers and for intra-group communication. This stage supports arbitrary parallel algorithms, such as simulations or image processing, without rasterization involvement. In the , stages execute sequentially: vertex shaders process inputs to form , followed optionally by tessellation control and evaluation for subdivision, then for primitive manipulation, rasterization to generate fragments, and finally fragment . Compute shaders run in parallel to this flow, invoked independently. Stage-specific restrictions include the absence of or in pipelines without explicit enablement, and compute requiring dedicated dispatch without integration into the render path; incompatible stage combinations during program linking result in errors.

Input and Output Interfaces

In the Shading Language (GLSL), input and output interfaces facilitate the declaration and transfer of between stages, from the application to , and to external resources such as buffers and textures. These interfaces use specific qualifiers to define the direction and nature of flow, ensuring efficient communication within the programmable . For instance, the in qualifier declares read-only variables that receive from the previous stage or vertex attributes provided by the application, while the out qualifier specifies writable variables that pass to the subsequent stage. Input variables declared with in are per-vertex in vertex and geometry shaders, meaning each invocation processes data for a single , whereas in fragment shaders, they represent interpolated values across the primitive. A typical declaration might be in vec3 normal;, where normal receives a attribute in the vertex shader or an interpolated in the fragment shader. Similarly, output variables use out, such as out vec4 color;, allowing the shader to compute and forward values like fragment colors to the or next stage. These qualifiers ensure that data is not modifiable within the receiving shader, promoting pipeline integrity. Uniforms provide a mechanism for the application to supply global, read-only data to , remaining constant for all invocations within a draw call. Declared as uniform mat4 modelView;, they can represent transformation matrices or lighting parameters set via calls like glUniformMatrix4fv. For better organization, uniforms can be grouped into , such as:
[uniform](/page/Uniform) [Block](/page/Block) {
    mat4 modelView;
    vec4 lightPosition;
};
This allows explicit and control, reducing overhead in uniform buffer objects. Uniforms differ from other storage qualifiers like in and out by being shared across all shader invocations without . Varyings enable the automatic transfer and interpolation of data between vertex or geometry shader outputs and fragment shader inputs. An output from the vertex shader, declared as out vec3 texCoord;, is smoothly interpolated by default across the primitive and becomes an in vec3 texCoord; in the fragment shader, providing per-fragment values for texturing or shading computations. Interpolation can be modified with qualifiers like flat for non-interpolated (constant) values or noperspective for perspective-correct adjustments, ensuring accurate rendering of varying attributes. Interface matching rules enforce compatibility during program linking, requiring that corresponding in and out variables across linked shader stages match in name, type, precision, and array dimensions; mismatches result in link-time failures. To avoid implicit matching and specify exact connections, developers use layout qualifiers like layout(location = 0) out vec4 color;, which assigns a fixed attribute location for direct binding. These rules extend to interface blocks, where entire groups of variables must align, supporting modular design while preventing runtime errors. For read-write data beyond simple varyings, shader storage buffer objects (SSBOs) allow large, flexible storage accessed via the buffer qualifier. Declared as:
buffer DataBuffer {
    vec4 positions[];
};
SSBOs support dynamic indexing and are bound to shader inputs using layout(binding = 0) buffer DataBuffer { ... };, enabling compute-like operations in graphics shaders with memory qualifiers such as coherent for synchronization across invocations. Atomic counters, declared as atomic_uint counter;, provide thread-safe increment/decrement operations for tasks like counter-based rasterization, often bound with layout(binding = 1, offset = 0) uniform atomic_uint counter;. These interfaces are essential for advanced techniques requiring shared mutable state. Texture and image interfaces handle access to GPU textures and images through opaque types like samplers and images. Sampler variables, such as uniform sampler2D tex;, are bound for read-only sampling with functions like texture(tex, coord), and require layout(binding = 0) uniform sampler2D tex; for explicit resource assignment. Image variables enable direct read-write access, declared as layout(binding = 0, rgba32f) uniform image2D img;, supporting operations like imageStore(img, coord, value) with format qualifiers to match the underlying texture format. These interfaces, combined with memory qualifiers like readonly or writeonly, ensure safe and performant interaction with GPU memory.

Language Syntax and Semantics

Data Types and Variables

The OpenGL Shading Language (GLSL) provides a range of fundamental data types to support shader computations, including scalars, , matrices, structures, arrays, and opaque types. These types are designed to facilitate efficient and matrix operations common in graphics processing, with strict rules for declaration and usage to ensure portability across implementations. Scalar types in GLSL form the building blocks for more complex structures. The language includes float, a 32-bit single-precision floating-point type; double, a 64-bit double-precision floating-point type introduced in GLSL 4.00; int, a 32-bit signed ; uint, a 32-bit unsigned ; and bool, which holds true or false values. These scalars cannot be directly modified in size and are used for basic arithmetic and logical operations. Vector types extend scalars to multi-component forms, enabling compact representation of spatial like positions or colors. Available vectors include vec2, vec3, and vec4 for floating-point; dvec2, dvec3, and dvec4 for double-precision floating-point; ivec2, ivec3, and ivec4 for signed integers; uvec2, uvec3, and uvec4 for unsigned integers; and bvec2, bvec3, bvec4 for booleans. Each has 2 to 4 components, accessible via swizzling selectors such as .xyzw for positions or .rgba for colors, allowing selection or reordering like position.xyz to extract the first three components. Matrix types support linear operations essential for transformations. GLSL defines square matrices mat2, mat3, and mat4 for single-precision floating-point, along with double-precision counterparts dmat2, dmat3, and dmat4; non-square variants like mat2x3 specify rows by columns. Matrices are stored in column-major order by default, and constructors such as mat4(1.0) create matrices by filling the diagonal with the scalar argument. Structures allow users to define custom aggregate types for organizing related data. Declared with the struct keyword, such as struct [Light](/page/Light) { vec3 pos; [float](/page/Float) intensity; };, structures support nesting but prohibit member methods or const qualifiers on fields. Members are accessed via dot notation, like [light](/page/Light).pos, promoting code readability for complex entities like lights or materials. Arrays provide collections of elements of the same type, useful for fixed datasets. Fixed-size arrays are declared as [float](/page/Float) a[4];, while unsized arrays like [float](/page/Float) a[]; can be used in contexts such as uniforms where size is inferred at . Initialization occurs via initializer lists, for example, [float](/page/Float) a[] = [float](/page/Float)[](1.0, 2.0, 3.0);, which implicitly sizes the array to match the list length. Opaque types represent resources that cannot be directly manipulated, serving as handles to external data. These include samplers like sampler2D, sampler3D, and samplerCube for access; image types such as image2D for read-write operations; and atomic_uint for counters. Opaque types lack constructors, cannot have members, and are used solely through built-in functions for operations like sampling or atomic increments. Variables in GLSL are declared with their type followed by an identifier, optionally including initialization, such as vec3 zero = vec3(0.0);. Scoping confines variables to (shader-wide, outside functions) or local (within functions or compound statements like if blocks) contexts, with redeclarations in the same scope causing errors. Global variables must be explicitly initialized if constant, as uninitialized globals are invalid; local variables, if uninitialized, hold undefined values. Constructors and swizzling enhance type flexibility without altering core definitions. Constructors build instances by matching argument types and counts, as in vec4 color = vec4(position, 1.0); where a vec3 is extended with a scalar. Swizzling enables component manipulation, such as assigning vec4 fragColor = vec4(textureColor.rgb, 1.0); to append an alpha channel. These features apply uniformly across vectors, matrices, and arrays but are unavailable for opaque types or structures (which use explicit member constructors).

Operators and Expressions

The OpenGL Shading Language (GLSL) supports a range of operators for constructing expressions, drawing from C-like syntax while accommodating and types central to programming. These operators enable arithmetic computations, comparisons, logical decisions, bitwise manipulations, and assignments, all of which operate component-wise on and unless otherwise specified. Expressions must resolve to compatible types, with implicit conversions handled through constructors rather than casts. Arithmetic operators include addition (+), subtraction (-), multiplication (*), and division (/). For scalars, these perform standard numerical operations. On vectors, they apply component-wise; for example, vec3 a = vec3(1.0, 2.0, 3.0); vec3 b = vec3(4.0, 5.0, 6.0); vec3 c = a + b; yields vec3(5.0, 7.0, 9.0). When mixing a scalar with a vector or matrix, the scalar is replicated across all components. Matrix multiplication (mat * vec or mat * mat) follows linear algebra rules, treating the right operand as a column vector or matrix, while other operations remain component-wise. Division by zero results in undefined behavior. Swizzling allows flexible component access in expressions, such as float sum = a.x + a.y; to add specific vector elements. Relational operators (==, !=, <, >, <=, >=) compare values and return results. The equality operators (==, !=) work on scalars, vectors, and matrices, performing component-wise comparisons and yielding a scalar bool for scalars or a bvec of matching for aggregates; for instance, bvec3 result = vec3(1.0) == vec3(1.0); produces bvec3(true, true, true). The operators (<, >, <=, >=) apply only to scalar integers and floats, returning a scalar bool. Logical operators include (!), (&&), and disjunction (||). The unary ! inverts a scalar bool or operates component-wise on a bvec. The binary && and || evaluate scalar bool s with short-circuiting—the right is skipped if the left determines the outcome—and return a scalar bool; they do not apply directly to bvec without component-wise treatment via other means. Bitwise operators are available for integer types: unary complement (~), binary AND (&), OR (|), XOR (^), left shift (<<), and right shift (>>). These operate component-wise on scalar int or uint, or on ivec/uvec vectors; a scalar operand replicates across vector components. For example, ivec3 d = ivec3(1, 2, 3) & ivec3(4, 5, 6); results in ivec3(0, 0, 2). Shifts on signed integers use arithmetic shifting for right operations. These operators are unavailable for floating-point or boolean types. Assignment operators facilitate value updates: simple assignment (=) and compounds like +=, -=, *=, /=, %=, <<=, >>=, &=, |=, ^=. They assign or modify the left-hand side (an l-value ), with operations component-wise for vectors and matrices. The right-hand side must match the left's type or be convertible. For instance, vec3 v = vec3(0.0); v += vec3(1.0); increments each component by 1.0. exist in pre- and post-forms but apply only to scalar l-values, not vectors or matrices. Vector and matrix expressions emphasize component-wise efficiency, with swizzling (e.g., .xy, .z) enabling selective access, replication, or reordering in any operator context, provided l-value swizzles avoid duplicates. Operations like dot product (dot(v1, v2)) and cross product (cross(v1, v2) for vec3) are provided as built-in functions rather than operators, as are utilities like length and normalize. Operator precedence ensures unambiguous evaluation, with parentheses overriding as needed. Most operators associate left-to-right; unary, ternary (?:), and assignment operators associate right-to-left. The table below lists precedence levels from highest to lowest:
LevelOperatorsAssociativity
1()N/A
2[], function calls, . (swizzle/structure), postfix ++ --Left-to-right
3Prefix ++ --, unary + - ! ~Right-to-left
4* / %Left-to-right
5+ -Left-to-right
6<< >>Left-to-right
7< > <= >=Left-to-right
8== !=Left-to-right
9&Left-to-right
10^Left-to-right
11``
12&&Left-to-right
13^^Left-to-right
14`
15?:Right-to-left
16=, +=, -=, *=, /=, %=, <<=, >>=, &=, ^=, `=`
17,Left-to-right
The (condition ? expr1 : expr2) selects between expr1 and expr2 based on scalar bool condition, short-circuiting the unevaluated branch. The selected expressions must yield matching or convertible types, supporting scalars, vectors, or matrices; for example, vec3 color = (intensity > 0.5) ? vec3(1.0) : vec3(0.0);. GLSL lacks explicit type casting syntax; conversions rely on constructor functions. For instance, float f = float(5); converts 5 to 5.0, applying component-wise to vectors like vec3 vf = vec3(float(ivec3(1,2,3)));. Implicit conversions occur in compatible contexts, such as narrower integers to wider or to , but explicit constructors ensure precision.

Control Flow and Functions

The OpenGL Shading Language (GLSL) provides a set of statements to manage the execution path within shaders, including selection, , and jump mechanisms, which enable conditional logic and looping while adhering to the constraints of GPU execution. These structures ensure that shaders can handle dynamic decisions without introducing unsupported features like , promoting efficient compilation to hardware instructions. Selection statements in GLSL consist of if-else constructs and switch statements. The if statement evaluates a scalar boolean expression and executes the associated statement if true, with an optional else clause for the alternative path; nested conditionals are permitted, but the condition must resolve to a scalar bool without introducing new scopes in the expression itself. Switch statements operate on scalar integer expressions (int or uint), using constant integer case labels and an optional default label; fall-through between cases is allowed if statements separate labels, and break statements are used to exit early, ensuring controlled branching in scenarios like multi-case processing. Iteration statements support standard looping patterns: for loops initialize (optionally declaring a variable since GLSL 4.00), test a bool , and update after each iteration, with the loop body scoped to the end of the construct; while loops repeatedly execute a while a bool holds; and do-while loops execute the body at least once before checking the . Loop variable declarations within the for initializer are a feature introduced in GLSL 4.00, limiting scope to the and aiding local variable management. Non-terminating loops are permitted but yield implementation-dependent behavior, reflecting GPU hardware limitations. Jump statements facilitate early exits and continuations: continue transfers control to the next iteration in enclosing for, while, or do-while loops; break exits the nearest enclosing or switch; discard terminates fragment shader execution prematurely, discarding the fragment output (valid only in fragment shaders and undefined in non-uniform flow); and exits the current , optionally providing a value matching the return type for non-void functions. User-defined functions in GLSL promote code modularity, declared with a return type, name, parameter list, and body, where functions must be defined before their first use or forward-declared via prototypes. Parameter qualifiers include in (default, read-only input passed by value), out (write-only output), inout (read-write), and const (immutable input, often combined as const in to prevent modification); arrays as parameters require explicit sizing. Overloading is supported based on distinct parameter type lists or counts, allowing polymorphic behavior such as scalar and variants of the same name. is explicitly disallowed, including static recursion, to align with the GLSL memory model and hardware constraints that preclude stack-based calls. The entry point for every shader is the void main() function, which takes no parameters and implicitly returns void, serving as the starting point for shader execution with optional early returns. Subroutines, introduced in GLSL 4.00, enable runtime selection among function sets via subroutine uniforms, declared with a subroutine type (e.g., subroutine vec4 ColorFunc) and implemented functions assignable at runtime, supporting dynamic behavior like selectable shading models while requiring layout qualifiers for indexing. Dynamic indexing in and is restricted for safety in execution: indices for blocks or buffers must be dynamically (constant across invocations) to prevent execution , though runtime indices are allowed for general arrays with potential performance implications. For example, a simple overloaded pair might appear as:
glsl
float computeValue(float x) {
    return x * 2.0;
}

vec3 computeValue(vec3 v) {
    return v * 2.0;
}
This allows calling computeValue with either scalar or arguments, resolved by matching. Similarly, a subroutine setup could define:
glsl
subroutine vec4 MixFunc(vec4 a, vec4 b, [float](/page/Float) t);
subroutine uniform MixFunc [mixer](/page/Mixer);

layout([index](/page/Index) = 0) subroutine(MixFunc) vec4 linearMix(vec4 a, vec4 b, [float](/page/Float) t) {
    return [mix](/page/Mix)(a, b, t);
}
Enabling binding of to linearMix or other implementations.

Preprocessing and Built-ins

Preprocessor Directives

The OpenGL Shading Language (GLSL) preprocessor processes directives to enable conditional compilation, macro expansion, and other compile-time controls before the shader source is parsed into tokens. This subset of the C preprocessor allows developers to manage version-specific features, extensions, and reusable definitions, ensuring portability across implementations. The preprocessing occurs after handling line continuations (using backslash \) and removing comments, but before tokenization of the GLSL code. Directives must appear on their own lines and are terminated by a newline; they are not expanded within macro definitions or other directives to prevent nesting issues. The #version directive must be the first non-comment, non-whitespace in the source and specifies the GLSL and optional . Its syntax is #version number [profile], where number is an like 460 for GLSL 4.60, and profile can be core, compatibility, or es (the latter for systems, available from 100). Omitting #version defaults to 110 in desktop or 100 in ; for 150 and above, the default is core. This directive defines predefined macros such as __VERSION__ (set to the specified number), GL_core_profile, GL_compatibility_profile, or GL_es_profile based on the . For example:
#version 460 core
This enables core profile features for GLSL 4.60. The directive cannot be redefined or undefine, and its absence in higher versions may lead to errors. The #define directive creates macros for code reuse, supporting both object-like and function-like forms. Object-like macros replace an identifier with a token sequence, such as #define PI 3.14159, while function-like macros include parameters, like #define max(a,b) ((a)>(b)?(a):(b)). Macro names starting with __ or GL_ are reserved, and predefined macros include __LINE__ (current line number), __FILE__ (current source string number), and __VERSION__. Function-like macros support token pasting with ## to concatenate tokens (e.g., #define PASTE(a,b) a##b), but stringification with # is not supported, unlike the full . Macros are expanded in place during preprocessing, but directives do not nest within macro expansions. Macro names must support at least 1024 significant initial characters. The #undef directive removes a previously defined macro, using the syntax #undef identifier. It cannot undefine predefined macros like __LINE__ and must not target undefined identifiers, though implementations may ignore such attempts silently. This allows conditional removal of macros to avoid conflicts in complex shaders. Conditional directives control which parts of the source are included in the final preprocessing output: #if for constant expressions, #ifdef and #ifndef to check if an identifier is defined, #elif for alternatives, #else for , and #endif to close blocks. The defined(identifier) tests macro existence within expressions, which are limited to constants and operators like +, -, &&, || (no constants or floating-point). For instance:
#ifdef GL_core_profile
#define USE_CORE_FEATURES 1
#endif
These support nested conditionals but evaluate strictly at compile time using host processor rules. The #error directive halts compilation and emits a user-defined , with syntax #error "message". It is useful for enforcing prerequisites, such as #error "Version 460 required", and the message follows the same token rules as other directives. The #pragma directive provides implementation-specific controls and is ignored if unrecognized. Standard forms include #pragma STDGL invariant all for handling, and #pragma optimize(on/off) or #pragma debug(on/off) for optimization and flags. No macro expansion occurs within #pragma tokens. The #line directive adjusts line numbers for error reporting and , with syntax #line [integer](/page/Integer) [ "string" ], where the sets the current line (defaulting to 1 if omitted) and the optional sets the source file name. For example, #line 100 "custom.glsl" reports errors as if from line 100 in that file. The must be a literal, and the directive updates subsequent lines accordingly. The #extension directive manages OpenGL extensions, using syntax #extension extension_name : behavior or #extension all : behavior, where behaviors are require (error if unavailable), enable (use if available), warn (warn if unavailable), or disable (ignore even if available). By default, all extensions are disabled except those required by the specified version. For example, #extension [GL_ARB_gpu_shader5](/page/OpenGL) : enable activates the extension if supported. Directives are processed in source order, and redefinitions may override prior settings. Overall, GLSL preprocessing follows a linear pass through the source, expanding macros and evaluating conditionals sequentially without or advanced C features like variadic macros. This design ensures deterministic behavior across GPU drivers while limiting complexity for hardware constraints.

Built-in Variables and Constants

The Shading Language (GLSL) includes a collection of built-in variables that grant s access to essential pipeline state and stage-specific information, such as positions, fragment coordinates, and invocation identifiers, without requiring user declaration. These variables are predefined with specific types and directions (input or output), varying by shader stage, and facilitate seamless integration with the rendering process. Additionally, GLSL defines built-in constants that expose hardware and implementation limits, enabling shaders to adapt to available resources. In modern GLSL versions, such as 4.60, many legacy built-in uniforms have been deprecated in favor of explicit user-defined uniforms to promote flexibility and portability. In vertex shaders, key built-in variables include gl_Position, an output vec4 representing the vertex position in clip-space coordinates, which is essential for subsequent transformation stages. Another is gl_PointSize, an output float that specifies the rasterized size of points in pixels when rendering point primitives. The gl_ClipDistance array, an output float[] with a size up to gl_MaxClipDistances, provides per-vertex distances for clipping against user-defined planes. Input variables common to vertex shaders encompass gl_VertexID, an int denoting the index of the current vertex, and gl_InstanceID, an int indicating the instance number in instanced rendering. Fragment shaders feature built-in variables such as gl_FragCoord, an input vec4 containing the window coordinates (x, y, z, 1/w) of the fragment. The gl_FrontFacing input bool determines whether the fragment belongs to a front-facing primitive, aiding in two-sided lighting or culling decisions. As an output, gl_FragDepth is a float that allows manual override of the fragment's depth value, which can be redeclared with layout qualifiers like layout(depth_any) for flexible depth testing. The gl_NumSamples input int reports the number of samples per fragment in multisampled rendering contexts. Certain built-in variables are shared or specific to advanced shader stages. For instance, gl_NumWorkGroups, an input uvec3 in compute shaders, specifies the total dimensions of work groups dispatched by the application. In compute shaders, gl_WorkGroupID (input uvec3) identifies the current , while gl_LocalInvocationID (input uvec3) gives the local index of the invocation within that group. Tessellation control shaders output gl_TessLevelOuter as a float[4] array for edge tessellation levels and use gl_in as an input array of gl_PerVertex blocks from the previous stage. Geometry shaders similarly employ gl_in as an input array for vertices from the input primitive. Earlier GLSL versions provided built-in uniform matrices for fixed-function state access, such as gl_ModelViewMatrix (uniform mat4) for the combined model-view and gl_ProjectionMatrix (uniform mat4) for perspective ; however, these are deprecated in core profiles beyond version 1.30 and must be replaced by application-provided s in contemporary usage. Built-in constants, available as read-only const int values across all shader stages, include gl_MaxVertexAttribs, which indicates the maximum number of generic vertex attributes supported (minimum 16), and gl_MaxTextureImageUnits, denoting the maximum number of texture image units (minimum 16); these limits can be queried via the API with functions like glGetIntegerv but are directly usable in shader code for conditional logic. The invariant qualifier can be applied to output variables or expressions in vertex, tessellation, and geometry shaders to enforce consistent evaluation across multiple invocations or linked programs, preventing discrepancies due to compiler optimizations or precision differences; for example, declaring invariant vec4 gl_Position; ensures reliable position outputs for pipeline compatibility. This qualifier must be explicitly stated at global scope, matches across stages to avoid linking errors, and supports redeclaration in built-in blocks like out gl_PerVertex { invariant vec4 gl_Position; }; but cannot apply to inputs, uniforms, or local variables.

Built-in Functions

The OpenGL Shading Language (GLSL) provides a comprehensive library of built-in functions that enable s to perform essential computations without requiring user-defined implementations. These functions are predefined in the language core and are accessible across shader stages, supporting and operations critical for rendering. Most built-in functions are overloaded to accept scalar or vector arguments of type , , or uint (where appropriate), allowing seamless operation on data of varying dimensions such as vec2, vec3, or vec4; however, users cannot overload these built-ins themselves. Mathematical functions form the foundational toolkit for scalar and vector arithmetic, including conversions between angle units, trigonometric evaluations, exponential and logarithmic operations, and common utilities for clamping and mixing values. For angle operations, radians(float degrees) converts degrees to radians, while degrees(float radians) performs the reverse, both overloaded for vector types. Trigonometric functions include sin(float angle), cos(float angle), and tan(float angle) for sine, cosine, and tangent in radians, with overloads like vec3 sin(vec3). Exponential functions encompass pow(float x, float y) for x raised to the power y, exp(float x) for e^x, log(float x) for natural logarithm, and exp2(float x)/log2(float x) for base-2 variants, all supporting vector inputs. Common functions handle absolute values with abs(genType x), sign extraction via sign(genType x) (returning -1, 0, or 1), flooring to the largest integer less than or equal to x with floor(genType x), ceiling with ceil(genType x), fractional part extraction using fract(genType x), modulus via mod(genType x, float y), minima and maxima through min(genType x, genType y) and max(genType x, genType y), clamping between bounds with clamp(genType x, genType minVal, genType maxVal), linear interpolation by mix(genType x, genType y, genType a) (where a is typically 0 to 1), step function step(edge, x) (1 if x >= edge, else 0), and smoothstep smoothstep(low, high, x) for Hermite interpolation between 0 and 1. Here, genType denotes compatible scalar or vector types for float, int, or uint. Geometric functions support vector algebra essential for 3D computations, such as computing lengths, distances, and transformations. length(genType x) returns the Euclidean length of a vector, overloaded for vec2 through vec4. distance(genType p0, genType p1) calculates the distance between two points, similarly overloaded. The dot product is obtained via dot(genType x, genType y), yielding a scalar for vectors of matching dimension. cross(genType x, genType y) computes the cross product for vec3 inputs, returning a perpendicular vec3. Normalization uses normalize(genType x) to produce a unit-length vector. Additional functions include faceforward(genType N, genType I, genType Nref) for reflecting incident vector I around normal Nref (with sign based on dot product), reflect(I, N) for reflection of incident vector I over normal N, and refract(I, N, eta) for refraction using Snell's law with index eta. These are primarily for float vectors but extend to compatible types where defined. Matrix functions facilitate operations on matrix types like mat2, mat3, and mat4, focusing on component-wise and linear tasks. matrixCompMult(matNxN x, matNxN y) performs element-wise multiplication of two of the same size (N=2,3,4). outerProduct(vecN c, vecN r) constructs an NxN from column c and row r via . transpose(matNxN m) returns the of m. For square , determinant(matNxN m) computes the , and inverse(matNxN m) yields the (singular return undefined results). Overloads are specific to matrix dimensions without int/uint variants. Vector relational functions enable component-wise comparisons, returning vectors (bvecN) for use in conditional . lessThan(genType x, genType y) produces a bvec where each component is true if x_i < y_i. Similarly, greaterThan(genType x, genType y) checks x_i > y_i, lessThanEqual and greaterThanEqual for inclusive comparisons, equal for x_i == y_i, notEqual for x_i != y_i, and not for logical negation of bvec inputs. These operate on , , or uint vectors, generating bvec2, bvec3, or bvec4 outputs. any(bvec x) returns true if any component of bvec x is true, while all(bvec x) requires all components to be true; both are overloaded for bvec2 to bvec4. Integer functions, introduced in GLSL 4.00 and later, provide bit-level manipulations for and uint types, supporting shaders targeting integer-heavy computations. Bit extraction uses bitfieldExtract(int/uint p, int offset, [int](/page/INT) bits) to return bits [offset, offset+bits-1] as a signed/unsigned . Insertion is via bitfieldInsert(int/uint base, int/uint insert, int offset, [int](/page/INT) bits). bitfieldReverse(uint p) reverses the bits in a uint. Counting functions include findLSB(int/uint x) for the least significant set bit position (or 32/-1 if zero for int/uint), and findMSB(int/uint x) for the most significant bit. Bit counting employs bitCount(int/uint x) to return the number of set bits. These are overloaded for ivecN and uvecN (N=2,3,4), with operations applied component-wise. Texture sampling functions allow access to texture data in fragment and other shaders, with variants for projection, LOD control, and shadow mapping. Core functions include texture(sampler2D sampler, vec2 coord) for 2D bilinear sampling returning vec4, overloaded for sampler3D (vec3 coord), samplerCube (vec3), and others; textureProj variants like textureProj(sampler2D, vec3) handle homogeneous coordinates. texelFetch(sampler2D, ivec2 coord, int lod) fetches exact texels at integer coordinates with LOD. Shadow comparisons use texture(sampler2DShadow, vec3 coord) returning float (0 to 1) for depth comparisons. LOD-specific overloads such as textureLod(sampler2D, vec2, float lod) and textureGrad with explicit gradients ensure precise control in varying environments. All support float vectors for coordinates. Image and atomic functions enable direct read-write access to image memory and thread-safe updates, primarily in compute and fragment shaders with appropriate qualifiers. Image loads occur via imageLoad(image2D image, ivec2 P) returning vec4 (or ivec4/uvec4 for signed/unsigned images), overloaded for 1D, , cube, , and buffer types. Stores use imageStore(image2D image, ivec2 P, vec4 data). Atomic operations on atomic_uint counters include atomicAdd(atomic_uint mem, uint data) returning the prior value, plus atomicExchange, atomicMin, atomicMax, atomicAnd, atomicOr, atomicXor, and atomicCompSwap(mem, uint compare, uint data). Atomic counters support atomicCounterIncrement(atomic_uint c) and atomicCounterDecrement(atomic_uint c), both returning the value before/after the operation. Overloads align with image and counter dimensions. Geometry functions are available in geometry shaders to construct output primitives dynamically. EmitVertex() outputs the current vertex to the primitive assembly stream. EndPrimitive() completes the current primitive and starts a new one. For multi-stream support, EmitStreamVertex(uint stream) emits to a specific stream (0 to max supported), and EndStreamPrimitive(uint stream) ends the primitive in that stream. These functions have no arguments beyond stream index and are not overloaded for types. Fragment functions provide derivatives and interpolation aids for per-fragment computations. dFdx(genType p) and dFdy(genType p) compute partial derivatives with respect to window x and y, overloaded for float vectors. fwidth(genType p) returns the sum of absolute x and y derivatives for anti-aliased operations. interpolateAtCentroid(genType interpolant) retrieves the interpolant value at the centroid, useful in derivatives. These are restricted to fragment shaders and operate on float types. Compute functions ensure synchronization in compute shaders for parallel execution. barrier() halts execution until all invocations in the workgroup reach it, synchronizing memory and control flow. memoryBarrier() synchronizes memory accesses without control flow, while memoryBarrierAtomicCounter() and memoryBarrierBuffer() target specific memory types. groupMemoryBarrier() limits to shared variables within the workgroup. These have no parameters and are void-returning, available only in compute shaders. Subgroup operations like ballot(bool value) (extension-based in core contexts) and readInvocation are noted but core to synchronization primitives.

Compilation and Runtime

Shader Compilation Process

The shader compilation process in the Shading Language (GLSL) transforms into an executable shader object suitable for GPU execution, managed through the . This process applies to individual s, such as or fragment shaders, and occurs independently before any linking. It ensures compliance with GLSL version and profile specifications while handling errors and implementation limits. To initiate compilation, an application first creates an empty shader object using the glCreateShader function, which accepts a shader type parameter such as GL_VERTEX_SHADER or GL_FRAGMENT_SHADER and returns a unique handle for referencing the object. Next, the glShaderSource function specifies the source code by passing an array of strings, along with optional length indicators; multiple strings are concatenated into a single input stream without inserting newlines between them, allowing shaders to be assembled from separate files or modules. The glCompileShader function then triggers the compilation of this source into machine-readable code for the shader object. Compilation proceeds through several phases to validate and optimize the code. It begins with preprocessing, which concatenates the source strings, processes directives like #version and #pragma, removes comments, and handles line numbering (as detailed in the Preprocessor Directives section). This is followed by parsing, which performs lexical and syntactic analysis to validate the code against GLSL's grammar. Semantic analysis then checks types, qualifiers, redeclarations, and interface matching, enforcing rules such as required precision qualifiers in the ES profile. Subsequent optimization improves performance by eliminating redundancies, and code generation produces target-specific instructions, often as GPU intermediate or assembly code. Version and profile enforcement occurs throughout; for instance, the source must start with a #version directive matching the context (e.g., #version 460 for core profile), and mismatches or unsupported features trigger compile-time errors. Error handling is integral to the process, with the application querying the via glGetShaderiv using the GL_COMPILE_STATUS parameter; a value of GL_FALSE indicates failure due to issues like syntax errors, undefined variables, or type mismatches. Detailed diagnostics are retrieved using glGetShaderInfoLog, which provides an implementation-dependent log of errors or warnings to aid . The process supports detached , allowing shaders to be compiled and stored independently for reuse across multiple programs without immediate linking. Implementation limits are validated during and linking, such as maximum identifier lengths (up to 1024 characters) and resource bindings; for example, gl_MaxShaderStorageBufferBindings caps the number of storage buffers, with violations reported as errors. These limits, along with others like uniform storage and array sizes, are queried via constants to ensure compatibility before .

Program Linking and Execution

In OpenGL, the process of linking shader objects into an executable program begins with creating a program object using glCreateProgram, which returns a unique handle for referencing the object. This empty program object serves as a container to which one or more compiled shader objects can be attached via glAttachShader(program, shader), allowing multiple shaders of the same type (e.g., vertex or fragment) to contribute to a single stage. Once attached, glLinkProgram(program) is called to link the shaders, generating executables for the relevant processing stages such as vertex, geometry, tessellation, or fragment based on the attached shader types. After successful linking, the shader objects remain attached to the program object but can be manually detached using glDetachShader if no longer needed, allowing reuse or deletion without affecting the linked executables. During linking, the OpenGL implementation performs several checks to ensure compatibility across shader stages. Interface matching requires that input and output variables between stages align in type, name, precision, and auxiliary qualifiers (e.g., centroid or sample); mismatches, such as differing array dimensions in geometry shader interfaces, result in a link-time error. Uniform variables and blocks must exhibit consistency in declarations, including matching types, names, array sizes, and structure member sequences across shaders in the same program; inconsistencies trigger errors reported via glGetProgramInfoLog(program). Resource binding is also validated, ensuring layout qualifiers for uniforms, buffers, and textures do not exceed limits like GL_MAX_UNIFORM_BLOCK_SIZE or GL_MAX_TEXTURE_IMAGE_UNITS. The link status can be queried with glGetProgram(program, GL_LINK_STATUS), and any errors are detailed in the program's information log. Optional validation with glValidateProgram(program) assesses whether the linked program can execute feasibly given the current state, checking aspects like sampler type mismatches or resource limit violations (e.g., active samplers exceeding available texture units). This step stores a validation status queryable via glGetProgram(program, GL_VALIDATE_STATUS) and populates the info log with implementation-specific details, aiding without affecting . It is recommended for development to catch issues like insufficient attributes or draw buffer configurations before execution. To execute a linked , glUseProgram(program) installs it as the current state for rendering operations, activating the executables for attached shader stages; the program remains active until another is bound or glUseProgram(0) is called to disable shading. For rendering pipelines, the program must include compatible and fragment stages; absence of required stages (e.g., no fragment shader for draw calls) leads to undefined behavior or errors like GL_INVALID_OPERATION. Compute shaders are invoked separately using glDispatchCompute(num_groups_x, num_groups_y, num_groups_z), which launches the specified number of work groups processed by the active compute program, with each group executing invocations independently. Errors such as exceeding GL_MAX_COMPUTE_WORK_GROUP_COUNT generate GL_INVALID_VALUE. Uniform values are set after linking using functions like glUniform1f(location, value) for scalar floats or glUniformMatrix4fv(location, count, transpose, value) for 4x4 matrices, where location is obtained via glGetUniformLocation(program, name). For uniform blocks, glUniformBlockBinding(program, blockIndex, binding) assigns a binding point to an active block (indexed by glGetUniformBlockIndex(program, name)), enabling association with buffer objects. These settings apply only to the active program and are validated against the program's uniform namespace during linking. Resources such as and buffers are bound to access points for use. Textures are assigned to units via glActiveTexture(GL_TEXTURE0 + unit) followed by glBindTexture(GL_TEXTURE_2D, texture), allowing to reference them through sampler variables set to the unit index. Buffers, including or buffers, are bound using glBindBufferBase(GL_UNIFORM_BUFFER, index, buffer) or similar targets, mapping the buffer's to the specified binding index for block access. Mismatches in binding or exceeding limits (e.g., GL_MAX_UNIFORM_BUFFER_BINDINGS) can cause validation failures or runtime errors.

Advanced Features

Qualifiers and Layout Options

In GLSL, qualifiers modify variable declarations to specify , , interpolation behavior, layout, memory access, and invariance, enabling fine-grained over behavior and . These qualifiers are categorized to avoid conflicts, with at most one qualifier per category allowed on a declaration; invalid combinations, such as applying const to an out variable, result in compilation errors. Layout qualifiers, introduced in GLSL 1.40, provide explicit over matching and , while others like and have been core since earlier versions. Storage Qualifiers define how variables are allocated and accessed across shader stages. The const qualifier declares read-only variables initialized at compile time, ensuring they are treated as constants throughout the shader. Deprecated in core profiles but retained in compatibility profiles, attribute specifies per-vertex inputs in vertex shaders, equivalent to in in modern GLSL. Similarly, varying handles data passed from vertex to fragment shaders in older versions, now replaced by out in the emitting stage and in in the receiving stage. Other storage qualifiers include in for inputs from previous pipeline stages, out for outputs to subsequent stages, uniform for read-only values constant across a primitive and set externally, buffer for readable/writable storage in buffer objects, and shared for compute shaders to share data within a workgroup. These apply only to global-scope variables, except const which can also be local. For example, a block might be declared as:
uniform vec3 lightDir;
This ensures lightDir remains constant for all invocations of the . Precision Qualifiers control the numerical of floating-point and integer types, primarily for portability with where hardware may vary. In desktop GLSL, these qualifiers have no semantic effect and default to full (highp), but they are syntactically supported for ES compatibility. In GLSL ES, highp provides the highest , typically single-precision floats (23-bit , range approximately $2^{-126} to $2^{127}) and 32-bit integers; mediump offers medium (at least 10-bit for floats, 16-bit integers); and lowp the lowest (at least 7-bit for floats, 8-bit integers), balancing on resource-constrained devices. Precision defaults to highp for floats and integers in vertex and compute s, but mediump for floats in fragment s unless specified. They can apply to opaque types like samplers as well, but mismatched precisions in expressions promote to the higher one. An example declaration in GLSL ES might be:
precision mediump float;
mediump vec4 color;
This optimizes for fragment processing where full precision may not be needed. Interpolation Qualifiers govern how input values to fragment shaders are interpolated across a . The default smooth performs perspective-correct , accounting for depth in the rasterization process. flat disables , passing the value from the provoking unchanged, which is required for or inputs to avoid precision loss. noperspective applies in screen space without perspective correction, useful for certain post-processing effects. These apply only to in variables in fragment shaders and at most one per variable; they are invalid on outputs or patch variables. For instance:
smooth in vec3 normal;
flat in int materialID;
Here, normal interpolates smoothly while materialID remains constant per primitive. Layout Qualifiers, available since GLSL 1.40, explicitly define interfaces and memory layouts for inputs, outputs, uniforms, and buffers to ensure portability and avoid implicit assignments. location = n assigns a specific input/output location (e.g., vertex attribute index), matching across linked shaders. binding = n sets the uniform buffer or sampler binding point, applying to blocks or individual uniforms/samplers. For buffer blocks, offset = n forces a member’s byte offset, align = n sets minimum alignment, std140 uses standardized padding for uniform blocks (conservative, with vec3 padded to 16 bytes), and std430 applies tighter packing for shader storage blocks (e.g., no padding for scalar arrays). Layout qualifiers cannot apply to function parameters and must match between stages for successful linking. A sample uniform block:
layout(std140, binding = 0) uniform LightBlock {
    vec4 position;
    layout(offset = 16) vec3 color;  // Explicit offset after vec4
};
This ensures predictable memory layout across implementations. Memory Qualifiers manage visibility and access to shared memory in buffers and images, introduced in GLSL 4.20 for compute and graphics shaders. coherent guarantees visibility of writes to other shader invocations using the same memory. volatile indicates potential external modification, preventing optimizations that assume stability. restrict assumes the variable is the only access point to its memory for optimization. For images, readonly forbids writes and writeonly forbids reads, with errors for violations. These combine with storage qualifiers like buffer or apply to image variables, but not to ordinary variables. Example for a shader storage buffer:
layout(std430, binding = 1) coherent [buffer](/page/Buffer) Data {
    writeonly float values[];
};
This allows thread-safe writes with visibility guarantees. The invariant qualifier ensures that output values from one stage match expectations in subsequent stages by requiring identical computations, mitigating discrepancies from optimizations or reordering. It applies only to out variables and their matching in counterparts, such as invariant out vec3 normal;, and is particularly useful for values used in computations like lighting. Invariant declarations must be consistent across all in a program.

Extensions and Compatibility Profiles

Extensions in the OpenGL Shading Language (GLSL) allow developers to access hardware-specific or advanced features beyond the core specification, enabling behaviors such as requiring support for an extension or warning on its absence. The #extension directive controls this, with syntax #extension <extension_name> : <behavior>, where behaviors include require (compilation fails if unsupported), enable (activates if available, otherwise ignores), warn (enables but issues warnings on use if unsupported), and disable (blocks usage even if supported). By default, all extensions are disabled (#extension all : disable), and directives must follow the #version statement, with later ones overriding earlier. Several key extensions have shaped GLSL's evolution. The GL_ARB_gpu_shader5 extension, introduced with GLSL 4.00, adds support for advanced integer operations, functions like bitfieldExtract, 64-bit integers, and enhanced gathering functions such as gather. Similarly, GL_ARB_compute_shader, aligned with GLSL 4.30, introduces compute shaders for general-purpose GPU computing, including shared variables within workgroups and built-ins like gl_NumWorkGroups for dispatching computations. More recently, GL_EXT_mesh_shader enables task and mesh shader stages for efficient , replacing traditional and shaders with a more flexible pipeline; this extension saw a resurgence in adoption for gaming applications in 2025, driven by performance needs in real-time rendering. GLSL operates under different profiles to balance modernity and legacy support. The core profile, default for GLSL versions 1.50 and higher, removes deprecated features to promote programmable pipelines, excluding fixed-function variables such as gl_ModelViewProjectionMatrix (unavailable after version 1.20 in core contexts post-OpenGL 3.30) and legacy keywords like attribute and varying (replaced by in and out). In contrast, the compatibility profile retains these elements for , allowing use of built-ins like gl_FogFragCoord and functions such as texture2D, though it is optional for implementations and unavailable in SPIR-V targets. Deprecated fixed-function variables, including those tied to the legacy like gl_LightSource, are entirely removed in core profiles to enforce explicit shader-based control. The GLSL ES variant, used in and , differs significantly from desktop GLSL to suit embedded systems. Earlier versions of GLSL ES lack double-precision floating-point support; it was introduced in GLSL ES 3.10 for 3.1 and later. It mandates precision qualifiers (lowp, mediump, highp) with enforced semantics rather than hints, and provides a reduced set of built-in functions, omitting advanced features like explicit multi-sampled textures. 1.0 aligns with GLSL ES 1.00 (based on 2.0), while 2.0 uses GLSL ES 3.00 ( 3.0), restricting shaders to these subsets for web compatibility. Integration with SPIR-V, a binary intermediate language, allows GLSL 4.60 and later to compile into SPIR-V modules primarily for , using tools like glslangValidator; supports loading these via glShaderBinary with the GL_ARB_gl_spirv extension since OpenGL 4.6, though direct support remains absent as of 2025, favoring extension-based adoption. For advanced resource handling, the GL_ARB_bindless_texture extension provides opaque handles for textures and samplers, enabling dynamic without fixed points and supporting up to billions of s via 64-bit integers in shaders. Developers query extension support at runtime using API calls, such as glGetString(GL_EXTENSIONS) to retrieve a space-separated list, though modern implementations prefer glGetIntegerv(GL_NUM_EXTENSIONS, &count) followed by glGetStringi(GL_EXTENSIONS, i) for indexed access, avoiding deprecated behaviors in core profiles. Limits like maximum texture size are queried via glGetIntegerv(GL_MAX_TEXTURE_SIZE, &value).

References

  1. [1]
    [PDF] The OpenGL® Shading Language, Version 4.60.8 - Khronos Registry
    ... OpenGL Shading Language (GLSL). It requires. __VERSION__ ... Specification for the es profile is specified in The OpenGL ES Shading Language specification.
  2. [2]
    History of OpenGL
    Feb 13, 2022 · Overview. OpenGL was first created as an open and reproducable alternative to Iris GL which had been the proprietary graphics API on Silicon ...
  3. [3]
    [PDF] The OpenGL ES® Shading Language, Version 3.20.8
    This document specifies only version 3.20 of the OpenGL ES Shading Language (GLSL ES). ... • Clarified that this specification completely defines the OpenGL ES ...
  4. [4]
    The OpenGL® Shading Language, Version 4.60.8 - Khronos Registry
    Aug 14, 2023 · These languages are used to create shaders for each of the programmable processors contained in the API's processing pipeline.
  5. [5]
    [PDF] OpenGL Shading Language Course Chapter 2 – GLSL Basics By ...
    GLSL itself is a C-like language, which borrows features from C++. ... Also, the language does not allow any pointer types or any kind of pointer arithmetic.
  6. [6]
    [PDF] GLSLangSpec.1.10.pdf - Khronos Registry
    Apr 30, 2004 · The OpenGL Shading Language is actually two closely related languages. These languages are used to create shaders for the programmable ...
  7. [7]
    [PDF] GLSLangSpec.1.20.pdf - Khronos Registry
    Sep 7, 2006 · This specification is based on the work of those who contributed to version 1.10 of the OpenGL Language. Specification, the OpenGL ES 2.0 ...
  8. [8]
    [PDF] GLSLangSpec.1.30.pdf - Khronos Registry
    Nov 22, 2009 · This document describes The OpenGL Shading Language, version 1.30. Independent compilation units written in this language are called shaders.<|control11|><|separator|>
  9. [9]
    [PDF] GLSLangSpec.3.30.pdf - Khronos Registry
    Mar 11, 2010 · This document specifies only version 3.30 of the OpenGL Shading Language. It requires __VERSION__ to substitute 330, and requires #version ...
  10. [10]
    [PDF] The OpenGL Shading Language - Khronos Registry
    Jul 24, 2010 · The OpenGL Graphics System Specification will specify the OpenGL entry points used to manipulate and communicate with programs and shaders. 1.4.
  11. [11]
    ARB_tessellation_shader - Khronos Registry
    Approved by the Khronos Board of Promoters on March 10, 2010. Version Last Modified Date: September 17, 2019 Revision: 23 Number ARB Extension #91 Dependencies ...
  12. [12]
    [PDF] The OpenGL Shading Language 4.5 - Khronos Registry
    May 9, 2017 · The Language Specification for the es profile is specified in The OpenGL ES Shading Language specification. Shaders for the core or ...
  13. [13]
    mesh shader - Khronos Registry
    Sep 17, 2018 · This extension provides a new mechanism allowing applications to use two new programmable shader types -- the task and mesh shader -- to ...
  14. [14]
    OpenGL Resurges in 2025 with Mesh Shader Extensions for Gaming
    Oct 28, 2025 · OpenGL is experiencing a resurgence in October 2025 with new extensions like GL_EXT_mesh_shader, driven by niche demands from gaming and ...
  15. [15]
    [PDF] The OpenGL Shading Language 4.3 - Khronos Registry
    Feb 7, 2013 · This document specifies only version 4.30 of the OpenGL Shading Language. It requires __VERSION__ to substitute 430, and requires #version ...
  16. [16]
    [PDF] OpenGL 4.6 (Core Profile) - May 5, 2022 - Khronos Registry
    May 1, 2025 · This specification has been created under the Khronos Intellectual Property Rights. Policy, which is Attachment A of the Khronos Group ...
  17. [17]
    Core Language (GLSL) - OpenGL Wiki - The Khronos Group
    Feb 14, 2024 · Functions in GLSL use a calling convention called "value-return." This means that values passed to functions are copied into parameters when ...
  18. [18]
  19. [19]
  20. [20]
  21. [21]
  22. [22]
  23. [23]
  24. [24]
  25. [25]
  26. [26]
  27. [27]
  28. [28]
  29. [29]
  30. [30]
  31. [31]
  32. [32]
  33. [33]
  34. [34]
  35. [35]
  36. [36]
  37. [37]
  38. [38]
  39. [39]
  40. [40]
    glCreateShader - OpenGL 4 Reference Pages - Khronos Registry
    glCreateShader creates an empty shader object and returns a non-zero value by which it can be referenced. A shader object is used to maintain the source code ...
  41. [41]
    glCompileShader - OpenGL 4 Reference Pages - Khronos Registry
    glCompileShader compiles the source code strings that have been stored in the shader object specified by shader.
  42. [42]
    glCreateProgram - OpenGL 4 Reference Pages - Khronos Registry
    ... linking the program object with glLinkProgram. These executables are made part of current state when glUseProgram is called. Program objects can be deleted ...<|control11|><|separator|>
  43. [43]
    glAttachShader - OpenGL 4 Reference Pages
    ### Summary of glAttachShader
  44. [44]
    glLinkProgram - OpenGL 4 Reference Pages - Khronos Registry
    glLinkProgram links the program object specified by program. If any shader objects of type GL_VERTEX_SHADER are attached to program, they will be used to ...
  45. [45]
    glValidateProgram - OpenGL 4 Reference Pages - Khronos Registry
    glValidateProgram checks to see whether the executables contained in program can execute given the current OpenGL state.
  46. [46]
    glUseProgram - OpenGL 4 Reference Pages - Khronos Registry
    ... linking the program object with glLinkProgram. A program object will contain an executable that will run on the vertex processor if it contains one or more ...
  47. [47]
    glDispatchCompute - OpenGL 4 Reference Pages - Khronos Registry
    glDispatchCompute launches one or more compute work groups. Each work group is processed by the active program object for the compute shader stage.
  48. [48]
    glUniform - OpenGL 4 Reference Pages - Khronos Registry
    glUniform modifies the value of a uniform variable or a uniform variable array. The location of the uniform variable to be modified is specified by location.
  49. [49]
    glUniformBlockBinding - OpenGL 4 Reference Pages
    Binding points for active uniform blocks are assigned using glUniformBlockBinding. Each of a program's active uniform blocks has a corresponding uniform buffer ...
  50. [50]
    glActiveTexture - OpenGL 4 Reference Pages
    ### Summary of glActiveTexture and glBindTexture for Texture Binding
  51. [51]
    glBindBufferBase - OpenGL 4 Reference Pages
    ### Summary of glBindBufferBase
  52. [52]
    [PDF] The OpenGL ES Shading Language - Khronos Registry
    Jan 29, 2016 · This specification is derived from OpenGL GLSL 3.3 revision 7. 1.1.1 Changes from GLSL ES 3.0 revision 5. •. Matching of row_major and ...
  53. [53]
    ARB_gpu_shader5 - Khronos Registry
    OpenGL 3.2 and GLSL 1.50 are required. This extension interacts with ARB_gpu_shader_fp64. This extension interacts with NV_gpu_shader5. This extension interacts ...Missing: GL_ARB_compute_shader GL_ARB_mesh_shader
  54. [54]
    ARB_compute_shader - Khronos Registry
    ... glDispatchCompute(5, 5, 5); the triangles will be processed by programA and programB, while the compute dispatch will be processed by programC. Similarly ...
  55. [55]
    ARB_bindless_texture - Khronos Registry
    Images are special data types used in the OpenGL Shading Language to identify a level of a texture to be read or written using image load, store, and atomic ...