Style Guide/GLSL
Coding Style (GLSL)
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,_geomand_compsuffix (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_libsuffix 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_caseconvention. The only exception is type names (ex:ViewMatrices). - Given that GLSL has only a global namespace, prefix all function inside a
_lib.glslwith the library name (ex:curvature_soft_clampinworkbench_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;
Shading Variables
Some common variables used in shading code are shortened by a single uppercase letter.
Pfor Position.Nfor surface shading Normal.Ngfor surface geometric Normal.Tfor surface Tangent.Bfor surface BiTangent or curve BiNormal.Lfor Light direction from the shading point.Vfor View vector (also namedI) 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.
vfor View space (ex:vVfor view space view vector).tfor Tangent space (ex:tNfor tangent space normal vector).lfor Local object space (ex:lPfor 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,.zand.w. - Prefer using
.xyzwset of swizzle..rgbacan be used when it make sense. `Do not` use the.stpqset of swizzle. They are not available on Metal.
Comparison
- Do not use direct vector comparison (ex:
my_vec == vec2(1.0)) useis_equal(a, b)oris_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:
vec2instead offloat2) .
Interface
- Resource name and input / output variable should follow the
snake_caseconvention. - Sampler resource names should have
_txsuffix. - Image resource names should have
_imgsuffix. - Storage and Uniform Buffers resource names should have
_bufsuffix. - Output fragment variable or written resources should have
out_. - Read & Write resources can have
inout_orin_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 minimise 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
inttofloatoruinttointpromotion. 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#defineseems to cause no issues. - Do not use builtin function name as variable name (e.g.:
distance,length, ...) - The
constkeyword 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
discardkeyword needs to be manually followed by areturnto 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.
boolis not supported assharedtype (because of Metal compatibility). Useintoruintinstead.- Vector component cannot be used as target of atomic operations (because of Metal compatibility). Use separate
intoruintinstead 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) {
/* ... */
}
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,boolXvectors andfloat4x4matrix types instead ofvecX,ivecX,uvecX,bvecXandmat4.
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_float3instead offloat3, and always follow it by a single scalar. - Use
bool1instead ofbool. - Align
float2,int2,uint2andbool2to 8 bytes. - Align
float3,int3,uint3andbool3to 16 bytes. - Align
float4,int4,uint4andbool4to 16 bytes. - Align all structures to 16 bytes.
- Remember that
float,int,uintandbool1are 4 bytes long.
Metal Shading Language Compatiblity
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::FLATorInterpolation::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.