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
inworkbench_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 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.
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 perspective 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))
ormat2(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)
) 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:
vec2
instead offloat2
) .
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_
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 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
tofloat
oruint
toint
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 areturn
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 asshared
type (because of Metal compatibility). Useint
oruint
instead.- Vector component cannot be used as target of atomic operations
(because of Metal compatibility). Use separate
int
oruint
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 andfloat4x4
matrix types instead ofvecX
,ivecX
,uvecX
,bvecX
andmat4
.
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 offloat3
, and always follow it by a single scalar. - Use
bool1
instead ofbool
. - Align
float2
,int2
,uint2
andbool2
to 8 bytes. - Align
float3
,int3
,uint3
andbool3
to 16 bytes. - Align
float4
,int4
,uint4
andbool4
to 16 bytes. - Align all structures to 16 bytes.
- Remember that
float
,int
,uint
andbool1
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
orInterpolation::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.