Language3 min read

Shader I/O

How data flows through attributes, input, output, and built-in stage values.

Reading Time
3 min
Word Count
380
Sections
11
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 uses four well-known namespaces inside shaders:

  • attributes for declared vertex inputs
  • input for stage inputs and built-ins
  • output for stage outputs
  • resources for shader-visible data declared in the pipeline resources {} block

The most common flow is vertex output.* becoming fragment input.* with matching field names.

Shader I/O Data Flow

How data moves between shader stages

Attribute Declarations

position: float3normal: float3texcoord: float2color: float4
attributes.*

Vertex Shader

PROGRAMMABLE

Reads from

attributes.positionattributes.normal
input.vertex_idinput.instance_id

Writes to

output.positionreqoutput.worldPosoutput.normaloutput.texcoord
output.*

Rasterizer

Interpolates vertex outputs across the triangle surface using barycentric coordinates

input.*

Fragment Shader

PROGRAMMABLE

Reads from

input.worldPosinput.normalinput.texcoord
input.position.z

Writes to

output.colorreqoutput.depth
output.color

Render Target

Screen or texture output

Attributes

Vertex inputs are declared at pipeline scope and selected per pass:

bwsl
attributes {
position: float3
normal: float3
texcoord: float2
color: float4
}

Read them from attributes.<name> in the vertex stage:

bwsl
vertex {
float3 pos = attributes.position;
float2 uv = attributes.texcoord;
output.position = float4(pos, 1.0);
output.uv = uv;
}

The Output Object

In a vertex block, use output to declare values that should be passed to the fragment stage:

bwsl
vertex {
output.position = float4(attributes.position, 1.0);
output.worldPos = attributes.position;
output.viewDir = normalize(float3(0.0, 0.0, 5.0) - attributes.position);
output.texcoord = attributes.texcoord;
}

output.position is special: it defines clip-space position and is required in every vertex stage.

The Input Object

The input object behaves differently depending on the shader stage.

Vertex Stage Input

In a vertex block, input provides access to built-in vertex identifiers:

bwsl
vertex {
uint vid = input.vertex_id;
uint iid = input.instance_id;
}
FieldTypeDescription
input.vertex_iduintIndex of the current vertex in the draw call
input.instance_iduintIndex of the current instance

These map to gl_VertexID and gl_InstanceID in GLSL-family back ends.

Fragment Stage Input

In a fragment block, named outputs from the vertex stage appear automatically on input, and input.position exposes the fragment position:

bwsl
fragment {
float3 worldPos = input.worldPos;
float3 viewDir = normalize(input.viewDir);
float2 uv = input.texcoord;
float depth = input.position.z;
float3 color = normalize(worldPos) * 0.5 + 0.5;
color *= 0.5 + 0.5 * abs(viewDir.z);
color *= 1.0 - depth * 0.25;
output.color = float4(color, 1.0);
}

Values in input are automatically interpolated across the triangle during rasterization.

Fragment Output

output.color defines the final render-target color:

bwsl
fragment {
output.color = float4(1.0, 0.0, 0.0, 1.0);
}

output.depth can also be written for custom depth output:

bwsl
fragment {
float depth = saturate(input.position.z);
output.depth = depth;
output.color = float4(depth, depth, depth, 1.0);
}

Automatic Type Inference

BWSL infers types for interpolated values. Assign to output.yourName in the vertex stage and read input.yourName in the fragment stage:

bwsl
vertex {
output.position = float4(attributes.position, 1.0);
output.uv = attributes.texcoord;
output.worldNormal = attributes.normal;
output.tint = float4(1.0, 0.8, 0.6, 1.0);
}
fragment {
float2 uv = input.uv;
float3 normal = input.worldNormal;
float4 tint = input.tint;
}

Compute Input

Compute stages use the same input namespace for workgroup built-ins:

bwsl
compute "Main" [64, 1, 1] {
uint3 gid = input.global_id;
uint3 lid = input.local_id;
uint3 group = input.workgroup_id;
uint3 groups = input.num_workgroups;
uint flat = input.local_index;
}

Complete Example

bwsl
pipeline ShaderIO {
attributes {
position: float3
normal: float3
texcoord: float2
color: float4
}
pass "Main" {
use attributes { position, normal, texcoord, color }
vertex {
uint vid = input.vertex_id;
output.position = float4(attributes.position, 1.0);
output.worldPos = attributes.position;
output.normal = attributes.normal;
output.texcoord = attributes.texcoord;
output.vertexColor = attributes.color;
output.vertexIndex = float(vid);
}
fragment {
float3 normal = normalize(input.normal);
float4 vertexColor = input.vertexColor;
float vertexFade = 0.8 + 0.2 * fract(input.vertexIndex * 0.125);
float lit = max(dot(normal, normalize(float3(1.0, 1.0, 1.0))), 0.0);
output.color = float4(vertexColor.rgb * lit * vertexFade, vertexColor.a);
}
}
}

Summary

ObjectStagePurpose
attributes.*VertexDeclared vertex input data
input.vertex_idVertexIndex of the current vertex
input.instance_idVertexIndex of the current instance
output.positionVertexRequired clip-space position
output.*VertexAny value to pass to the fragment shader
input.positionFragmentBuilt-in fragment position
input.*FragmentInterpolated values from the vertex shader
output.colorFragmentFinal color written to the render target
output.depthFragmentOptional custom depth output
input.global_id etc.ComputeCompute built-ins for dispatch/workgroup access

See Also