Requirements
Standard Uniform Buffer Layout
Every member of an OpTypeStruct with storage class of Uniform and a decoration of Block (uniform buffers) must be laid out according to the following rules:
-
The Offset decoration must be a multiple of its base alignment.
-
Any ArrayStride or MatrixStride decoration must be an integer multiple of the base alignment of the array or matrix from above.
-
The Offset decoration of a member must not place it between the end of a structure or an array and the next multiple of the base alignment of that structure or array.
-
The numeric order of Offset decorations need not follow member declaration order.
reference
These also then follow the rules from std140
. Also mentioned in 14.5.4
.
Additionally from the SPIR-V spec
Composite objects in the UniformConstant, Uniform, and PushConstant Storage Classes must be explicitly laid out. The following apply to all the aggregate and matrix types describing such an object, recursively through their nested types:
-
Each structure-type member must have an Offset Decoration.
-
Each array type must have an ArrayStride Decoration.
-
Each structure-type member that is a matrix or array-of-matrices must have be decorated with
-
a MatrixStride Decoration, and
-
one of the RowMajor or ColMajor Decorations.
-
The ArrayStride, MatrixStride, and Offset Decorations must be large enough to hold the size of the objects they affect (that is, specifying overlap is invalid). Each ArrayStride and MatrixStride must be greater than zero, and no two members of a given structure can be assigned to the same Offset.
Meaning
RLSL needs to explicit decorate members of a struct with the correct offset. That offset needs to follow the rules defined in std140
, and this only applies to PushConstant and Uniform.
Implementation
Idea 1
RLSL can easily generate the correct offset from the rules from std140
but then the Rust and RLSL code would get out of sync.
One idea would be to only check if the struct is conformant with the rules from std140
. Then RLSL would just extract the offset that was defined by the user.
Generally annotation of structs doesn't seem reasonable, for example you don't want your Vec3<f32>
to be 16 bytes wide in the vertex input. Either accept the performance hit, or create separate types for the all the uniforms.
Idea 2
#[derive(Packed)]
pub struct SomeStruct{
pub v: Vec4<f32>,
pub f: f32
}
// generated
fn compute_packed(&self) -> Packed<SomeStruct>{..}
Compute the correct layout inside RLSL and implement Packed
in normal Rust as a custom derive.
It would then know how to serialize the specified struct with the rules of std140
.
And Packed
could look like this
struct Packed<T>{
_m: PhantomData<T>,
data: Vec<u8>
}
// Hopefully the max size can be computed statically
struct Packed<T: Uniform>{
_m: PhantomData<T>,
data: [u8; T::Size]
}
For example if you have
let uniforms: Vec<SomeStruct> = ..;
And if you want to send it to the shader you have to pack them correctly
for packed_struct in uniforms.iter().map(SomeStruct::compute_packed){
// pseudo api
some_shader.send(packed_struct);
//draw
}
And inside RLSL you can just use SomeStruct
directly.
I am unsure how exactly I could implement it because custom derive doesn't expose type information but I think there should be a way. I might have to use const_fn
. Alternatively everything could be computed in a build script with the reflection api.
How do you currently handle alignment? What would be your preferred API?