Bindings and Reflection
The bindings.json format, the names BWSL gives GL/Metal/HLSL interface objects, and how a host should bind resources.
Turn the guide into code
Take the key idea from this page into the playground and validate it in a real shader instead of leaving it as theory.
Open PlaygroundThe compiler assigns every binding itself. Hosts should read the result from the reflection output rather than infer bindings from declaration order or hard-code generated names. Pass -bindings (implied by -all) to write <stem>.bindings.json next to the shaders.
bwslc lit.bwsl -o out/ -gles -bindings
bindings.json
For this pipeline:
pipeline Lit {
attributes { position: float3 uv: float2 }
struct Camera { mat4 viewProj; float3 eye; }
resources {
camera: Camera
tint: float4
albedo: texture2D
units: texture2D[2]
}
pass "Main" {
use attributes { position, uv }
use resources { camera, tint, albedo, units }
// ...
}
}
the compiler writes:
{
"pass": "Main",
"resources": [
{ "name": "camera", "type": "uniform", "set": 0, "binding": 0,
"stages": ["vertex"], "access": "readonly", "glName": "ub_camera" },
{ "name": "tint", "type": "uniform", "set": 0, "binding": 1,
"stages": ["fragment"], "access": "readonly", "glName": "ub_tint" },
{ "name": "albedo", "type": "texture", "set": 0, "binding": 2,
"stages": ["fragment"], "access": "readonly", "glName": "t_albedo" },
{ "name": "units", "type": "texture", "set": 0, "binding": 3, "count": 2,
"stages": ["fragment"], "access": "readonly", "glName": "t_units" }
]
}
Resource Fields
| Field | Meaning |
|---|---|
name | The resource's name in the resources {} block |
type | uniform, storage_buffer, texture, sampler, … |
set, binding | Descriptor set and binding (Vulkan/SPIR-V). Samplers that are separate from their textures use set 2. |
count | Element count for texture arrays such as texture2D[4]. Absent for single resources. |
stages | Stages that actually reference the resource |
access | readonly, writeonly or readwrite, from how the shader uses it |
glName | The GL name to look up: pass it to glGetUniformBlockIndex for uniform blocks or glGetUniformLocation for combined textures |
abi | How a texture or sampler is bound when it isn't a plain combined pair (see below) |
defaultSamplerFor | On a compiler-generated sampler: the texture it serves when that texture is sampled without an explicit sampler |
combinedSamplerUniforms | On a separate texture, for GLSL/GLES: one entry per texture/sampler pair, giving the combined uniform name, the sampler, its samplerSet and samplerBinding, and stages |
Only referenced resources are listed. Unused declarations are dropped, and the remaining resources are packed into the available slots.
Combined and Separate Samplers
A texture that is only ever used with one sampler is bound as a combined sampled image. When a texture is used with several samplers, or one sampler is shared between textures, the compiler keeps them separate:
- the texture gets
"abi": "separate_image"; - each sampler gets
"abi": "sampler"and is placed in set 2; - a sampler the compiler generates for implicit
sample(tex, uv)calls is named<texture>__default_samplerand carriesdefaultSamplerFor.
GL has no separate samplers, so for GLSL and GLES each pair becomes its own combined sampler uniform. Look those up through combinedSamplerUniforms:
{ "name": "a", "type": "texture", "set": 0, "binding": 0, "abi": "separate_image",
"combinedSamplerUniforms": [
{ "name": "bwsl_tex_0_0_sampler_2_0", "sampler": "linearSamp", "samplerSet": 2, "samplerBinding": 0, "stages": ["fragment"] },
{ "name": "bwsl_tex_0_0_sampler_2_1", "sampler": "pointSamp", "samplerSet": 2, "samplerBinding": 1, "stages": ["fragment"] }
] }
HLSL Extra Resources
HLSL has no built-in for the compute dispatch size. A compute stage that reads input.num_workgroups needs a constant buffer the host fills, listed under hlslResources:
"hlslResources": [
{ "name": "bwsl_num_workgroups", "type": "uniform", "set": 3, "binding": 0,
"stages": ["compute"], "builtin": "num_workgroups", "valueType": "uint3", "byteSize": 16 }
]
Bind 16 bytes at register b0, space3: the x, y and z group counts plus one padding word.
Interface Names
GL and GLES output uses predictable names derived from your source. Metal and HLSL output use the same names.
| Source | Generated name | Example |
|---|---|---|
Uniform resource r | block ub_r, instance bwsl_ub_r, member bwsl_u_r | uniform ub_camera { Camera bwsl_u_camera; } bwsl_ub_camera; |
Texture tex | combined sampler t_tex | uniform highp sampler2D t_albedo; |
Texture array tex | t_tex[N] | uniform highp sampler2D t_units[2]; |
Default sampler for tex | bwsl_s_tex | |
Attribute a | a_a | in vec3 a_position; |
Varying v | v_v | out vec2 v_uv; |
Struct type and member names are kept as written, so nested uniform structs link across GL stages.
Because these names are emitted as written, some source names would collide with them and are rejected:
- a struct named like an interface name (for example a struct
a_positionwhen there is an attributeposition); - a sampler named like a texture's combined name (a sampler
t_atlasnext to a textureatlas); - two varyings whose names are the same in their first 31 characters.
Changed in v0.11.0
Older compilers emitted generated names such as _12_14, UB_<resource>, u_<texture>, attr0 and varying0. Hosts that looked those up must switch to the names above, ideally by reading glName from bindings.json.
WebGL Stage Sidecars
With -gles, each stage also gets a small JSON sidecar (<stem>.vert.json, <stem>.frag.json) mapping attribute names to locations and resources to the uniform blocks and samplers that stage uses:
{
"attributes": { "position": 0, "uv": 1 },
"uniforms": { "camera": 0 },
"samplers": {}
}
WGSL Bindings
The experimental WGSL backend (-wgsl) writes its own <stem>.<stage>.wgsl.bindings.json per stage, with WebGPU group/binding pairs instead of set/binding:
{
"version": 3,
"target": "wgsl",
"stage": "compute",
"entryPoint": "main",
"workgroupSize": [64, 1, 1],
"resources": [
{ "name": "data", "type": "storage_buffer", "group": 0, "binding": 0,
"access": "read_write", "elementType": "UINT", "stride": 4 }
]
}
Storage buffers go in group 0, uniforms in group 1, sampled textures in group 2 and samplers in group 3. The implicit sampler for texture slot N is at binding 32 + N. This layout differs from the SPIR-V one, so WebGPU hosts must read the WGSL file. See CLI Reference.
See Also
- Resources - Declaring resources, samplers and texture arrays
- CLI Reference -
-bindingsand output files - BWSL & Your Engine - Host integration