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:
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.
float getCircleArea(float radius) {
return PI * radius * radius;
}
It is also possible to use #define to create "pseudo-functions":
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:
#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.
#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:
// 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.
#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:
#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:
8. #extension – Enabling Extensions
Some OpenGL features require enabling extensions. This is done using #extension:
Syntax:
Modes:
- enable – Enables the extension.
- require – Requires the extension (compilation fails if unavailable).
- disable – Disables the extension.
- warn – Issues a warning when the extension is used.
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.
#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:
- Optimize shaders
- Create adaptive effects
- Make code more readable and convenient