Language Overview
An introduction to BWSL syntax and core concepts.
Pressure-test the syntax
Take the concept from this page into the playground and deliberately break a pass, binding, or type signature to see how the compiler responds.
Try a Live EditBWSL (Brawl Shading Language) is designed to be familiar to developers who know C-style languages while providing modern features for shader development.
Core Concepts
Pipelines
Every BWSL program starts with a pipeline definition. A pipeline represents a complete rendering setup that can contain multiple passes.
pipeline MyPipeline {
// Pipeline contents
}
Passes
Passes represent individual render passes within a pipeline. Each pass contains vertex and fragment shader code.
pass "MainPass" {
vertex { /* ... */ }
fragment { /* ... */ }
}
Attributes
Attributes define the vertex input data for your shader:
attributes {
position: float3
normal: float3
texcoord: float2
color: float4
}
Resources
Resources are shader-visible values declared directly in the pipeline and provided by the host at runtime:
resources {
modelViewMatrix: mat4
projectionMatrix: mat4
normalMatrix: mat3
time: float
sceneColor: texture2D
sceneSampler: sampler
}
Modules
Modules organize reusable shader functions, constants, structs, and enums. They are file-scope declarations, so they can live in separate .bwsl files or next to pipelines in the same source file. Import them with import, optionally give them a local alias with as, and access members through Module::member:
import PBR as BRDF
using Material = BRDF::PBRMaterial
shade :: (Material mat, float3 normal) -> float3 {
return BRDF::lambertianDiffuse(mat.albedo) * saturate(normal.y);
}
Use using ModuleName when an already imported module should also contribute functions and constants to unqualified lookup in the current scope.
Use submodule Name extends ParentModule when a separate file or declaration should contribute more members to an existing module namespace; import the parent module to use those members.
Type System
BWSL provides a strong type system with the following primitive types:
| Type | Description |
|---|---|
float | 32-bit floating point |
float2 | 2-component float vector |
float3 | 3-component float vector |
float4 | 4-component float vector |
int | 32-bit signed integer |
uint | 32-bit unsigned integer |
bool | Boolean value |
mat2 | 2x2 float matrix |
mat3 | 3x3 float matrix |
mat4 | 4x4 float matrix |
BWSL matrix source spelling is mat2, mat3, and mat4. Backend names like float2x2, float3x3, and float4x4 are not supported aliases.
Resource types are declared in resources {} blocks:
| Type | Description |
|---|---|
texture2D | 2D texture resource |
texture3D | 3D texture resource |
textureCube | Cube texture resource |
texture2DArray | 2D texture array resource |
sampler | Sampler state used with texture resources |
buffer<T> | Structured buffer resource |
cbuffer<T> | Constant-buffer-style resource |
Custom struct resource payloads can be local structs or module-defined structs. In a resources {} block, module-defined structs use Module::Type spelling after importing the module (the older Module.Type form is deprecated).
Specification Draft
The BWSL source repo includes a canonical spec draft in docs/spec/ and a BNF grammar in docs/spec/bwsl.bnf. Use the public docs for authoring guidance, and use the source-tree spec when you need exact grammar or conformance details for compiler and editor tooling.
Variables and Constants
// Local variables
float x = 1.0;
float3 color = float3(1.0, 0.5, 0.0);
// Constants
const float PI = 3.14159265359;
const float3 UP = float3(0.0, 1.0, 0.0);
Constants are folded into the code that uses them and do not appear in the compiled output. See Constants.
Literals
| Literal | Type | Examples |
|---|---|---|
| Decimal integer | int | 42, -7, 010 (ten: leading zeros stay decimal) |
| Unsigned integer | uint | 42u, 0xFFu |
| Hexadecimal / binary | int (or uint with u) | 0x1F, 0b1010 |
| Float | float | 1.0, 10., 0.25, 1e3, 2.5e-4, 1.0f |
| Boolean | bool | true, false |
Integer literals must fit in 32 bits, and float literals must be finite float values. An out-of-range literal or an empty prefix such as 0x is a compile error.
Trailing commas are accepted in parameter lists, call arguments, array initializers, use lists, enum payloads, workgroup sizes and switch case lists.
Comments are // line and /* block */. Block comments nest, so you can comment out code that already contains one.
Names and Keywords
Identifiers are ASCII letters, digits and _, and cannot start with a digit.
- Duplicates are errors. A name can be declared only once per scope, and struct names must be unique. Nested blocks, stages and switch cases are separate scopes.
- Shadowing is allowed, with a note. Declaring a local, parameter or loop variable that hides an outer variable, constant or struct field compiles, but the compiler emits a non-blocking note that points at both declarations.
- Intrinsic names are reserved for functions and methods. See Functions.
- Keywords can't be used as names. Naming a variable, parameter, field or iterator after a keyword reports, for example, 'inputs' is a keyword and cannot be used as a variable name.
The reserved words are:
as attributes bool break buffer by case cbuffer compute compute_function compute_graph conflict const constraint continue default discard dmat2 dmat3 dmat4 double double2 double3 double4 else enum eval extend extends false float float2 float3 float4 for foreach fragment fragment_function if import in inputs int int2 int3 int4 int64 int64x2 int64x3 int64x4 is loop mat2 mat3 mat4 module node null out outputs pass pass_block pipeline readonly readwrite require resources return rules sampler self shader shared skip slot struct submodule switch texture2D texture2DArray texture3D textureCube true uint uint2 uint3 uint4 uint64 uint64x2 uint64x3 uint64x4 until use using variants vertex vertex_function where while writeonly
Some of these, such as double, int64 and where, are reserved for future use and are not supported types or syntax yet.
Control Flow
BWSL supports standard control flow statements:
// Conditionals
if (condition) {
// ...
} else {
// ...
}
// Loops
for (int i = 0; i < 10; i++) {
// ...
}
// Early returns
if (alpha < 0.01) {
discard; // Fragment shader only
}
switch is also available, with one important difference from C: cases never fall through. See Control Flow for switch, conditional returns and discard, and Loops for every loop form.
Swizzling
Vector components can be accessed and rearranged using swizzle notation:
float4 color = float4(1.0, 0.5, 0.3, 1.0);
float3 rgb = color.rgb; // (1.0, 0.5, 0.3)
float2 rg = color.rg; // (1.0, 0.5)
float r = color.r; // 1.0
// Rearranging
float3 bgr = color.bgr; // (0.3, 0.5, 1.0)
float4 rrra = color.rrra; // (1.0, 1.0, 1.0, 1.0)
You can use either rgba or xyzw notation for swizzling, but not both in one swizzle: color.xg is an error. A component past the end of the vector, such as .z on a float2, is also an error. Swizzles work on any vector expression, e.g. float2(0.25, 0.75).xyxy.
Next Steps
- Learn about The Pipeline in detail
- Organize reusable code with Modules
- Understand Types and type conversion
- Explore built-in Intrinsics
- Check the Language Specification for exact grammar and conformance references