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]
  • Version Number: A three-digit integer matching the GLSL version (such as 120, 330, 430, or 460). From OpenGL 3.3 onward, GLSL version numbers mirror the OpenGL core specification versions directly (e.g., GLSL 4.50 corresponds to OpenGL 4.5).
  • Profile Identifier: Introduced in GLSL 1.50, the profile flag specifies the context target: core, compatibility, or es (for OpenGL ES and WebGL2).

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:

  • attribute: Used exclusively in vertex shaders for per-vertex attributes.
  • varying: Used to pass interpolated data from vertex shaders to fragment shaders.
  • uniform: Shared constant data across all stages.
  • Built-in outputs such as gl_FragColor and gl_Position handle direct pipeline communication.

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:

  • in replaces attribute in vertex shaders and receives interpolated data in fragment shaders.
  • out writes data from vertex stages and defines user-declared render targets in fragment shaders.
  • gl_FragColor is deprecated in 1.30 and completely removed in 3.30 core, requiring developers to declare explicit out vec4 fragColor targets.

Memory Layouts, Buffers, and Feature Unlocks

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

  • Uniform Buffer Objects (UBOs): Available starting in GLSL 1.40/3.30 using the uniform BlockName { ... }; syntax to share large uniform blocks across programs.
  • Explicit Layout Qualifiers: GLSL 3.30 and 4.00 introduce layout(location = 0) to bind vertex attribute indices and render target locations directly in shader code, removing the need for runtime API queries.
  • Shader Storage Buffer Objects (SSBOs) & Compute Shaders: Declaring #version 430 unlocks the compute pipeline (layout(local_size_x = ...) in;) and read-write buffer blocks for massive data manipulation.
  • Bindless and SPIR-V Capabilities: Versions 450 and 460 enable modern Vulkan-compatible semantics, enhanced memory qualifiers, and explicit binding models.

Impact of Profile Modifiers

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

  • Core Profile (core): Strips all deprecated features, including fixed-function state variables (gl_ModelViewMatrix, gl_Vertex), legacy lighting states, and implicit texture functions.
  • Compatibility Profile (compatibility): Retains full backward compatibility, allowing legacy variables (gl_FragColor, gl_Normal) to coexist with modern features.
  • Embedded Systems (es): Enforces OpenGL ES specification constraints, mandatory precision qualifiers (precision highp float;), and mobile-optimized feature subsets required for mobile hardware and WebGL2 contexts.

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.