Procedural Macros to create `VertexBufferLayout`
tgross35 opened this issue · 5 comments
Is your feature request related to a problem? Please describe.
I think it is fairly common to have code like this:
#[repr(C)]
#[derive(Copy, Clone, Debug, Pod, Zeroable)]
struct TessVertex {
position: [f32; 2],
normal: [f32; 2],
prim_id: u32,
}
impl TessVertex {
fn desc() -> wgpu::VertexBufferLayout<'static> {
wgpu::VertexBufferLayout {
array_stride: mem::size_of::<Self>() as wgpu::BufferAddress,
step_mode: wgpu::VertexStepMode::Vertex,
attributes: &[
wgpu::VertexAttribute {
offset: 0,
shader_location: 0,
format: wgpu::VertexFormat::Float32x2,
},
wgpu::VertexAttribute {
offset: 8,
shader_location: 1,
format: wgpu::VertexFormat::Float32x2,
},
wgpu::VertexAttribute {
offset: 16,
shader_location: 2,
format: wgpu::VertexFormat::Uint32,
},
],
}
}
}
This is a bit tedious to maintain but is fairly boilerplate, so a macro could help usability here.
Describe the solution you'd like
Probably extract the vertex format to a trait:
trait GpuFormat {
const VERTEX_FORMAT: VertexFormat;
}
// implement this for relevant primitives
impl GpuFormat for f32 { const VERTEX_FORMAT: VertexFormat = VertexFormat::Float32; }
impl GpuFormat for [f32; 2] { const VERTEX_FORMAT: VertexFormat = VertexFormat::Float32x2; }
// ...
And then add a derive macro that leveragesGpuFormat
and the recently-stabilized offset_of
:
// trait to be implemented
trait BufferLayout {
fn vertex_buffer_layout() -> VertexBufferLayout<'static>;
}
// usage
#[derive(BufferLayout)]
struct TessVertex {
#[wgpu(shader_location = 0)]
position: [f32; 2],
#[wgpu(shader_location = 10)]
normal: [f32; 2],
#[wgpu(shader_location = 4)]
prim_id: u32,
}
// code generated by the macro
impl BufferLayout for TessVertex {
fn vertex_buffer_layout() -> VertexBufferLayout<'static> {
VertexBufferLayout {
array_stride: mem::size_of::<Self>() as wgpu::BufferAddress,
step_mode: wgpu::VertexStepMode::Vertex,
attributes: &[
wgpu::VertexAttribute {
offset: std::mem::offset_of!(Self, position),
shader_location: 0,
format: [f32; 2]::VERTEX_FORMAT,
},
wgpu::VertexAttribute {
offset: std::mem::offset_of!(Self, normal),
shader_location: 10,
format: [f32; 2]::VERTEX_FORMAT,
},
wgpu::VertexAttribute {
offset: std::mem::offset_of!(Self, prim_id),
shader_location: 4,
format: u32::VERTEX_FORMAT,
},
],
}
}
}
Note that this exact code above currently hits rust-lang/rust#124478, but that should be fixed soon in rust-lang/rust#124484.
Describe alternatives you've considered
It would be nice if more information could be reflected from the shader, rather than needing to keep the shader and the Rust code in sync (pretty big source of my errors when just getting started). Maybe an alternative to include_wgsl!
could create a rust module with automatically created types for everything in WGSL shader?
I am sure this has been talked about somewhere :)
I also think this would be pretty cool (and also dunno if its been talked about before). It feels like something that could also be done by a separate crate, though it may be a good edition to the util module? I'd like to give it a crack
Do note that we have a vertex_attr_array!
macro.
Agreed that this could probably be developed in a separate crate. I actually kind of like the wgsl reflection proc macro idea even more than just the derive macro. It could be invoked like this:
wgpu::wgsl_module! {
file: "my_shader.wgsl", // file to load
module: my_shader, // Rust module name to create
}
And the macro would:
- Use
naga
to load the file into aModule
- Create a Rust representation of all
Type
s in the module - For each of these types, implement a trait like the
BufferLayout
trait I suggested in the top post - Make a function for all
StructMember
s with a binding to produce the bind specification - Create a
State
struct that holds buffer objects - Add methods on
State
that allow you to pass a queue and a slice of vertex/index objects towrite_buffer
Of course not all of that would be necessary, a MVP would just be creating a Rust representation for any struct
s in the shader. But it would really be convenient to only define data structures once in the shader to get a strongly typed API on the Rust side. Out of sync layouts or mistakes sending data through untyped byte buffers is probably the biggest source of bugs and beginner unfriendliness.
Second that this is a very interesting idea, but definitely not in scope for the wgpu
crate itself.
Thirding this and closing as won't fix.
There's a myriad of things that would be nice sugar on top of wgpu, but it's a slippery slope. vertex_attr_array
goes a long way without hiding too much of the underlying functionality, this proposal here sounds a lot more complex and I really don't want to start adding derive macros to wgpu if we can avoid it :)