Structs
User-defined struct types, member access, methods, const receivers, and resource payloads in BWSL.
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 PlaygroundStructs are user-defined nominal types for grouping shader data. They can be declared at pipeline scope or inside modules, used as local values, passed to functions, returned from functions, nested inside other structs, and used as resource payload shapes.
Declaring Structs
Define fields inside a struct body. Fields use the same type names as ordinary declarations, and fixed-size array fields use the canonical Type[N] name form:
struct Light {
float3 position;
float intensity;
float4[4] shadowPlanes;
}
struct Material {
float3 albedo;
float roughness;
float metallic;
}
A trailing semicolon after the closing brace is accepted, but not required.
Constructing and Accessing
Create struct values with either field assignments or positional constructors. Constructor arguments follow field declaration order.
Material assigned;
assigned.albedo = float3(0.9, 0.4, 0.2);
assigned.roughness = 0.35;
assigned.metallic = 0.0;
Material constructed = Material(float3(0.9, 0.4, 0.2), 0.35, 0.0);
float3 baseColor = constructed.albedo;
float reflectance = constructed.metallic;
Nested structs and struct arrays use normal member and index access:
struct Pair {
float a;
float b;
}
struct Surface {
Material material;
Pair[4] samples;
}
Surface surface;
surface.material.albedo = float3(1.0, 0.8, 0.6);
surface.samples[0] = Pair(1.0, 2.0);
float sampleA = surface.samples[0].a;
Struct Methods
Methods are declared inside the struct body with the same name :: (...) -> ReturnType form as functions. Call them through a receiver expression with dot syntax:
struct Basis {
float3 tangent;
float3 bitangent;
float3 normal;
to_world :: (float3 v) -> float3 {
return tangent * v.x + bitangent * v.y + normal * v.z;
}
}
Basis basis;
basis.tangent = float3(1.0, 0.0, 0.0);
basis.bitangent = float3(0.0, 1.0, 0.0);
basis.normal = float3(0.0, 0.0, 1.0);
float3 world = basis.to_world(float3(1.0, 2.0, 3.0));
Inside a method, self is available implicitly. Fields can be read by bare name, and self.field is useful when a parameter or local variable shadows a field:
struct ShadowBox {
float value;
select :: (float value) const -> float {
float local = value * 2.0;
return local + self.value;
}
}
Non-const methods can mutate receiver fields:
struct Accumulator {
float total;
add :: (float v) -> void {
total = total + v;
}
reset :: () -> void {
self.total = 0.0;
}
}
Const Methods
Place const after the parameter list to declare a method that can be called on a const receiver:
struct Accumulator {
float total;
add :: (float v) -> void {
total = total + v;
}
read :: () const -> float {
return self.total;
}
}
Accumulator acc;
acc.total = 1.0;
acc.add(2.0);
const Accumulator frozen = acc;
float value = frozen.read();
const methods cannot assign to receiver fields, and a const receiver cannot call a non-const method.
Method Overloads
Struct methods can be overloaded by parameter types. const receiver eligibility is also part of method selection: mutable receivers prefer a matching non-const method, while const receivers can only call matching const methods.
struct Preference {
float v;
pick :: () -> float3 {
return float3(v + 2.0, 0.0, 0.0);
}
pick :: () const -> float {
return v + 1.0;
}
}
Preference pref;
pref.v = 10.0;
float3 mutablePick = pref.pick();
const Preference frozen = pref;
float constPick = frozen.pick();
A struct cannot declare the same method name, parameter signature, and constness more than once.
Module Structs
Structs declared in modules can have methods too. Use Module::Type in ordinary type positions and call methods on values of that type:
module ToneLib {
struct Tone {
float3 color;
float gain;
tint :: (float scale) const -> float3 {
return color * gain * scale;
}
boost :: (float value) -> void {
gain = gain + value;
}
}
}
pipeline UsesTone {
import ToneLib
pass "Compute" {
compute "Main" [1, 1, 1] {
ToneLib::Tone tone;
tone.color = float3(0.25, 0.5, 1.0);
tone.gain = 2.0;
tone.boost(1.0);
float3 result = tone.tint(0.5);
}
}
}
Resource Payloads
Structs can describe host-facing resource payloads:
struct CameraData {
mat4 viewProj;
float3 cameraPosition;
}
resources {
camera: CameraData
}
Imported module structs can also be resource payloads. In a resources {} block, module-defined payload types use Module.Type spelling:
module RenderTypes {
struct FrameData {
float4 scaleOffset;
}
}
pipeline UsesFrameData {
import RenderTypes
resources {
frame: RenderTypes.FrameData
}
}
After declaration, access fields through resources.<name> like any other resource:
float2 p = attributes.position.xy * resources.frame.scaleOffset.zw
+ resources.frame.scaleOffset.xy;
See Also
- Types Overview - Built-in and user-defined types
- Functions - Function declarations and overloads
- Modules - Sharing structs and helpers
- Resources - Structs as resource payloads