Functions
Function declaration, shader entry points, generics, and scoping in BWSL.
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 functions use the :: declaration form and support overloading, pass-local helpers, module-qualified calls, stage-returning helpers, and pass-block-returning helpers.
Function Declaration
Functions are declared with typed parameters and an explicit return type after ->:
add :: (int a, int b) -> int {
return a + b;
}
calculateNormal :: (float3 p0, float3 p1, float3 p2) -> float3 {
float3 edge1 = p1 - p0;
float3 edge2 = p2 - p0;
return normalize(cross(edge1, edge2));
}
The general form is:
functionName :: (Type arg1, Type arg2, ...) -> ReturnType {
// body
}
Function Names
A function or method cannot share its name with a built-in intrinsic. length :: (float3 v) -> float is rejected with Function name 'length' is reserved for a built-in intrinsic, even when the function is never called or lives in a module. Pick a distinct name such as signedLength or signed_distance.
Parameters Are Passed by Value
Every parameter is a local copy. Assigning to a parameter, or to a field or element of one, never changes the caller's variable. Structs and fixed-size arrays are copied as well:
scaled :: (float3 v, float k) -> float3 {
v *= k; // Changes only the local copy
return v;
}
float3 p = float3(1.0);
float3 q = scaled(p, 2.0); // p is still (1, 1, 1)
Pointer-typed parameters (float^ out) are not supported. To produce several results, return a struct, or use a mutating struct method, which can update its receiver.
Returning
A function with a return type must return a value on every path that can reach the end of its body. A missing return after an if or a switch without default is a compile error. void functions exit early with return; or return if (cond);. Functions with a return type can use return expr if (cond); (see Control Flow).
Recursion
Shaders run without a call stack, so a function that calls itself, directly or through other functions, is a compile error. Rewrite such algorithms as loops. eval functions are the exception: they run at compile time and may recurse up to 128 levels deep (see Eval).
Function Calls
Call functions with positional arguments:
int result = add(5, 3);
float3 normal = calculateNormal(v0, v1, v2);
The current parser surface uses positional calls only.
Overloading
Multiple functions can share a name as long as their parameter lists differ:
square :: (float x) -> float {
return x * x;
}
square :: (float3 x) -> float3 {
return x * x;
}
Shader Entry Points
BWSL can package shader stages into helper functions that return vertex_function, fragment_function, or compute_function.
Vertex and Fragment Helpers
vertexMain :: () -> vertex_function {
vertex {
output.position = float4(attributes.position, 1.0);
output.uv = attributes.texcoord;
}
}
fragmentMain :: () -> fragment_function {
fragment {
output.color = float4(input.uv, 0.0, 1.0);
}
}
Use them in a pass with stage assignment:
pass "Main" {
use attributes { position, texcoord }
vertex = vertexMain()
fragment = fragmentMain()
}
Compute Helpers
Provisional
compute_function return types are parsed, but compute stages are normally declared directly in a pass with compute "Name" [x, y, z] { ... }. Treat this form as provisional.
clearPass :: () -> compute_function {
compute "Main" [64, 1, 1] {
uint idx = input.global_id.x;
if (idx >= 1024u) {
return;
}
// resources.output[idx] = 0.0;
}
}
Pass Block Helpers
Functions can also return pass_block to package a whole pass body:
makePass :: () -> pass_block {
pass {
vertex {
output.position = float4(0.0, 0.0, 0.0, 1.0);
}
fragment {
output.color = float4(1.0);
}
}
}
pass "Generated" = makePass();
Module pass blocks can map their local attributes, resources, and variants to the caller pipeline. See Pass Blocks for the complete rules.
Function Scope
Functions can live at pipeline scope, pass scope, or module scope.
Pipeline Scope
Pipeline-scoped helpers are visible to every pass in the pipeline:
pipeline MyPipeline {
luma :: (float3 color) -> float {
return dot(color, float3(0.299, 0.587, 0.114));
}
pass "Forward" {
fragment {
float v = luma(float3(1.0, 0.8, 0.2));
output.color = float4(v, v, v, 1.0);
}
}
}
Pass Scope
Helpers declared inside a pass stay inside that pass:
pipeline MyPipeline {
pass "SpecialEffect" {
wobble :: (float2 uv, float t) -> float2 {
return uv + sin(uv.y * 10.0 + t) * 0.01;
}
fragment {
float2 distorted = wobble(input.uv, 1.0);
output.color = float4(distorted, 0.0, 1.0);
}
}
}
Module Scope
Functions defined in modules are accessed using the :: namespace operator:
module Math {
square :: (float x) -> float {
return x * x;
}
}
pipeline MyPipeline {
import Math
pass "Main" {
fragment {
float v = Math::square(0.5);
output.color = float4(v, v, v, 1.0);
}
}
}
Imported modules can be aliased, and using ModuleName can opt an imported module into unqualified lookup:
pipeline MyPipeline {
import Math as M
using M
pass "Main" {
fragment {
float v = square(0.5) + M::PI;
output.color = float4(v, v, v, 1.0);
}
}
}
Constraint-Based Functions
BWSL's active generic system is constraint-based:
constraint FloatVectors = float2 | float3 | float4;
scale :: (FloatVectors v, float s) -> FloatVectors {
return v * s;
}
Constrained functions can dispatch on the resolved concrete type:
componentSum :: (FloatVectors v) -> float {
float2: v.x + v.y
float3: v.x + v.y + v.z
float4: v.x + v.y + v.z + v.w
}
Angle-bracket syntax such as foo<T> and where clauses are not part of the current supported surface. See Generics for the constraint-based form.
Function Summary
| Feature | Syntax |
|---|---|
| Declaration | name :: (params) -> ReturnType { } |
| Positional call | func(a, b, c) |
| Overload | name :: (float x) -> ... and name :: (float3 x) -> ... |
| Vertex helper | name :: () -> vertex_function { vertex { } } |
| Fragment helper | name :: () -> fragment_function { fragment { } } |
| Compute helper | name :: () -> compute_function { compute "Main" [X, Y, Z] { } } |
| Pass-block helper | name :: () -> pass_block { pass { } } |
| Type constraint | constraint Name = Type1 | Type2; |
| Constrained parameter | (ConstraintName param) |
| Module function | moduleName::functionName() |
| Imported module alias | import Module as Alias |
| Unqualified module lookup | using Alias |
| Type alias | using Name = ExistingType |
See Also
- Generics - Type constraints and generic functions
- Modules - Organizing code into reusable modules
- Shader I/O - Input and output in shader stages
- Language Overview - BWSL syntax and concepts