Language4 min read

Resources

Accessing uniforms, textures, buffers, samplers, and storage images through the resources namespace.

Reading Time
4 min
Word Count
695
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

Shader-visible resources are declared in the pipeline with resources {} and accessed through resources.* from stages that opt into them.

bwsl
float4 albedo = sample(resources.albedoTexture, resources.albedoSampler, input.uv);
mat4 viewProj = resources.projectionMatrix * resources.viewMatrix;
Particle p = resources.particles[input.instance_id];

Where Resources Come From

Resources are declared in a pipeline-level resources {} block:

bwsl
resources {
viewMatrix: mat4
projectionMatrix: mat4
time: float
albedoTexture: texture2D
albedoSampler: sampler
particles: buffer<Particle>
}

Each pass selects the resources it uses:

bwsl
pass "Main" {
use resources { viewMatrix, projectionMatrix, time, albedoTexture, albedoSampler }
fragment {
float4 albedo = sample(resources.albedoTexture, resources.albedoSampler, input.uv);
output.color = albedo;
}
}

The compiler resolves usage from source, assigns bindings automatically, and emits reflection for the host. The resource block is the public declaration surface for shader-visible host data.

Common resource types include:

  • scalar, vector, and matrix values such as float, float3, and mat4
  • custom structs, including structs defined in imported modules
  • textures such as texture2D, texture3D, textureCube, and texture2DArray
  • sampler
  • buffers such as buffer<Particle> and cbuffer<CameraData>
  • storage images where supported

The exact resource type controls how it can be used from BWSL.

Struct Resource Types

Resources can use local struct types:

bwsl
struct CameraData {
mat4 viewProj;
float3 cameraPosition;
};
resources {
camera: CameraData
}

They can also use structs defined in imported modules. Module-defined resource payloads use Module::Type spelling in the resources {} block, including inside buffer<> and cbuffer<>:

bwsl
module RenderTypes {
struct FrameData {
float4[4] transforms;
};
}
pipeline ModuleResourcePayload {
import RenderTypes
attributes {
position: float3
}
resources {
frame: RenderTypes::FrameData
}
pass "Main" {
use attributes { position }
use resources { frame }
vertex {
float4 transform = resources.frame.transforms[0];
float2 p = attributes.position.xy * transform.zw + transform.xy;
output.position = float4(p, attributes.position.z, 1.0);
}
fragment {
output.color = float4(1.0);
}
}
}

After declaration, access fields and arrays through resources.<name> like any other resource.

Selecting Resources Per Pass

use resources is pass-scoped. A resource must be selected before that pass can access resources.<name>.

bwsl
pass "Shadow" {
use resources { lightViewProj }
vertex {
output.position = resources.lightViewProj * float4(attributes.position, 1.0);
}
fragment = null
}

Resources can also be optional. Optional resources create implicit variant facts that can be used for specialization:

bwsl
pass "Main" {
use resources { albedoTexture?, albedoSampler? }
fragment {
if (variants.has_resource_albedoTexture && variants.has_resource_albedoSampler) {
output.color = sample(resources.albedoTexture, resources.albedoSampler, input.uv);
} else {
output.color = float4(1.0);
}
}
}

Uniforms and Buffers

Uniforms and buffers are read with normal member or index access:

bwsl
float time = resources.time;
mat4 vp = resources.projectionMatrix * resources.viewMatrix;
Particle particle = resources.particles[input.instance_id];

Sampled Textures

Sampled textures can be accessed through reflected texture bindings:

bwsl
float4 texel = sample(resources.albedoTexture, resources.albedoSampler, input.uv);

sample also accepts a texture with no sampler argument, in which case the compiler supplies a default sampler for that texture. texture3D resources are real volume textures on every backend: sample them with a float3 coordinate, and load them with an int3 texel coordinate.

Samplers

Declare sampler resources and pass them explicitly when you need control over filtering or addressing. The compiler keeps each sampler's identity:

  • A texture always used with the same single sampler may compile to a combined sampled-image binding.
  • When one texture is used with different samplers, or one sampler is shared between several textures, the compiler emits separate image and sampler bindings. Reflection marks these with abi: "separate_image" and abi: "sampler" (samplers use descriptor set 2).
  • For GLSL and GLES, each texture/sampler pair becomes a combined sampler uniform, listed in the texture's combinedSamplerUniforms.

Bind from the reflection output (-bindings), not from declaration order. See Bindings and Reflection.

Texture Arrays

A texture resource can be a fixed-size array of separate textures:

bwsl
resources {
units: texture2D[4]
}
fragment {
float4 base = sample(resources.units[0], input.uv);
float4 picked = sample(resources.units[input.unit], input.uv);
}
  • Index an element with a constant, or with an int/uint expression. resources.units.length is the element count.
  • A non-constant index can only appear as the texture argument of a texture function such as sample. Because GLSL ES 3.00 only accepts constant sampler indices, it compiles to one constant-index call per element, chosen by branches. An out-of-range index reads the last element.
  • Each element takes one of the pipeline's resource slots. On GL it gets its own texture unit, and on Metal and HLSL its own texture register. Vulkan uses one binding with a descriptor count. bindings.json lists the array once, with a count.
  • The array has one sampler per element (GL, Metal) or is paired with one explicit sampler. It can't share a sampler with other textures or be passed to a function.

Passing Textures to Functions

Functions can take texture2D, texture3D, textureCube, texture2DArray and sampler parameters, and you call them with resources:

bwsl
shade :: (texture2D tex, sampler samp, float2 uv) -> float3 {
return sample(tex, samp, uv).rgb;
}
fragment {
output.color = float4(shade(resources.albedo, resources.linearSampler, input.uv), 1.0);
}

Storage Images

Storage images are used with load and store style intrinsics:

bwsl
float4 texel = load(resources.inputImage, int2(4, 8), 0);
store(resources.outputImage, int2(4, 8), texel);

Limits and Restrictions

  • 32 referenced resources per pipeline. You can declare more; unused declarations are dropped and the rest are packed into the available slots. Referencing more than 32 is a compile error.
  • Metal binding limits: buffers must fit in slots 0–30 and samplers in 0–15. The compiler reports an error when its resolved bindings exceed them.
  • No bool in uniforms or storage buffers, including bool vectors and bool fields nested in structs or arrays. Store a flag as uint and test it with flag != 0u. Local bool values are fine.
  • Payload types must exist. An unknown type in a resource declaration, or as a buffer<T>/cbuffer<T> element type, is a compile error even if the resource is never used.

Stage Validation

Resource access is validated against how the resource is actually used in the pipeline.

That means:

  • invalid stage/resource access is rejected during compilation
  • unsupported backend bindings can be surfaced through reflection before runtime

Typical Patterns

Graphics:

bwsl
vertex {
output.position = resources.mvp * float4(attributes.position, 1.0);
}
fragment {
float4 albedo = sample(resources.albedoTexture, resources.albedoSampler, input.uv);
output.color = albedo;
}

Compute:

bwsl
compute "Main" [16, 16, 1] {
uint2 gid = input.global_id.xy;
float4 value = load(resources.inputImage, int2(gid), 0);
store(resources.outputImage, int2(gid), value);
}