Language2 min read

Functions

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

Reading Time
2 min
Word Count
271
Sections
13
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, and stage-returning helper functions.

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;
}
}

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);
}
}
}

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] { } }
Type constraintconstraint Name = Type1 | Type2;
Constrained parameter(ConstraintName param)
Module functionmoduleName::functionName()

See Also