Skip to content

GLSL Coding Style

While Blender uses auto-formatting (clang-format), this page covers aspects of code style which aren't automated. Please make sure that your IDE is running auto-formatting on .glsl files or that you manually run it.

Note: There is a lot of existing code that does not follow the rules below yet. Don't do global replacements without talking to a maintainer beforehand.

There are only two important rules:

  • When making changes, always follow the style of this documentation. The code style of the existing code-base is so fragmented that it is better to always follow the new conventions.
  • Strive for clarity, even if that means occasionally breaking the guidelines. Use your head and ask for advice if your common sense seems to disagree with the conventions.

As a starting point, you should follow the C and C++ Style Guide even in GLSL files. This present documentation will only list what is different from the C and C++ guide.

Files

  • Vertex, fragment, geometry and compute shaders file should have respectively _vert, _frag, _geom and _comp suffix (ex: eevee_film_frag.glsl).
  • Shader files name must be unique and must be prefixed by the module they are part of (ex: workbench_material_lib.glsl, eevee_film_lib.glsl).
  • A shader file must contain one and only one main() function.
  • If a shader file does not contain a main() function it is considered as a shader library and must have _lib suffix in its filename.
  • Put code shared between multiple shaders inside library files.

Naming

  • Use descriptive names for global variables and functions.
  • Naming should follow the snake_case convention. The only exception is type names (ex: ViewMatrices).
  • Given that GLSL has only a global namespace, prefix all function inside a _lib.glsl with the library name (ex: curvature_soft_clamp in workbench_curvature_lib.glsl).
  • Use variable names with common words at the beginning, and specifics as suffixes. Sort them alphabetically.
  • Do not use reversed keywords like sampler.
/* Don't: */
fg_dof_coc_tile;
bg_dof_coc_tile;
dof_color_tile;
/* Do: */
dof_tile_coc_fg;
dof_tile_coc_bg;
dof_tile_color;

Texture Coordinates

Prefer using either uv or coord for normalized texture coordinates (ex: textureLod(tex, uv, 0.0)).

Always use texel for integer pixel coordinates (ex: texelFetch(tex, texel, 0)).

Shading Variables

Some common variables used in shading code are shortened by a single uppercase letter.

  • P for Position.
  • N for surface shading Normal.
  • Ng for surface geometric Normal.
  • T for surface Tangent.
  • B for surface BiTangent or curve BiNormal.
  • L for Light direction from the shading point.
  • V for View vector (also named I) from the shading point towards the camera.

All of them, with the exception of P, are expected to be unit vectors.

Space Prefix

These can have a prefix telling the space they are supposed to be in:

  • no prefix is assumed to be World space.
  • v for View space (ex: vV for view space view vector).
  • t for Tangent space (ex: tN for tangent space normal vector).
  • l for Local object space (ex: lP for local position).

Note that local position is most often read from a vertex input and name pos instead of lP.

For other variable, we use a two character prefix (ex: ws_ray_dir for world space ray direction):

  • ws_ for World Space.
  • vs_ for View Space.
  • ls_ for Local object Space.
  • hs_ for Homogenous Space (also known as Clip Coordinates).

NDC stands for Normalized Device Coordinate space (which is the homogeneous coordinates after perspecitve division) and is a special case which uses the ndc_ prefix.

Value Literals

  • float (no f suffix):
    use: 0.3, 1.0
    not: .3, 1.f
  • uint (always have u suffix):
    use: 0xFFu, 0u
    not: 0xFF, uint(0)

Vector Constructors

  • Vectors use same type of argument in multi-scalar constructors:
    use: vec2(2.0, 0.0)
    not: vec2(2, 0.0)
  • Matrices use either all scalar constructor or multi-column constructor:
    use: mat2(vec2(0.0), vec2(0.0)) or mat2(0.0, 0.0, 0.0, 0.0);
    not: mat2(vec2(0.0), 0.0, 0.0)

Vector Components

  • Do not use vector array subscript [] unless it is for runtime random access. Use swizzle syntax instead .x, .y, .z and .w.
  • Prefer using .xyzw set of swizzle. .rgba can be used when it make sense.
  • Do not use the .stpq set of swizzle. They are not available on Metal.

Comparison

  • Do not use direct vector comparison (ex: my_vec == vec2(1.0)) use is_equal(a, b) or is_zero(a).
  • Do not use comparison between different types (ex: float(1) == int(1), uint(1) == int(1)) use explicit cast instead.

Types

  • Use GLSL vector and matrix types even if HLSL/MSL ones are defined (ex: vec2 instead of float2) .

Interface

  • Resource name and input / output variable should follow the snake_case convention.
  • Sampler resource names should have _tx suffix.
  • Image resource names should have _img suffix.
  • Storage and Uniform Buffers resource names should have _buf suffix.
  • Output fragment variable or written resources should have out_.
  • Read & Write resources can have inout_ or in_ prefix if it make sense.

Defines

Use #define and #ifdef only if needed. Optimizing out branches of code can be made using constant boolean and if clauses.

/* Don't: */
#define OPTIMIZE 
#ifndef OPTIMIZE
heavy_computation();
#endif
/* Do: */
const bool optimize = true;
if (!optimize) {
  heavy_computation();
}

A good use case of defines is to remove code sections that need resources that may not be available in some shader configuration.

/* This is ok since `diffuse_tx` can be missing from the create info. */
#ifdef SHADER_VARIATION_TEXTURED
color *= texture(diffuse_tx, uv);
#endif

Driver Differences

Not all drivers are created equal. These rules are written in order to minimize the most common errors when dealing with different GLSL compilers.

  • Avoid putting too much data in global space. Prefer moving large arrays to local function constant variable.
  • Do not rely on implicit cast for int to float or uint to int promotion. Use explicit cast.
/* Don't: */
vec2 uv = gl_FragCoord.xy / textureSize(depth_tx, 0);
ivec2 a = ivec2(1, 4) << 5u;
/* Do: */
vec2 uv = gl_FragCoord.xy / vec2(textureSize(depth_tx, 0));
uvec2 a = uvec2(1, 4) << 5u;
  • Avoid multi-line preprocessor directive like #if. Prefer breaking into multiple statements. Multi-line #define seems to cause no issues.
  • Do not use builtin function name as variable name (e.g.: distance, length, ...)
  • The const keyword is only allowed on compile time constant expression. Some driver do not consider function calls as constant expression even if all their parameters are.
  • The discard keyword needs to be manually followed by a return to avoid undefined behavior on Metal.
  • If fragment shader is writing to gl_FragDepth, usage must be correctly defined in the shader's create info using .depth_write(DepthWrite).
  • Image type opaque variables (ex: image2D) are not allowed to be function parameters (because of differences in the decorators required). Transform the function into a macro, or access the image as a global variable.
  • bool is not supported as shared type (because of Metal compatibility). Use int or uint instead.
  • Vector component cannot be used as target of atomic operations (because of Metal compatibility). Use separate int or uint instead and convert to a vector when needed.

Shader File Structure

The structure of a shader file should follow this order:

/* ### Leave a blank line at the top. This avoid issue when concatenating the shader files together. */

/**
 * Short description of what the shader does, or what the library contains.
 *
 * If complex enough or part of a bigger pipeline, describe in more detail
 * and describe inputs & outputs.
 **/

/* ### Required dependencies. */
#pragma BLENDER_REQUIRE(common_math_lib.glsl)

/* ### Constants. */
const vec2 const1 = vec2(0.0);

/* ### Local Util functions. */
vec4 local_functions(vec4 color) {
  /* ... */
}

/* ### Main function (entry point). */
void main(void) {
  /* ... */
}

Shared Shader Files

These files contains data structures and small functions shared between GPU and CPU code. They have .h or .hh extensions.

  • Use the supported C or C++ syntax subset depending on file extensions. (TODO link to documentation)
  • Use blender's floatX, intX, uintX, boolX vectors and float4x4 matrix types instead of vecX, ivecX, uvecX, bvecX and mat4.

Packing Rules

Shared structures should follow std140 and std430 packing rules depending if the struct is used in a Uniform Buffer or a Storage Buffer. Member alignment is currently not error checked so be sure to follow the rules. Here is a list of the rules:

  • Do not use float3x3.
  • Do not use arrays of scalars (ex: float array[16]) inside struct used by Uniform Buffers.
  • Use packed_float3 instead of float3, and always follow it by a single scalar.
  • Use bool1 instead of bool.
  • Align float2, int2, uint2 and bool2 to 8 bytes.
  • Align float3, int3, uint3 and bool3 to 16 bytes.
  • Align float4, int4, uint4 and bool4 to 16 bytes.
  • Align all structures to 16 bytes.
  • Remember that float, int, uint and bool1 are 4 bytes long.

Metal Shading Language Compatibility

Our Metal Backend takes adds some modifications to the GLSL sources to make them MSL compatible. This mean 2 things:

  • Not all of the GLSL syntaxes are supported. It isn't a goal of our backend to have 100% GLSL syntax compatible.
  • It might make some MSL syntax valid inside the GLSL if the Metal backend enabled. Always check if the shaders compile with other backends using compile-time shader compilation.

Vulkan Shading Language Compatibility

  • Vulkan doesn't allow using reserved keyword as parameters or variable names.
  • Vulkan doesn't allow using parameters or variable names that are the same as resource names.
  • After a function body it is not allowed to add a semicolon
  • Vulkan doesn't support stage interfaces using an instance name and different interpolation modes. When using instance names each struct can only have attribute sharing the same interpolation mode (Interpolation::SMOOTH, Intepolation::FLAT or Interpolation::NO_PERSPECTIVE). The solution is to add a stage interface per interpolation mode.

Validation

On all platforms it is possible to validate Cross compilation to Vulkan for static shaders. It is highly recommended to test if GLSL compiles on Vulkan before creating a pull request.

This can be done by enabling - WITH_VULKAN_BACKEND=On - WITH_GPU_BUILDTIME_SHADER_BUILDER=On

Both options are marked as advanced in CCMake.

When compiling Blender it should now validate the static GLSL shaders.

[2174/2177] Generating shader_baked.hh
Shader Test compilation result: 767 / 767 passed (skipped 10 for compatibility reasons)
OpenGL backend shader compilation succeeded.
Shader Test compilation result: 712 / 712 passed (skipped 65 for compatibility reasons)
Vulkan backend shader compilation succeeded.