Language2 min read

Functions

Function declaration, shader entry points, generics, and scoping in BWSL.

Reading Time
2 min
Word Count
346
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 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 ->:

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

bwsl
functionName :: (Type arg1, Type arg2, ...) -> ReturnType {
// body
}

Function Calls

Call functions with positional arguments:

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

bwsl
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

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

bwsl
pass "Main" {
use attributes { position, texcoord }
vertex = vertexMain()
fragment = fragmentMain()
}

Compute Helpers

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

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

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

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

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

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

bwsl
constraint FloatVectors = float2 | float3 | float4;
scale :: (FloatVectors v, float s) -> FloatVectors {
return v * s;
}

Constrained functions can dispatch on the resolved concrete type:

bwsl
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

FeatureSyntax
Declarationname :: (params) -> ReturnType { }
Positional callfunc(a, b, c)
Overloadname :: (float x) -> ... and name :: (float3 x) -> ...
Vertex helpername :: () -> vertex_function { vertex { } }
Fragment helpername :: () -> fragment_function { fragment { } }
Compute helpername :: () -> compute_function { compute "Main" [X, Y, Z] { } }
Pass-block helpername :: () -> pass_block { pass { } }
Type constraintconstraint Name = Type1 | Type2;
Constrained parameter(ConstraintName param)
Module functionmoduleName::functionName()
Imported module aliasimport Module as Alias
Unqualified module lookupusing Alias
Type aliasusing Name = ExistingType

See Also