How the GL_ES Macro Differentiates GLSL and GLSL ES

This article explains how the predefined GL_ES preprocessor macro enables developers to distinguish between OpenGL ES Shading Language (GLSL ES) and desktop OpenGL Shading Language (GLSL). It explores the technical definition of the macro, why it is necessary for cross-platform rendering pipelines, and how to use it effectively to handle differences in precision qualifiers, version declarations, and language feature sets.

The Purpose and Definition of GL_ES

The GL_ES macro is a built-in preprocessor definition defined by the GLSL compiler. According to the OpenGL ES Shading Language specification, every compliant GLSL ES implementation must predefine GL_ES with an integer value of 1.

Conversely, standard desktop GLSL implementations (for desktop OpenGL) do not define GL_ES. By querying whether this macro exists at compile time, a single shader source file can determine the underlying rendering API and conditionally adapt its syntax and features.

#ifdef GL_ES
    // Code compiled specifically for OpenGL ES / WebGL
#else
    // Code compiled for desktop OpenGL
#endif

Key Differences Resolved by GL_ES

1. Precision Qualifiers

The most common use case for GL_ES is handling precision qualifiers (lowp, mediump, highp). In GLSL ES, fragment shaders do not have a default precision for floating-point types, meaning compilation fails unless a default precision is explicitly set.

Desktop GLSL historically did not support precision qualifiers (prior to GLSL 1.30, and even then they are often ignored). Attempting to declare default precision in older desktop contexts can cause syntax errors. Using GL_ES solves this discrepancy:

#ifdef GL_ES
precision mediump float;
precision mediump int;
#endif

2. Cross-Platform Shader Sharing (Mobile, Web, Desktop)

Because WebGL is based on OpenGL ES (WebGL 1.0 uses GLSL ES 1.00; WebGL 2.0 uses GLSL ES 3.00), the GL_ES macro is always defined in WebGL shaders. Developers targeting mobile devices (Android, iOS via Metal translation layers), the web, and desktop platforms (Windows, Linux, macOS) can maintain unified shader files instead of managing separate forks for desktop and mobile runtimes.

3. Handling Differing Built-in Functions and Extensions

Desktop OpenGL exposes features that are either unavailable or available only via extensions in OpenGL ES, such as certain texture sampling modes, geometry shaders, or arbitrary buffer access. Conditional compilation driven by #ifdef GL_ES allows the shader to provide fallback algorithms or lower-complexity approximations on embedded devices while taking advantage of advanced hardware on desktop GPUs.

Preprocessor Ordering and Caveats

When using GL_ES, the placement relative to the #version directive is critical:

  • The #version directive must always appear as the very first non-whitespace, non-comment line of the shader string.
  • You cannot use #ifdef GL_ES to conditionally switch the #version directive itself, because #version must be evaluated before the preprocessor processes any conditional logic.
  • In typical multi-target workflows, the host application injects the correct #version string programmatically before passing the shader source to glShaderSource(). Once the compiler receives the source with an ES version string (such as #version 300 es), it automatically defines GL_ES 1 for the rest of the compilation process.