Glslang Preprocessor Macro Injection Using -D

This article provides a technical overview of how glslang processes and injects preprocessor macros via the -D command-line argument. It covers the command syntax, the compiler's internal preamble handling, the interaction with GLSL #version constraints, and best practices for configuring shader build pipelines.

The Purpose of the -D Flag

The glslangValidator and glslang command-line tools support the -D option to define preprocessor macros prior to compiling a shader. This mirrors standard C/C++ compiler behavior (such as gcc or clang), allowing developers to compile multiple permutations of a single shader—such as enabling debug modes, setting feature flags, or toggling platform-specific optimizations—without altering the underlying source files.

Syntax and Argument Parsing

The -D switch can be used in two primary forms:

  1. Boolean/Existence Definitions:

    glslangValidator -DENABLE_FOG shader.frag -V -o shader.spv

    When no value is assigned, glslang defines the identifier with a default value of 1. This is equivalent to inserting #define ENABLE_FOG 1.

  2. Key-Value Definitions:

    glslangValidator -DMAX_LIGHTS=8 -DQUALITY_LEVEL=2 shader.frag -V -o shader.spv

    This assigns the specified literal or token to the identifier. Equivalent to #define MAX_LIGHTS 8.

Multiple -D arguments can be chained in a single command. The definitions are evaluated in the order they are passed on the command line.

Internal Handling and the #version Directive

In GLSL, the #version directive must strictly appear before any other non-whitespace, non-comment tokens in the source file. Naively prepending #define statements to the source file would cause standard-compliant GLSL parsers to emit an error.

glslang avoids this issue through internal preamble management:

  • Symbol Table Pre-population: Rather than performing raw text concatenation at the beginning of the file, glslang stores command-line macros in its internal preprocessor symbol table.
  • Preamble Injection: In workflows using the C++ API (glslang::TShader), definitions can be supplied via TShader::setPreamble(). The compiler engine guarantees that injected preamble tokens are processed in the proper preprocessor context without violating the requirement that #version remains the first effective directive in the input source.

Handling HLSL vs. GLSL

When using glslang to compile HLSL via the -D argument, standard C-preprocessor rules apply directly without the strict #version positioning constraints of GLSL. In this mode, injected definitions immediately populate the global macro definition table before parsing the entry point.

Common Caveats

  • Spaces Around Equals Signs: Shell parsers require arguments to remain intact. Commands like -D MAX_LIGHTS = 8 will fail. The syntax must not contain unescaped spaces: -DMAX_LIGHTS=8.

  • String Literals: To define a macro that expands to a string literal, quotes must be escaped so the shell does not strip them:

    glslangValidator -DTEXTURE_NAME=\"diffuse.png\" shader.frag -V
  • Shader-Level #undef: A macro injected through -D can still be undefined inside the shader code using #undef. Compiler injection establishes the initial state, but does not make the identifier immutable during preprocessing.