Language2 min read

Pass Blocks

Reusable pass-returning functions, interface mappings, and variant remapping in BWSL.

Reading Time
2 min
Word Count
328
Sections
7
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

Pass blocks let a function return a reusable pass body. They are useful when a module owns a complete graphics or compute pass shape, but the caller pipeline owns the concrete attributes, resources, and variants.

Declaring Pass Blocks

A pass-block helper returns pass_block and its body must contain a pass { ... } block:

bwsl
module SpritePasses {
attributes {
position: float3
normal: float3
texCoord: float2
}
resources {
viewProj: mat4
atlas: texture2D
atlasSampler: sampler
}
variants {
localTint: bool = false
}
sprite :: () -> pass_block {
pass {
use attributes { position, normal?, texCoord }
use resources { viewProj, atlas, atlasSampler }
vertex {
output.position = resources.viewProj * float4(attributes.position, 1.0);
output.uv = attributes.texCoord;
}
fragment {
float4 texel = sample(resources.atlas, resources.atlasSampler, input.uv);
if (variants.localTint) {
output.color = texel;
} else {
output.color = float4(input.uv, 0.0, 1.0);
}
}
}
}
}

The pass { ... } wrapper is required. A bare body that starts with use, vertex, fragment, or compute is rejected.

Instantiating Pass Blocks

Instantiate a pass block with pass "Name" = helperCall(). A pipeline-local helper can be used directly:

bwsl
pipeline LocalPipeline {
attributes {
position: float3
uv: float2
}
localPass :: () -> pass_block {
pass {
use attributes { position, uv }
vertex {
output.position = float4(attributes.position, 1.0);
output.uv = attributes.uv;
}
fragment {
output.color = float4(input.uv, 0.0, 1.0);
}
}
}
pass "Local" = localPass();
}

Pass-block instantiation must be a direct function call. Ternaries or arbitrary expressions on the right-hand side are rejected.

Mapping Interfaces

When a pass block comes from a module and uses attributes or resources, the caller must map the module-local interface names to names declared in the caller pipeline:

bwsl
pipeline GameUI {
import SpritePasses
attributes {
meshPosition: float3
meshNormal: float3
uv: float2
}
resources {
cameraVP: mat4
uiAtlas: texture2D
uiSampler: sampler
}
variants {
tintEnabled: bool = true
}
pass "Sprites" = SpritePasses::sprite() {
use attributes { position = meshPosition, normal = meshNormal, texCoord = uv }
use resources { viewProj = cameraVP, atlas = uiAtlas, atlasSampler = uiSampler }
variants { localTint = tintEnabled }
}
}

Inside the mapping body, only these forms are accepted:

bwsl
use attributes { localName = pipelineName, sameName }
use resources { localName = pipelineName, sameName }
variants { localVariant = pipelineVariant }

The left side is the name used by the pass-block helper. The right side is the caller pipeline name. A bare entry maps a name to itself.

Mapping Rules

Attribute mappings require matching attribute type and decorators. Resource mappings require matching resource type. Variant mappings require both sides to be declared variants with matching types.

Variant mapping targets must be caller pipeline variants, not arbitrary expressions:

bwsl
variants {
tintEnabled: bool = true
}
pass "Sprites" = SpritePasses::sprite() {
variants { localTint = tintEnabled }
}

Optional attribute and resource facts are remapped with their interface names. For example, a module pass using normal? can refer to variants.has_normal; when the caller maps normal = meshNormal, that optional fact follows the caller's mapped attribute.

The instantiation body is for mapping only. You cannot add or override vertex, fragment, compute, outputs, or pass-local helper functions from the caller.

Parameters

Pass-block helpers may take parameters, but instantiation arguments must be compile-time constants:

bwsl
makeShadow :: (float bias) -> pass_block {
pass {
vertex {
output.position = float4(0.0, 0.0, bias, 1.0);
}
fragment = null
}
}
pass "Shadow" = makeShadow(0.001);

Runtime values such as attributes.position.x cannot be passed into a pass_block helper.

Compute Pass Blocks

Pass blocks can return compute passes too:

bwsl
module ComputePasses {
resources {
values: buffer<float>
result: buffer<float>
}
copyValues :: () -> pass_block {
pass {
use resources { values, result }
compute "Main" [8, 1, 1] {
uint idx = input.global_id.x;
resources.result[idx] = resources.values[idx];
}
}
}
}
pipeline CopyPipeline {
import ComputePasses
resources {
sourceValues: buffer<float>
outputValues: buffer<float>
}
pass "Copy" = ComputePasses::copyValues() {
use resources { values = sourceValues, result = outputValues }
}
}

See Also

  • The Pass - Inline passes and stage assignment
  • Functions - Function declarations and return types
  • Modules - Sharing reusable shader code
  • Shader Variants - Variant declarations and specialization