Language5 min read

Language Overview

An introduction to BWSL syntax and core concepts.

Reading Time
5 min
Word Count
834
Sections
14
Try It Live

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 Edit

BWSL (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.

bwsl
pipeline MyPipeline {
// Pipeline contents
}

Passes

Passes represent individual render passes within a pipeline. Each pass contains vertex and fragment shader code.

bwsl
pass "MainPass" {
vertex { /* ... */ }
fragment { /* ... */ }
}

Attributes

Attributes define the vertex input data for your shader:

bwsl
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:

bwsl
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:

bwsl
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:

TypeDescription
float32-bit floating point
float22-component float vector
float33-component float vector
float44-component float vector
int32-bit signed integer
uint32-bit unsigned integer
boolBoolean value
mat22x2 float matrix
mat33x3 float matrix
mat44x4 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:

TypeDescription
texture2D2D texture resource
texture3D3D texture resource
textureCubeCube texture resource
texture2DArray2D texture array resource
samplerSampler 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

bwsl
// 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

LiteralTypeExamples
Decimal integerint42, -7, 010 (ten: leading zeros stay decimal)
Unsigned integeruint42u, 0xFFu
Hexadecimal / binaryint (or uint with u)0x1F, 0b1010
Floatfloat1.0, 10., 0.25, 1e3, 2.5e-4, 1.0f
Booleanbooltrue, 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:

bwsl
// 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:

bwsl
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