Resources
Accessing uniforms, textures, buffers, samplers, and storage images through the resources namespace.
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 EditShader-visible resources are declared in the pipeline with resources {} and accessed through resources.* from stages that opt into them.
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:
resources {
viewMatrix: mat4
projectionMatrix: mat4
time: float
albedoTexture: texture2D
albedoSampler: sampler
particles: buffer<Particle>
}
Each pass selects the resources it uses:
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, andmat4 - custom structs, including structs defined in imported modules
- textures such as
texture2D,texture3D,textureCube, andtexture2DArray sampler- buffers such as
buffer<Particle>andcbuffer<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:
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<>:
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>.
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:
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:
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:
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"andabi: "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:
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/uintexpression.resources.units.lengthis 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.jsonlists the array once, with acount. - 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:
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:
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
boolin uniforms or storage buffers, includingboolvectors andboolfields nested in structs or arrays. Store a flag asuintand test it withflag != 0u. Localboolvalues 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:
vertex {
output.position = resources.mvp * float4(attributes.position, 1.0);
}
fragment {
float4 albedo = sample(resources.albedoTexture, resources.albedoSampler, input.uv);
output.color = albedo;
}
Compute:
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);
}