How Does #version in GLSL Dictate Syntax and Features?

The #version directive is the mandatory first line in OpenGL Shading Language (GLSL) source code that instructs the shader compiler which language specification to enforce. By specifying a version number and an optional profile, #version controls the availability of modern shader stages, built-in variables, keywords, data structures, and texture sampling functions. Omitting or misconfiguring this directive forces compilers to default to legacy specifications, fundamentally altering how inputs, outputs, and hardware capabilities are parsed.

Core Mechanics and Syntax of the Directive

Every GLSL shader source must declare #version before any other tokens or comments, except for whitespace and comments depending on compiler tolerance. The general syntax follows:

#version number [profile]

If no profile is explicitly defined on desktop targets above GLSL 1.50, the compiler defaults to the core profile. If the #version directive is entirely omitted from a shader, the GLSL compiler falls back to version 110 (OpenGL 2.0), triggering strict legacy parsing rules.

Evolution of Variable Qualifiers Across Versions

The declared #version drastically changes how data enters and exits shader stages.

Legacy Qualifiers (GLSL 1.10 to 1.20)

In GLSL 1.10 and 1.20, data passing relies on fixed-function and stage-specific qualifiers:

Modern Unified Qualifiers (GLSL 1.30 and Above)

Starting with GLSL 1.30 (OpenGL 3.0), the language deprecated stage-specific data qualifiers in favor of unified in/out syntax:

Memory Layouts, Buffers, and Feature Unlocks

Modern GPU features are gated directly behind specific #version targets:

Impact of Profile Modifiers

The optional profile argument defines whether deprecated legacy functions remain accessible:

Texture Sampling Function Overhauls

The #version declaration alters built-in sampling functions. Prior to GLSL 1.30, sampling functions were explicitly named after their texture types:

// GLSL 1.20
vec4 color = texture2D(u_Sampler, uv);
vec4 cube  = textureCube(u_CubeMap, dir);

In GLSL 1.30 and later, these overloaded functions were unified into a single polymorphic identifier:

// GLSL 330 core / 460 core
vec4 color = texture(u_Sampler, uv);
vec4 cube  = texture(u_CubeMap, dir);

Compiling overloaded texture() calls under #version 120 results in a compilation failure, just as invoking texture2D() in modern core profiles triggers deprecation or unsupported symbol errors.