Shader I/O
How data flows through attributes, input, output, and built-in stage values.
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 uses four well-known namespaces inside shaders:
attributesfor declared vertex inputsinputfor stage inputs and built-insoutputfor stage outputsresourcesfor shader-visible data declared in the pipelineresources {}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
attributes { }Vertex Shader
PROGRAMMABLEvertex { }Reads from
Writes to
Rasterizer
Interpolates vertex outputs across the triangle surface using barycentric coordinates
Fragment Shader
PROGRAMMABLEfragment { }Reads from
Writes to
Render Target
Screen or texture output
Attributes
Vertex inputs are declared at pipeline scope and selected per pass:
attributes {
position: float3
normal: float3
texcoord: float2
color: float4
}
Read them from attributes.<name> in the vertex stage:
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:
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:
vertex {
uint vid = input.vertex_id;
uint iid = input.instance_id;
}
| Field | Type | Description |
|---|---|---|
input.vertex_id | uint | Index of the current vertex in the draw call |
input.instance_id | uint | Index 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:
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:
fragment {
output.color = float4(1.0, 0.0, 0.0, 1.0);
}
output.depth can also be written for custom depth output:
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:
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:
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
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
| Object | Stage | Purpose |
|---|---|---|
attributes.* | Vertex | Declared vertex input data |
input.vertex_id | Vertex | Index of the current vertex |
input.instance_id | Vertex | Index of the current instance |
output.position | Vertex | Required clip-space position |
output.* | Vertex | Any value to pass to the fragment shader |
input.position | Fragment | Built-in fragment position |
input.* | Fragment | Interpolated values from the vertex shader |
output.color | Fragment | Final color written to the render target |
output.depth | Fragment | Optional custom depth output |
input.global_id etc. | Compute | Compute built-ins for dispatch/workgroup access |
See Also
- Language Overview - BWSL syntax and concepts
- Compute Shaders - Compute built-ins and workgroup behavior
- Operators - Operator reference for shader math