GLSL #line Directive and Debug Line Numbers

The #line preprocessor directive in the OpenGL Shading Language (GLSL) overrides the compiler's internal tracking of line numbers and source file identifiers. In shader development, programs are frequently constructed by concatenating multiple source strings, injecting defines, or preprocessing code dynamically at runtime. When compilation errors or warnings occur, the compiler reports locations based on its current internal counters; using #line allows developers to remap these diagnostics back to original source files, preventing misaligned line numbers in debug output.

Syntax and Parameters

In GLSL, the #line directive accepts two forms:

#line line_number
#line line_number source_string_number
  • line_number: An integer constant expression that specifies the line number of the line immediately following the directive.
  • source_string_number: An optional integer constant expression representing the index of the source string (analogous to a file identifier).

Once the preprocessor encounters #line, it resets its internal counters. The line immediately following the directive is assigned the specified line_number, and subsequent lines increment from that value until another #line directive is encountered or the file ends.

Impact on Diagnostic Messages

GLSL compiler diagnostics typically follow standard reporting formats to locate syntax errors, semantic errors, and warnings. A common format implemented by GPU drivers is:

source_string_number:line_number: error: message

Under default behavior, a single consolidated shader file starts at line 1 and source string 0. If a host application injects 50 lines of configuration headers before the user's shader code, a syntax error on line 10 of the original file will be reported as line 60.

By placing #line 1 0 immediately after injected headers, the compiler resets the counter:

// Dynamically injected preamble
#define QUALITY_HIGH
#define MAX_LIGHTS 4

// Directive resetting location tracking
#line 1 0

// User source code begins here
void main() {
    float x = ; // Compiler reports error at 0:1, matching user source
}

The error report identifies the problem at line 1 of source 0, precisely aligning the diagnostic output with the original authoring file rather than the concatenated runtime buffer.

Managing Multiple Source Buffers

The OpenGL API function glShaderSource accepts an array of strings to build a single shader module. By default, the driver assigns each array element an increasing source string index (0, 1, 2, etc.).

When using third-party preprocessors or custom virtual file systems that merge external files (such as simulated #include statements), you can assign a unique integer to represent each distinct file. Setting the source_string_number in the #line directive ensures that diagnostics point to both the correct virtual file ID and the corresponding line number within that file.

// Main shader code
#line 1 1 // Switch context to "included_utility.glsl" represented by ID 1
vec3 computeNormal() {
    return vec3(0.0);
}

#line 25 0 // Switch context back to original "main.glsl" (ID 0) at line 25
void main() {
    gl_Position = vec4(1.0);
}

Behavioral Variations and Implementation Considerations

The GLSL specification defines that #line applies to the line following the directive itself. While modern conformant drivers strictly adhere to this rule, minor discrepancies have historically existed across vendor implementations (e.g., whether the line number applies to the directive itself or the next line). In cross-platform rendering pipelines, diagnostic parsers must account for vendor-specific log formatting differences (such as NVIDIA versus AMD versus Mesa syntax) while relying on #line to ensure numerical accuracy across all target platforms.