How Did GLSL Replace Attribute and Varying with In and Out?

When the OpenGL Shading Language (GLSL) introduced version 1.30 alongside OpenGL 3.0, it fundamentally changed how data flows between pipeline stages by deprecating the legacy attribute and varying qualifiers in favor of unified in and out keywords. This architectural shift modernized shader design by removing stage-specific terminology, establishing a consistent syntax across all shader types, and laying the groundwork for complex programmable pipelines that include geometry, tessellation, and compute stages.

The Legacy Model: Stage-Specific Keywords

In early versions of GLSL (such as GLSL 1.10 and 1.20), data routing across the rendering pipeline depended heavily on fixed naming conventions:

While straightforward for fixed two-stage pipelines (vertex to fragment), this keyword structure became rigid and confusing as OpenGL evolved.

The Unified in and out Interface

With the release of GLSL 1.30 (and later GLSL ES 3.00 for embedded systems), the language adopted a stage-agnostic directional model. Instead of relying on specialized keywords tied to specific stages, variables are defined strictly by their direction of flow relative to the current shader execution.

// Legacy GLSL (1.20)
// Vertex Shader:
attribute vec3 aPos;
varying vec2 vTexCoord;
void main() {
    vTexCoord = aPos.xy;
    gl_Position = vec4(aPos, 1.0);
}

// Fragment Shader:
varying vec2 vTexCoord;
void main() {
    gl_FragColor = vec4(vTexCoord, 0.0, 1.0);
}
// Modern GLSL (1.30+ / Core Profile)
// Vertex Shader:
#version 330 core
layout(location = 0) in vec3 aPos;
out vec2 vTexCoord;
void main() {
    vTexCoord = aPos.xy;
    gl_Position = vec4(aPos, 1.0);
}

// Fragment Shader:
#version 330 core
in vec2 vTexCoord;
out vec4 FragColor;
void main() {
    FragColor = vec4(vTexCoord, 0.0, 1.0);
}

Key Architectural Advantages

The transition brought several technical improvements to the graphics programming workflow:

  1. Pipeline Extensibility: As OpenGL added geometry, tessellation control, tessellation evaluation, and compute shaders, a directional model scaled seamlessly. An out variable from any prior stage automatically maps to a matching in variable in the subsequent stage without requiring new language keywords.
  2. Explicit Fragment Outputs: Replacing built-in globals like gl_FragColor with explicit out variables simplified Multiple Render Targets (MRT), allowing developers to output to specific framebuffer attachments cleanly.
  3. Layout Qualifiers: The unified model integrates directly with explicit layout bindings (such as layout(location = 0)), reducing the need for CPU-side reflection queries like glGetAttribLocation or glGetFragDataLocation.
  4. Interface Blocks: The syntax facilitated the addition of uniform blocks and named interface blocks (e.g., in VertexData { vec2 uv; } inData;), streamlining the sharing of structured datasets between stages.

By standardizing inputs and outputs as directional interfaces rather than stage-specific declarations, GLSL achieved a cleaner, more modular architecture that aligns with modern GPU hardware pipelines and continues to define modern shader standards.