18.03.2025

What is preprocessing in GLSL?

Preprocessing in GLSL (OpenGL Shading Language) is a mechanism for processing shader code before compilation. It allows you to manage shader code using macros, conditional constructs, and extension inclusion. This is particularly useful for optimization, cross-platform compatibility, and flexible shader configuration.

In this article, we will explore how preprocessing works in GLSL, which directives are available, and where it can be applied.

Main Preprocessor Directives

The GLSL preprocessor works on the principle of text substitution: it replaces or removes parts of the code before the shader is compiled. Let's take a look at the main directives available in GLSL.

1. #version – Specifying the GLSL Version

Each shader must begin with a version declaration:

#version 450

This line tells the compiler which GLSL version to use. If the version is not specified, the compiler may default to an outdated standard that lacks modern features.

2. #define – Macros

Allows defining constants or replacing expressions before compilation.

#define PI 3.14159265359

float getCircleArea(float radius) {
return PI * radius * radius;
}

It is also possible to use #define to create "pseudo-functions":

#define SQR(x) ((x) * (x))

float lengthSquared(vec3 v) {
return SQR(v.x) + SQR(v.y) + SQR(v.z);
}

3. #undef – Removing a Macro

If you need to cancel a previously declared #define, you can use #undef:

#define USE_TEXTURE
#undef USE_TEXTURE

After #undef USE_TEXTURE, the code will no longer recognize USE_TEXTURE as defined.

4. #ifdef, #ifndef, #endif – Conditional Compilation

Allows including or excluding parts of the code depending on whether a macro is defined.

#define USE_LIGHTING

#ifdef USE_LIGHTING
uniform vec3 lightColor;
#endif

If USE_LIGHTING is defined, the lightColor variable will be declared. If not, this code is ignored.

Similarly, #ifndef works in the opposite way—the block of code is compiled only if the macro is NOT defined:

#ifndef DEBUG_MODE
// Code that executes only if DEBUG_MODE is not enabled
#endif

5. #if, #elif, #else, #endif – Conditional Statements Based on Values

Allows checking macro values and modifying code based on them.

#define QUALITY 2

#if QUALITY == 1
vec3 color = vec3(0.5);
#elif QUALITY == 2
vec3 color = vec3(1.0);
#else
vec3 color = vec3(0.0);
#endif

If QUALITY == 1, one version of the code will be used; if QUALITY == 2, another version; otherwise, a third version.

6. #error – Triggering a Compilation Error

If you need to halt compilation with an error message, use #error:

#ifndef MAX_LIGHTS
#error "Macro MAX_LIGHTS is not defined!"
#endif

The compiler will output "Macro MAX_LIGHTS is not defined!" if it has not been declared.

7. #pragma – Compiler Directives

This directive is used to control compiler behavior but is rarely seen in standard GLSL. An example from OpenGL ES:

#pragma optimize(on)

8. #extension – Enabling Extensions

Some OpenGL features require enabling extensions. This is done using #extension:

#extension GL_ARB_shader_image_load_store : enable

Syntax:

#extension <extension_name> : <mode>

Modes:

Where is Preprocessing Used?

Preprocessing in GLSL is used in various scenarios:

Performance Optimization

Unnecessary effects can be disabled depending on hardware capabilities or player settings.

Different Shader Configurations

For example, you can change the number of light sources or rendering quality without modifying the main code.

Cross-Platform Development

Different GPUs and OpenGL versions may support different features. Using #ifdef, you can write universal shaders.

Debugging Convenience

You can add debug macros to enable and disable additional checks.

#define DEBUG_MODE

#ifdef DEBUG_MODE
vec3 debugColor = vec3(1.0, 0.0, 0.0); // Red color for debugging
#endif

Conclusion

Preprocessing in GLSL is a powerful tool that allows for flexible shader code management. With it, you can: