Skip to content

Principal Differences between Blender BSL and C++

This document is an overview of how Blender Shading Language (BSL) differs from standard C++ (C++14), focusing only on the most important semantic and syntactic deviations.

Refer to the BSL specification for more details.


1. Purpose and Compilation Model

BSL is not a general-purpose language.

  • BSL code is transpiled, not compiled to machine code.
  • It targets GPU shading languages (GLSL 4.3, MSL 2.3).
  • A single source must be valid C++ and valid BSL.
  • Shader variants are expressed through language constructs, not preprocessor macros.

2. Unsupported or Removed C++ Features

BSL removes many core C++ features that are incompatible with GPU execution:

  • Pointers of any kind
  • Dynamic memory (new, delete)
  • Virtual functions, inheritance, polymorphism
  • Exceptions, RTTI (typeid, dynamic_cast)
  • Lambdas
  • Operator overloading
  • Iterators
  • Constructors and destructors
  • goto
  • noexcept
  • extern, thread_local, register

Classes are closer to plain data containers than C++ objects.


3. Restricted Control Flow

  • All control-flow statements must use braces ({}), even for single statements.
  • for loops may be annotated with [[unroll]] or [[unroll_n(N)]].
  • break and continue are forbidden inside unrolled loops.
  • Additionally, GPU-specific uniform vs non-uniform control flow rules apply, similar to GLSL.

4. Strong Reliance on Attributes

BSL heavily uses C++-style attributes ([[...]]) for semantics that do not exist in C++:

  • Shader stages: [[vertex]], [[fragment]], [[compute]]
  • Resource bindings: [[uniform]], [[storage]], [[sampler]], [[image]]
  • Pipeline specialization: [[compilation_constant]], [[specialization_constant]]
  • Control flow: [[static_branch]], [[unroll]]

Attributes are semantically mandatory, not just annotations.


5. Entry Points and main

  • The global function main is reserved and forbidden.
  • Shader entry points are ordinary functions annotated with stage attributes.
  • Entry point functions:

    • Must return void
    • Cannot be overloaded
    • Require all parameters to be explicitly annotated ([[in]], [[out]], built-ins, or resource tables)

6. Types: GPU-Oriented, Not C++-Oriented

Scalars

  • int, uint, float are always 32-bit.
  • double exists only as a literal, not as a type.
  • bool may be 1-bit; bool32_t is required for buffer layouts.

Vectors and Matrices

  • Built-in vector types (float3, int4, etc.) and matrix types (float4x4).
  • Column-major matrices only.
  • No user-defined operator overloading.

Strings

  • string_t is not a runtime string.
  • Stored as a 32-bit hash.
  • Immutable, comparable only via built-in functions.

7. References Are Not Real References

  • References may behave like copies in function arguments.
  • Aliasing the same variable through multiple references in a function call is undefined behavior.
  • Local references are syntactic sugar and are expanded by the compiler. Definitions cannot contain side effects.

This is very different from C++ reference semantics.


8. Templates: Heavily Constrained

  • No default template arguments.
  • All instantiations must be explicit and in the same file.
  • Template argument deduction is forced if all arguments are present in the function parameters (implemented as overload).
  • Explicit template arguments are needed if function overload is not possible.

Templates exist mainly for code generation, not abstraction.


9. Namespaces: Limited Visibility Rules

  • using namespace X; is forbidden.
  • Namespace elision only works in the immediate definition scope.
  • Symbols must be imported explicitly with using X::Y.
  • Only symbols from the same namespace can be imported at namespace scope.

10. Classes and Structs

  • No inheritance or nesting.
  • No forward declarations.
  • No constructors or operators.
  • Methods must be defined inline in the class.

Host-Shared Classes

  • [[host_shared]] enforces strict CPU/GPU layout compatibility.
  • Layout follows std140 / std430-like rules.
  • Members must be padded and 16-byte aligned.

This is far stricter than standard C++ layout rules.


11. Resource Management via Shader Resource Tables (SRT)

Instead of pointers, references, or descriptors:

  • Resources are grouped into Shader Resource Tables.
  • Resources are accessed structurally, not dynamically.
  • Conditional presence of resources is supported via compile-time conditions.

This has no direct C++ equivalent.


12. Preprocessor Differences

  • Excepted #include and #pragma once, all directives are left untouched during compilation and will be copied as-is into the compatible-language source. This is to allow runtime evaluation of theses directive (e.g. switch implementation based on system config).
  • Likewise, macro calls will be considered as regular tokens and are not expanded during the compilation step.
  • For these two reasons, usage of preprocessor directives is strongly discouraged.
  • #pragma once is mandatory for headers.
  • #include uses prepend semantics, not textual replacement.
  • #include will not respect conditionals (e.g. #if)